@@ -38,6 +38,7 @@ internal sealed class FileSystemWatcherMock : Component, IFileSystemWatcher
3838 NotifyFilters . LastWrite ;
3939
4040 private string _path = string . Empty ;
41+ private string _fullPath = string . Empty ;
4142
4243 private ISynchronizeInvoke ? _synchronizingObject ;
4344
@@ -213,6 +214,7 @@ public string Path
213214 }
214215
215216 _path = value ;
217+ FullPath = _path ;
216218 }
217219 }
218220
@@ -258,6 +260,32 @@ public ISynchronizeInvoke? SynchronizingObject
258260 }
259261 }
260262
263+ /// <summary>
264+ /// Caches the full path of <see cref="Path"/>
265+ /// </summary>
266+ private string FullPath
267+ {
268+ get => _fullPath ;
269+ set
270+ {
271+ if ( string . IsNullOrEmpty ( value ) )
272+ {
273+ _fullPath = value ;
274+
275+ return ;
276+ }
277+
278+ string fullPath = _fileSystem . Path . GetFullPath ( value ) ;
279+
280+ if ( ! fullPath . EndsWith ( _fileSystem . Path . DirectorySeparatorChar ) )
281+ {
282+ fullPath += _fileSystem . Path . DirectorySeparatorChar ;
283+ }
284+
285+ _fullPath = fullPath ;
286+ }
287+ }
288+
261289 /// <inheritdoc cref="IFileSystemWatcher.BeginInit()" />
262290 public void BeginInit ( )
263291 {
@@ -399,19 +427,19 @@ private void NotifyChange(ChangeDescription item)
399427 if ( item . ChangeType . HasFlag ( WatcherChangeTypes . Created ) )
400428 {
401429 Created ? . Invoke ( this , ToFileSystemEventArgs (
402- item . ChangeType , item . Path , item . Name ) ) ;
430+ item . ChangeType , item . Path ) ) ;
403431 }
404432
405433 if ( item . ChangeType . HasFlag ( WatcherChangeTypes . Deleted ) )
406434 {
407435 Deleted ? . Invoke ( this , ToFileSystemEventArgs (
408- item . ChangeType , item . Path , item . Name ) ) ;
436+ item . ChangeType , item . Path ) ) ;
409437 }
410438
411439 if ( item . ChangeType . HasFlag ( WatcherChangeTypes . Changed ) )
412440 {
413441 Changed ? . Invoke ( this , ToFileSystemEventArgs (
414- item . ChangeType , item . Path , item . Name ) ) ;
442+ item . ChangeType , item . Path ) ) ;
415443 }
416444
417445 if ( item . ChangeType . HasFlag ( WatcherChangeTypes . Renamed ) )
@@ -502,68 +530,6 @@ private void Stop()
502530 _changeHandler ? . Dispose ( ) ;
503531 }
504532
505- private FileSystemEventArgs ToFileSystemEventArgs (
506- WatcherChangeTypes changeType ,
507- string changePath ,
508- string ? changeName )
509- {
510- string path = TransformPathAndName (
511- changePath ,
512- changeName ,
513- out string name ) ;
514-
515- FileSystemEventArgs eventArgs = new ( changeType , Path , name ) ;
516- if ( _fileSystem . SimulationMode != SimulationMode . Native )
517- {
518- // FileSystemEventArgs implicitly combines the path in https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs
519- // HACK: Have to resort to Reflection to override this behavior!
520- #if NETFRAMEWORK
521- typeof ( FileSystemEventArgs )
522- . GetField ( "fullPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
523- . SetValue ( eventArgs , path ) ;
524- #else
525- typeof ( FileSystemEventArgs )
526- . GetField ( "_fullPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
527- . SetValue ( eventArgs , path ) ;
528- #endif
529- }
530-
531- return eventArgs ;
532- }
533-
534- private string TransformPathAndName (
535- string changeDescriptionPath ,
536- string ? changeDescriptionName ,
537- out string name )
538- {
539- string ? transformedName = changeDescriptionName ;
540- string ? path = changeDescriptionPath ;
541- if ( ! _fileSystem . Path . IsPathRooted ( Path ) )
542- {
543- string rootedWatchedPath = _fileSystem . Directory . GetCurrentDirectory ( ) ;
544- if ( ! rootedWatchedPath . EndsWith ( _fileSystem . Path . DirectorySeparatorChar ) )
545- {
546- rootedWatchedPath += _fileSystem . Path . DirectorySeparatorChar ;
547- }
548-
549- if ( path . StartsWith ( rootedWatchedPath , _fileSystem . Execute . StringComparisonMode ) )
550- {
551- path = path . Substring ( rootedWatchedPath . Length ) ;
552- }
553-
554- transformedName = _fileSystem . Execute . Path . GetFileName ( changeDescriptionPath ) ;
555- }
556- else if ( transformedName == null ||
557- _fileSystem . Execute . Path . IsPathRooted ( changeDescriptionName ) )
558- {
559- transformedName = _fileSystem . Execute . Path . GetFileName ( changeDescriptionPath ) ;
560- }
561-
562- name = transformedName ;
563-
564- return path ?? "" ;
565- }
566-
567533 private void TriggerRenameNotification ( ChangeDescription item )
568534 {
569535 if ( _fileSystem . Execute . IsWindows )
@@ -578,13 +544,13 @@ private void TriggerRenameNotification(ChangeDescription item)
578544 if ( MatchesWatcherPath ( item . OldPath ) )
579545 {
580546 Deleted ? . Invoke ( this , ToFileSystemEventArgs (
581- WatcherChangeTypes . Deleted , item . OldPath , item . OldName ) ) ;
547+ WatcherChangeTypes . Deleted , item . OldPath ) ) ;
582548 }
583549
584550 if ( MatchesWatcherPath ( item . Path ) )
585551 {
586552 Created ? . Invoke ( this , ToFileSystemEventArgs (
587- WatcherChangeTypes . Created , item . Path , item . Name ) ) ;
553+ WatcherChangeTypes . Created , item . Path ) ) ;
588554 }
589555 }
590556 }
@@ -601,54 +567,92 @@ private void TriggerRenameNotification(ChangeDescription item)
601567
602568 private bool TryMakeRenamedEventArgs (
603569 ChangeDescription changeDescription ,
604- [ NotNullWhen ( true ) ] out RenamedEventArgs ? eventArgs )
570+ [ NotNullWhen ( true ) ] out RenamedEventArgs ? eventArgs
571+ )
605572 {
606573 if ( changeDescription . OldPath == null )
607574 {
608575 eventArgs = null ;
576+
609577 return false ;
610578 }
611579
612- string path = TransformPathAndName (
613- changeDescription . Path ,
614- changeDescription . Name ,
615- out string name ) ;
580+ string name = TransformPathAndName ( changeDescription . Path ) ;
581+
582+ string oldName = TransformPathAndName ( changeDescription . OldPath ) ;
583+
584+ eventArgs = new RenamedEventArgs ( changeDescription . ChangeType , Path , name , oldName ) ;
585+
586+ SetFileSystemEventArgsFullPath ( eventArgs , name ) ;
587+ SetRenamedEventArgsFullPath ( eventArgs , oldName ) ;
616588
617- string oldPath = TransformPathAndName (
618- changeDescription . OldPath ,
619- changeDescription . OldName ,
620- out string oldName ) ;
589+ return _fileSystem . Execute . Path . GetDirectoryName ( changeDescription . Path ) ? . Equals (
590+ _fileSystem . Execute . Path . GetDirectoryName ( changeDescription . OldPath ) ,
591+ _fileSystem . Execute . StringComparisonMode
592+ )
593+ ?? true ;
594+ }
595+
596+ private FileSystemEventArgs ToFileSystemEventArgs (
597+ WatcherChangeTypes changeType ,
598+ string changePath )
599+ {
600+ string name = TransformPathAndName ( changePath ) ;
601+
602+ FileSystemEventArgs eventArgs = new ( changeType , Path , name ) ;
603+
604+ SetFileSystemEventArgsFullPath ( eventArgs , name ) ;
605+
606+ return eventArgs ;
607+ }
621608
622- eventArgs = new RenamedEventArgs (
623- changeDescription . ChangeType ,
624- Path ,
625- name ,
626- oldName ) ;
609+ private string TransformPathAndName ( string changeDescriptionPath )
610+ {
611+ return changeDescriptionPath . Substring ( FullPath . Length ) . TrimStart ( _fileSystem . Path . DirectorySeparatorChar ) ;
612+ }
627613
628- if ( _fileSystem . SimulationMode != SimulationMode . Native )
614+ private void SetFileSystemEventArgsFullPath ( FileSystemEventArgs args , string name )
615+ {
616+ if ( _fileSystem . SimulationMode == SimulationMode . Native )
629617 {
630- // RenamedEventArgs implicitly combines the path in https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/RenamedEventArgs.cs
631- // HACK: Have to resort to Reflection to override this behavior!
618+ return ;
619+ }
620+
621+ string fullPath = _fileSystem . Path . Combine ( Path , name ) ;
622+
623+ // FileSystemEventArgs implicitly combines the path in https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs
624+ // HACK: The combination uses the system separator, so to simulate the behavior, we must override it using reflection!
632625#if NETFRAMEWORK
633626 typeof ( FileSystemEventArgs )
634627 . GetField ( "fullPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
635- . SetValue ( eventArgs , path ) ;
628+ . SetValue ( args , fullPath ) ;
629+ #else
630+ typeof ( FileSystemEventArgs )
631+ . GetField ( "_fullPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
632+ . SetValue ( args , fullPath ) ;
633+ #endif
634+ }
635+
636+ private void SetRenamedEventArgsFullPath ( RenamedEventArgs args , string oldName )
637+ {
638+ if ( _fileSystem . SimulationMode == SimulationMode . Native )
639+ {
640+ return ;
641+ }
642+
643+ string fullPath = _fileSystem . Path . Combine ( Path , oldName ) ;
644+
645+ // FileSystemEventArgs implicitly combines the path in https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs
646+ // HACK: The combination uses the system separator, so to simulate the behavior, we must override it using reflection!
647+ #if NETFRAMEWORK
636648 typeof ( RenamedEventArgs )
637649 . GetField ( "oldFullPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
638- . SetValue ( eventArgs , oldPath ) ;
650+ . SetValue ( args , fullPath ) ;
639651#else
640- typeof ( FileSystemEventArgs )
641- . GetField ( "_fullPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
642- . SetValue ( eventArgs , path ) ;
643- typeof ( RenamedEventArgs )
644- . GetField ( "_oldFullPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
645- . SetValue ( eventArgs , oldPath ) ;
652+ typeof ( RenamedEventArgs )
653+ . GetField ( "_oldFullPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
654+ . SetValue ( args , fullPath ) ;
646655#endif
647- }
648-
649- return _fileSystem . Execute . Path . GetDirectoryName ( changeDescription . Path ) ?
650- . Equals ( _fileSystem . Execute . Path . GetDirectoryName ( changeDescription . OldPath ) ,
651- _fileSystem . Execute . StringComparisonMode ) ?? true ;
652656 }
653657
654658 private IWaitForChangedResult WaitForChangedInternal (
0 commit comments