diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs index 8099ead93..127bca599 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs @@ -39,7 +39,8 @@ public MockDirectoryInfo(IMockFileDataAccessor mockFileDataAccessor, string dire } SetDirectoryPath(directoryPath); - Refresh(); + // Like the real FileSystemInfo, the state is read on first access, not on construction. + refreshOnNextRead = true; } #if FEATURE_CREATE_SYMBOLIC_LINK @@ -62,6 +63,7 @@ public override void Refresh() { var mockFileData = mockFileDataAccessor.GetFile(directoryPath) ?? MockFileData.NullObject; cachedMockFileData = mockFileData.Clone(); + refreshOnNextRead = false; } #if FEATURE_CREATE_SYMBOLIC_LINK @@ -304,7 +306,13 @@ public override IDirectoryInfo[] GetDirectories(string searchPattern, Enumeratio private DirectoryInfoBase[] ConvertStringsToDirectories(IEnumerable paths) { return paths - .Select(path => new MockDirectoryInfo(mockFileDataAccessor, path)) + .Select(path => + { + // Enumeration results carry the state found while enumerating. + var directoryInfo = new MockDirectoryInfo(mockFileDataAccessor, path); + directoryInfo.Refresh(); + return directoryInfo; + }) .Cast() .ToArray(); } @@ -338,7 +346,13 @@ public override IFileInfo[] GetFiles(string searchPattern, EnumerationOptions en IFileInfo[] ConvertStringsToFiles(IEnumerable paths) { return paths - .Select(mockFileDataAccessor.FileInfo.New) + .Select(path => + { + // Enumeration results carry the state found while enumerating. + var fileInfo = mockFileDataAccessor.FileInfo.New(path); + fileInfo.Refresh(); + return fileInfo; + }) .ToArray(); } @@ -373,6 +387,7 @@ public override void MoveTo(string destDirName) { mockFileDataAccessor.Directory.Move(directoryPath, destDirName); SetDirectoryPath(destDirName); + refreshOnNextRead = true; } /// diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileInfo.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileInfo.cs index 188ea3e0a..de49aa90f 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileInfo.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileInfo.cs @@ -24,7 +24,8 @@ public MockFileInfo(IMockFileDataAccessor mockFileSystem, string path) : base(mo this.originalPath = path; this.path = mockFileSystem.Path.GetFullPath(path); this.mockFile = new MockFile(mockFileSystem); - Refresh(); + // Like the real FileSystemInfo, the state is read on first access, not on construction. + refreshOnNextRead = true; } #if FEATURE_CREATE_SYMBOLIC_LINK @@ -47,6 +48,7 @@ public override void Refresh() { var mockFileData = mockFileSystem.GetFile(path)?.Clone(); cachedMockFileData = mockFileData ?? MockFileData.NullObject.Clone(); + refreshOnNextRead = false; } #if FEATURE_CREATE_SYMBOLIC_LINK @@ -265,6 +267,7 @@ public override void MoveTo(string destFileName) { mockFile.Move(path, destFileName); path = mockFileSystem.Path.GetFullPath(destFileName); + refreshOnNextRead = true; } #if FEATURE_FILE_MOVE_WITH_OVERWRITE @@ -273,6 +276,7 @@ public override void MoveTo(string destFileName, bool overwrite) { mockFile.Move(path, destFileName, overwrite); path = mockFileSystem.Path.GetFullPath(destFileName); + refreshOnNextRead = true; } #endif diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs index bed4d6702..bed8c2ef1 100644 --- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs +++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs @@ -465,6 +465,24 @@ public async Task MockDirectoryInfo_ToString_ShouldReturnDirectoryName(string di await That(mockDirectoryInfo.ToString()).IsEqualTo(directoryPath); } + [Test] + public async Task MockDirectoryInfo_MoveTo_ShouldDiscardTheCachedData() + { + // Arrange + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\folder"); + fileSystem.AddDirectory(path); + var directoryInfo = fileSystem.DirectoryInfo.New(path); + _ = directoryInfo.Attributes; + fileSystem.File.SetAttributes(path, FileAttributes.Directory | FileAttributes.Hidden); + + // Act + directoryInfo.MoveTo(XFS.Path(@"c:\temp\moved")); + + // Assert + await That(directoryInfo.Attributes.HasFlag(FileAttributes.Hidden)).IsTrue(); + } + [Test] public async Task MockDirectoryInfo_Exists_ShouldReturnCachedData() { @@ -472,6 +490,7 @@ public async Task MockDirectoryInfo_Exists_ShouldReturnCachedData() var fileSystem = new MockFileSystem(); var path = XFS.Path(@"c:\abc"); var directoryInfo = fileSystem.DirectoryInfo.New(path); + _ = directoryInfo.Exists; // Act fileSystem.AddDirectory(path); @@ -721,4 +740,85 @@ public async Task MockDirectoryInfo_LastWriteTimeUtc_SetterShouldThrowDirectoryN await That(() => directoryInfo.LastWriteTime = newTime).Throws(); } -} \ No newline at end of file + [Test] + public async Task MockDirectoryInfo_Exists_ShouldNotReadStateBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\abc"); + var directoryInfo = fileSystem.DirectoryInfo.New(path); + + fileSystem.Directory.CreateDirectory(path); + + await That(directoryInfo.Exists).IsTrue(); + } + + [Test] + public async Task MockDirectoryInfo_Exists_ShouldBeFalseWhenDeletedBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\abc"); + fileSystem.Directory.CreateDirectory(path); + var directoryInfo = fileSystem.DirectoryInfo.New(path); + + fileSystem.Directory.Delete(path); + + await That(directoryInfo.Exists).IsFalse(); + } + + [Test] + public async Task MockDirectoryInfo_Exists_ShouldStayTrueWhenDeletedAfterFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\abc"); + fileSystem.Directory.CreateDirectory(path); + var directoryInfo = fileSystem.DirectoryInfo.New(path); + _ = directoryInfo.Exists; + + fileSystem.Directory.Delete(path); + + await That(directoryInfo.Exists).IsTrue(); + directoryInfo.Refresh(); + await That(directoryInfo.Exists).IsFalse(); + } + + [Test] + public async Task MockDirectoryInfo_LastWriteTime_ShouldReflectChangesBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\abc"); + fileSystem.Directory.CreateDirectory(path); + var directoryInfo = fileSystem.DirectoryInfo.New(path); + var date = new DateTime(2020, 1, 2, 3, 4, 5, DateTimeKind.Local); + + fileSystem.Directory.SetLastWriteTime(path, date); + + await That(directoryInfo.LastWriteTime).IsEqualTo(date); + } + + [Test] + public async Task MockDirectoryInfo_LastWriteTime_ShouldReturnCachedDataAfterFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\abc"); + fileSystem.Directory.CreateDirectory(path); + var directoryInfo = fileSystem.DirectoryInfo.New(path); + var before = directoryInfo.LastWriteTime; + + fileSystem.Directory.SetLastWriteTime(path, new DateTime(2020, 1, 2, 3, 4, 5, DateTimeKind.Local)); + + await That(directoryInfo.LastWriteTime).IsEqualTo(before); + } + + [Test] + public async Task MockDirectoryInfo_FromEnumeration_ShouldHoldStateFromEnumeration() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\abc\sub"); + fileSystem.Directory.CreateDirectory(path); + var directoryInfo = fileSystem.DirectoryInfo.New(XFS.Path(@"c:\abc")).GetDirectories()[0]; + + fileSystem.Directory.Delete(path); + + await That(directoryInfo.Exists).IsTrue(); + } +} diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs index 55a7b1f01..851ecc6b4 100644 --- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs +++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs @@ -872,6 +872,24 @@ public async Task MockFileInfo_Replace_ShouldThrowIfDestinationFileDoesNotExist( await That(() => fileInfo.Replace(path2, null)).Throws(); } + [Test] + public async Task MockFileInfo_MoveTo_ShouldDiscardTheCachedData() + { + // Arrange + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.FileInfo.New(path); + _ = fileInfo.Length; + fileSystem.File.WriteAllText(path, "abcdefghij"); + + // Act + fileInfo.MoveTo(XFS.Path(@"c:\temp\file2.txt")); + + // Assert + await That(fileInfo.Length).IsEqualTo(10); + } + [Test] public async Task MockFileInfo_Exists_ShouldReturnCachedData() { @@ -879,6 +897,7 @@ public async Task MockFileInfo_Exists_ShouldReturnCachedData() var fileSystem = new MockFileSystem(); var path1 = XFS.Path(@"c:\temp\file1.txt"); var fileInfo = fileSystem.FileInfo.New(path1); + _ = fileInfo.Exists; // Act fileSystem.AddFile(path1, new MockFileData("1")); @@ -1054,4 +1073,167 @@ public async Task MockFileInfo_LastWriteTimeWithUnspecifiedDateTimeKind_ShouldSe await That(fileInfo.LastWriteTime).IsEqualTo(date); await That(fileInfo.LastWriteTime.Kind).IsNotEqualTo(DateTimeKind.Unspecified); } -} \ No newline at end of file + + [Test] + public async Task MockFileInfo_Exists_ShouldNotReadStateBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + var fileInfo = fileSystem.FileInfo.New(path); + + fileSystem.AddFile(path, new MockFileData("1")); + + await That(fileInfo.Exists).IsTrue(); + } + + [Test] + public async Task MockFileInfo_Exists_ShouldBeFalseWhenDeletedBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("1")); + var fileInfo = fileSystem.FileInfo.New(path); + + fileSystem.File.Delete(path); + + await That(fileInfo.Exists).IsFalse(); + } + + [Test] + public async Task MockFileInfo_Exists_ShouldStayTrueWhenDeletedAfterFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("1")); + var fileInfo = fileSystem.FileInfo.New(path); + _ = fileInfo.Exists; + + fileSystem.File.Delete(path); + + await That(fileInfo.Exists).IsTrue(); + } + + [Test] + public async Task MockFileInfo_Length_ShouldReturnLengthWhenFileCreatedBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + var fileInfo = fileSystem.FileInfo.New(path); + + fileSystem.AddFile(path, new MockFileData("abc")); + + await That(fileInfo.Length).IsEqualTo(3); + } + + [Test] + public async Task MockFileInfo_Length_ShouldThrowFileNotFoundWhenFileDeletedBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.FileInfo.New(path); + + fileSystem.File.Delete(path); + + await That(() => fileInfo.Length).Throws(); + } + + [Test] + public async Task MockFileInfo_Length_ShouldReflectChangesBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.FileInfo.New(path); + + fileSystem.File.AppendAllText(path, "defg"); + + await That(fileInfo.Length).IsEqualTo(7); + } + + [Test] + public async Task MockFileInfo_Length_ShouldReturnCachedDataAfterFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.FileInfo.New(path); + _ = fileInfo.Length; + + fileSystem.File.AppendAllText(path, "defg"); + + await That(fileInfo.Length).IsEqualTo(3); + fileInfo.Refresh(); + await That(fileInfo.Length).IsEqualTo(7); + } + + [Test] + public async Task MockFileInfo_FirstAccess_ShouldSnapshotAllProperties() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.FileInfo.New(path); + _ = fileInfo.Exists; + + fileSystem.File.AppendAllText(path, "defg"); + fileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + await That(fileInfo.Length).IsEqualTo(3); + await That(fileInfo.Attributes).IsNotEqualTo(FileAttributes.Hidden); + } + + [Test] + public async Task MockFileInfo_Attributes_ShouldReflectChangesBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.FileInfo.New(path); + + fileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + await That(fileInfo.Attributes).IsEqualTo(FileAttributes.Hidden); + } + + [Test] + public async Task MockFileInfo_LastWriteTime_ShouldReflectChangesBeforeFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.FileInfo.New(path); + var date = new DateTime(2020, 1, 2, 3, 4, 5, DateTimeKind.Local); + + fileSystem.File.SetLastWriteTime(path, date); + + await That(fileInfo.LastWriteTime).IsEqualTo(date); + } + + [Test] + public async Task MockFileInfo_LastWriteTime_ShouldReturnCachedDataAfterFirstAccess() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.FileInfo.New(path); + var before = fileInfo.LastWriteTime; + + fileSystem.File.SetLastWriteTime(path, new DateTime(2020, 1, 2, 3, 4, 5, DateTimeKind.Local)); + + await That(fileInfo.LastWriteTime).IsEqualTo(before); + } + + [Test] + public async Task MockFileInfo_FromEnumeration_ShouldHoldStateFromEnumeration() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\temp\file1.txt"); + fileSystem.AddFile(path, new MockFileData("abc")); + var fileInfo = fileSystem.DirectoryInfo.New(XFS.Path(@"c:\temp")).GetFiles()[0]; + + fileSystem.File.AppendAllText(path, "defg"); + + await That(fileInfo.Length).IsEqualTo(3); + } +}