diff --git a/src/System.IO.Abstractions.TestingHelpers/MockDirectoryData.cs b/src/System.IO.Abstractions.TestingHelpers/MockDirectoryData.cs index 7cc83e102..28d8925f1 100644 --- a/src/System.IO.Abstractions.TestingHelpers/MockDirectoryData.cs +++ b/src/System.IO.Abstractions.TestingHelpers/MockDirectoryData.cs @@ -11,9 +11,6 @@ public class MockDirectoryData : MockFileData [NonSerialized] private DirectorySecurity accessControl; - /// - public override bool IsDirectory { get { return true; } } - /// public MockDirectoryData() : base(string.Empty) { diff --git a/src/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs b/src/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs index 61d17c99e..1fa30a351 100644 --- a/src/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs +++ b/src/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs @@ -13,6 +13,8 @@ public class MockDirectoryInfo : DirectoryInfoBase private readonly IMockFileDataAccessor mockFileDataAccessor; private readonly string directoryPath; private readonly string originalPath; + private MockFileData cachedMockFileData; + private bool refreshOnNextRead; /// /// Initializes a new instance of the class. @@ -33,6 +35,7 @@ public MockDirectoryInfo(IMockFileDataAccessor mockFileDataAccessor, string dire directoryPath = directoryPath.TrimEnd(' '); } this.directoryPath = directoryPath; + Refresh(); } /// @@ -44,7 +47,7 @@ public override void Delete() /// public override void Refresh() { - // Nothing to do here. Mock file system is always up-to-date. + cachedMockFileData = mockFileDataAccessor.GetFile(directoryPath)?.Clone(); } /// @@ -71,7 +74,7 @@ public override DateTime CreationTimeUtc /// public override bool Exists { - get { return mockFileDataAccessor.Directory.Exists(FullName); } + get { return GetMockFileDataForRead() != MockFileData.NullObject; } } /// @@ -380,11 +383,17 @@ public override IDirectoryInfo Root private MockFileData GetMockFileDataForRead() { - return mockFileDataAccessor.GetFile(directoryPath) ?? MockFileData.NullObject; + if (refreshOnNextRead) + { + Refresh(); + refreshOnNextRead = false; + } + return cachedMockFileData ?? MockFileData.NullObject; } private MockFileData GetMockFileDataForWrite() { + refreshOnNextRead = true; return mockFileDataAccessor.GetFile(directoryPath) ?? throw CommonExceptions.FileNotFound(directoryPath); } diff --git a/src/System.IO.Abstractions.TestingHelpers/MockFileData.cs b/src/System.IO.Abstractions.TestingHelpers/MockFileData.cs index 213c9ed8b..84f9532dc 100644 --- a/src/System.IO.Abstractions.TestingHelpers/MockFileData.cs +++ b/src/System.IO.Abstractions.TestingHelpers/MockFileData.cs @@ -42,7 +42,7 @@ public class MockFileData /// /// Gets a value indicating whether the is a directory or not. /// - public virtual bool IsDirectory { get { return false; } } + public bool IsDirectory { get { return Attributes.HasFlag(FileAttributes.Directory); } } /// /// Initializes a new instance of the class with an empty content. @@ -183,5 +183,10 @@ internal void CheckFileAccess(string path, FileAccess access) throw CommonExceptions.ProcessCannotAccessFileInUse(path); } } + + internal virtual MockFileData Clone() + { + return new MockFileData(this); + } } } diff --git a/src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs b/src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs index ea6e63685..ad8d4c58e 100644 --- a/src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs +++ b/src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs @@ -10,7 +10,9 @@ public class MockFileInfo : FileInfoBase { private readonly IMockFileDataAccessor mockFileSystem; private string path; - private string originalPath; + private readonly string originalPath; + private MockFileData cachedMockFileData; + private bool refreshOnNextRead; /// public MockFileInfo(IMockFileDataAccessor mockFileSystem, string path) : base(mockFileSystem?.FileSystem) @@ -18,12 +20,7 @@ public MockFileInfo(IMockFileDataAccessor mockFileSystem, string path) : base(mo this.mockFileSystem = mockFileSystem ?? throw new ArgumentNullException(nameof(mockFileSystem)); this.originalPath = path ?? throw new ArgumentNullException(nameof(path)); this.path = mockFileSystem.Path.GetFullPath(path); - - } - - MockFileData MockFileData - { - get { return mockFileSystem.GetFile(path); } + Refresh(); } /// @@ -35,7 +32,7 @@ public override void Delete() /// public override void Refresh() { - // Nothing to do here. Mock file system is always up-to-date. + cachedMockFileData = mockFileSystem.GetFile(path)?.Clone(); } /// @@ -43,19 +40,13 @@ public override FileAttributes Attributes { get { - if (MockFileData == null) - { - throw CommonExceptions.FileNotFound(path); - } - return MockFileData.Attributes; + var mockFileData = GetMockFileDataForRead(); + return mockFileData.Attributes; } set { - if (MockFileData == null) - { - throw CommonExceptions.FileNotFound(path); - } - MockFileData.Attributes = value; + var mockFileData = GetMockFileDataForWrite(); + mockFileData.Attributes = value; } } @@ -64,19 +55,13 @@ public override DateTime CreationTime { get { - if (MockFileData == null) - { - throw CommonExceptions.FileNotFound(path); - } - return MockFileData.CreationTime.DateTime; + var mockFileData = GetMockFileDataForRead(); + return mockFileData.CreationTime.DateTime; } set { - if (MockFileData == null) - { - throw CommonExceptions.FileNotFound(path); - } - MockFileData.CreationTime = value; + var mockFileData = GetMockFileDataForWrite(); + mockFileData.CreationTime = value; } } @@ -85,20 +70,24 @@ public override DateTime CreationTimeUtc { get { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - return MockFileData.CreationTime.UtcDateTime; + var mockFileData = GetMockFileDataForRead(); + return mockFileData.CreationTime.UtcDateTime; } set { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - MockFileData.CreationTime = value.ToLocalTime(); + var mockFileData = GetMockFileDataForWrite(); + mockFileData.CreationTime = value.ToLocalTime(); } } /// public override bool Exists { - get { return MockFileData != null && !MockFileData.IsDirectory; } + get + { + var mockFileData = GetMockFileDataForRead(throwIfNotExisting: false); + return mockFileData != null && !mockFileData.IsDirectory; + } } /// @@ -123,13 +112,13 @@ public override DateTime LastAccessTime { get { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - return MockFileData.LastAccessTime.DateTime; + var mockFileData = GetMockFileDataForRead(); + return mockFileData.LastAccessTime.DateTime; } set { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - MockFileData.LastAccessTime = value; + var mockFileData = GetMockFileDataForWrite(); + mockFileData.LastAccessTime = value; } } @@ -138,13 +127,13 @@ public override DateTime LastAccessTimeUtc { get { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - return MockFileData.LastAccessTime.UtcDateTime; + var mockFileData = GetMockFileDataForRead(); + return mockFileData.LastAccessTime.UtcDateTime; } set { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - MockFileData.LastAccessTime = value; + var mockFileData = GetMockFileDataForWrite(); + mockFileData.LastAccessTime = value; } } @@ -153,13 +142,13 @@ public override DateTime LastWriteTime { get { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - return MockFileData.LastWriteTime.DateTime; + var mockFileData = GetMockFileDataForRead(); + return mockFileData.LastWriteTime.DateTime; } set { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - MockFileData.LastWriteTime = value; + var mockFileData = GetMockFileDataForWrite(); + mockFileData.LastWriteTime = value; } } @@ -168,13 +157,13 @@ public override DateTime LastWriteTimeUtc { get { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - return MockFileData.LastWriteTime.UtcDateTime; + var mockFileData = GetMockFileDataForRead(); + return mockFileData.LastWriteTime.UtcDateTime; } set { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - MockFileData.LastWriteTime = value.ToLocalTime(); + var mockFileData = GetMockFileDataForWrite(); + mockFileData.LastWriteTime = value.ToLocalTime(); } } @@ -201,7 +190,11 @@ public override IFileInfo CopyTo(string destFileName, bool overwrite) { if (!Exists) { - if (MockFileData == null) throw CommonExceptions.FileNotFound(FullName); + var mockFileData = GetMockFileDataForRead(throwIfNotExisting: false); + if (mockFileData == null) + { + throw CommonExceptions.FileNotFound(FullName); + } } if (destFileName == FullName) { @@ -226,17 +219,15 @@ public override StreamWriter CreateText() /// public override void Decrypt() { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - - MockFileData.Attributes &= ~FileAttributes.Encrypted; + var mockFileData = GetMockFileDataForWrite(); + mockFileData.Attributes &= ~FileAttributes.Encrypted; } /// public override void Encrypt() { - if (MockFileData == null) throw CommonExceptions.FileNotFound(path); - - MockFileData.Attributes |= FileAttributes.Encrypted; + var mockFileData = GetMockFileDataForWrite(); + mockFileData.Attributes |= FileAttributes.Encrypted; } /// @@ -341,25 +332,19 @@ public override bool IsReadOnly { get { - if (MockFileData == null) - { - throw CommonExceptions.FileNotFound(path); - } - return (MockFileData.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; + var mockFileData = GetMockFileDataForRead(); + return (mockFileData.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; } set { - if (MockFileData == null) - { - throw CommonExceptions.FileNotFound(path); - } + var mockFileData = GetMockFileDataForWrite(); if (value) { - MockFileData.Attributes |= FileAttributes.ReadOnly; + mockFileData.Attributes |= FileAttributes.ReadOnly; } else { - MockFileData.Attributes &= ~FileAttributes.ReadOnly; + mockFileData.Attributes &= ~FileAttributes.ReadOnly; } } } @@ -369,11 +354,12 @@ public override long Length { get { - if (MockFileData == null || MockFileData.IsDirectory) + var mockFileData = GetMockFileDataForRead(throwIfNotExisting: false); + if (mockFileData == null || mockFileData.IsDirectory) { throw CommonExceptions.FileNotFound(path); } - return MockFileData.Contents.Length; + return mockFileData.Contents.Length; } } @@ -382,5 +368,34 @@ public override string ToString() { return originalPath; } + + private MockFileData GetMockFileDataForRead(bool throwIfNotExisting = true) + { + if (refreshOnNextRead) + { + Refresh(); + refreshOnNextRead = false; + } + var mockFileData = cachedMockFileData; + if (mockFileData == null) + { + if (throwIfNotExisting) + { + throw CommonExceptions.FileNotFound(path); + } + else + { + return null; + } + } + return mockFileData; + } + + private MockFileData GetMockFileDataForWrite() + { + refreshOnNextRead = true; + return mockFileSystem.GetFile(path) + ?? throw CommonExceptions.FileNotFound(path); + } } } diff --git a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs index b8fc34e15..7b894f755 100644 --- a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs +++ b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs @@ -393,5 +393,55 @@ public void MockDirectoryInfo_ToString_ShouldReturnDirectoryName(string director // Assert Assert.AreEqual(directoryPath, mockDirectoryInfo.ToString()); } + + [Test] + public void MockDirectoryInfo_Exists_ShouldReturnCachedData() + { + // Arrange + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\abc"); + var directoryInfo = fileSystem.DirectoryInfo.FromDirectoryName(path); + + // Act + fileSystem.AddDirectory(path); + + // Assert + Assert.IsFalse(directoryInfo.Exists); + } + + [Test] + public void MockDirectoryInfo_Exists_ShouldUpdateCachedDataOnRefresh() + { + // Arrange + var fileSystem = new MockFileSystem(); + var path = XFS.Path(@"c:\abc"); + var directoryInfo = fileSystem.DirectoryInfo.FromDirectoryName(path); + + // Act + fileSystem.AddDirectory(path); + directoryInfo.Refresh(); + + // Assert + Assert.IsTrue(directoryInfo.Exists); + } + + [Test] + public void MockDirectoryInfo_LastAccessTime_ShouldReflectChangedValue() + { + // Arrange + var path = XFS.Path(@"c:\abc"); + var fileSystem = new MockFileSystem(new Dictionary + { + { path, new MockDirectoryData() } + }); + var directoryInfo = fileSystem.DirectoryInfo.FromDirectoryName(path); + var lastAccessTime = new DateTime(2022, 1, 8); + + // Act + directoryInfo.LastAccessTime = lastAccessTime; + + // Assert + Assert.AreEqual(lastAccessTime, directoryInfo.LastAccessTime); + } } } \ No newline at end of file diff --git a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs index 82ae5f0cb..440d2b7a7 100644 --- a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs +++ b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs @@ -718,5 +718,36 @@ public void MockFileInfo_Replace_ShouldThrowIfDestinationFileDoesNotExist() Assert.Throws(() => fileInfo.Replace(path2, null)); } + + [Test] + public void MockFileInfo_Exists_ShouldReturnCachedData() + { + // Arrange + var fileSystem = new MockFileSystem(); + var path1 = XFS.Path(@"c:\temp\file1.txt"); + var fileInfo = fileSystem.FileInfo.FromFileName(path1); + + // Act + fileSystem.AddFile(path1, new MockFileData("1")); + + // Assert + Assert.IsFalse(fileInfo.Exists); + } + + [Test] + public void MockFileInfo_Exists_ShouldUpdateCachedDataOnRefresh() + { + // Arrange + var fileSystem = new MockFileSystem(); + var path1 = XFS.Path(@"c:\temp\file1.txt"); + var fileInfo = fileSystem.FileInfo.FromFileName(path1); + + // Act + fileSystem.AddFile(path1, new MockFileData("1")); + fileInfo.Refresh(); + + // Assert + Assert.IsTrue(fileInfo.Exists); + } } }