Skip to content

Commit

Permalink
Adjust times in MockFileSystem
Browse files Browse the repository at this point in the history
Also apply time in MockFileStream
Mark MockFile_AfterSetAccessControl_ShouldUpdateLastAccessTime as Windows-Only
  • Loading branch information
vbtig committed Aug 11, 2022
1 parent ad875d7 commit 7ecbd63
Show file tree
Hide file tree
Showing 12 changed files with 502 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ namespace System.IO.Abstractions.TestingHelpers
/// </summary>
public interface IMockFileDataAccessor : IFileSystem
{
/// <summary>
/// Adjust the times of the <paramref name="fileData"/>.
/// </summary>
/// <param name="fileData">The <see cref="MockFileData"/> for which the times should be adjusted.</param>
/// <param name="timeAdjustments">The adjustments to make on the <see cref="MockFileData"/>.</param>
/// <returns>The adjusted file.</returns>
MockFileData AdjustTimes(MockFileData fileData, TimeAdjustments timeAdjustments);

/// <summary>
/// Gets a file.
/// </summary>
Expand Down
42 changes: 29 additions & 13 deletions src/System.IO.Abstractions.TestingHelpers/MockFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,13 @@ public override void AppendAllText(string path, string contents, Encoding encodi
if (!mockFileDataAccessor.FileExists(path))
{
VerifyDirectoryExists(path);
mockFileDataAccessor.AddFile(path, new MockFileData(contents, encoding));
mockFileDataAccessor.AddFile(path, mockFileDataAccessor.AdjustTimes(new MockFileData(contents, encoding), TimeAdjustments.All));
}
else
{
var file = mockFileDataAccessor.GetFile(path);
file.CheckFileAccess(path, FileAccess.Write);
mockFileDataAccessor.AdjustTimes(file, TimeAdjustments.LastAccessTime | TimeAdjustments.LastWriteTime);
var bytesToAppend = encoding.GetBytes(contents);
file.Contents = file.Contents.Concat(bytesToAppend).ToArray();
}
Expand Down Expand Up @@ -126,7 +127,7 @@ public override void Copy(string sourceFileName, string destFileName, bool overw
var sourceFileData = mockFileDataAccessor.GetFile(sourceFileName);
sourceFileData.CheckFileAccess(sourceFileName, FileAccess.Read);
var destFileData = new MockFileData(sourceFileData);
destFileData.CreationTime = destFileData.LastAccessTime = DateTime.Now;
mockFileDataAccessor.AdjustTimes(destFileData, TimeAdjustments.CreationTime | TimeAdjustments.LastAccessTime);
mockFileDataAccessor.AddFile(destFileName, destFileData);
}

Expand All @@ -153,6 +154,7 @@ private Stream CreateInternal(string path, FileAccess access, FileOptions option
VerifyDirectoryExists(path);

var mockFileData = new MockFileData(new byte[0]);
mockFileDataAccessor.AdjustTimes(mockFileData, TimeAdjustments.All);
mockFileDataAccessor.AddFile(path, mockFileData);
return OpenInternal(path, FileMode.Open, access, options);
}
Expand Down Expand Up @@ -396,8 +398,8 @@ public override void Move(string sourceFileName, string destFileName)
throw CommonExceptions.ProcessCannotAccessFileInUse();
}
VerifyDirectoryExists(destFileName);

mockFileDataAccessor.AddFile(destFileName, new MockFileData(sourceFile));
mockFileDataAccessor.AddFile(destFileName, mockFileDataAccessor.AdjustTimes(new MockFileData(sourceFile), TimeAdjustments.LastAccessTime));
mockFileDataAccessor.RemoveFile(sourceFileName);
}

Expand Down Expand Up @@ -443,7 +445,7 @@ public override void Move(string sourceFileName, string destFileName, bool overw
}
VerifyDirectoryExists(destFileName);

mockFileDataAccessor.AddFile(destFileName, new MockFileData(sourceFile));
mockFileDataAccessor.AddFile(destFileName, mockFileDataAccessor.AdjustTimes(new MockFileData(sourceFile), TimeAdjustments.LastAccessTime));
mockFileDataAccessor.RemoveFile(sourceFileName);
}
#endif
Expand Down Expand Up @@ -501,6 +503,12 @@ private Stream OpenInternal(

var mockFileData = mockFileDataAccessor.GetFile(path);
mockFileData.CheckFileAccess(path, access);
var timeAdjustments = TimeAdjustments.LastAccessTime;
if (access != FileAccess.Read)
{
timeAdjustments |= TimeAdjustments.LastWriteTime;
}
mockFileDataAccessor.AdjustTimes(mockFileData, timeAdjustments);

return new MockFileStream(mockFileDataAccessor, path, mode, access, options);
}
Expand Down Expand Up @@ -540,7 +548,9 @@ public override byte[] ReadAllBytes(string path)
throw CommonExceptions.FileNotFound(path);
}
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
return mockFileDataAccessor.GetFile(path).Contents.ToArray();
var fileData = mockFileDataAccessor.GetFile(path);
mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);
return fileData.Contents.ToArray();
}

/// <inheritdoc />
Expand All @@ -552,10 +562,11 @@ public override string[] ReadAllLines(string path)
{
throw CommonExceptions.FileNotFound(path);
}
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
var fileData = mockFileDataAccessor.GetFile(path);
fileData.CheckFileAccess(path, FileAccess.Read);
mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);

return mockFileDataAccessor
.GetFile(path)
return fileData
.TextContents
.SplitLines();
}
Expand All @@ -575,9 +586,11 @@ public override string[] ReadAllLines(string path, Encoding encoding)
throw CommonExceptions.FileNotFound(path);
}

mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
var fileData = mockFileDataAccessor.GetFile(path);
fileData.CheckFileAccess(path, FileAccess.Read);
mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);

using (var ms = new MemoryStream(mockFileDataAccessor.GetFile(path).Contents))
using (var ms = new MemoryStream(fileData.Contents))
using (var sr = new StreamReader(ms, encoding))
{
return sr.ReadToEnd().SplitLines();
Expand Down Expand Up @@ -675,6 +688,7 @@ public override void SetAccessControl(string path, FileSecurity fileSecurity)
}

var fileData = mockFileDataAccessor.GetFile(path);
mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);
fileData.AccessControl = fileSecurity;
}

Expand All @@ -698,6 +712,7 @@ public override void SetAttributes(string path, FileAttributes fileAttributes)
}
else
{
mockFileDataAccessor.AdjustTimes(possibleFileData, TimeAdjustments.LastAccessTime);
possibleFileData.Attributes = fileAttributes;
}
}
Expand Down Expand Up @@ -786,7 +801,7 @@ public override void WriteAllBytes(string path, byte[] bytes)
mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(path, "path");
VerifyDirectoryExists(path);

mockFileDataAccessor.AddFile(path, new MockFileData(bytes.ToArray()));
mockFileDataAccessor.AddFile(path, mockFileDataAccessor.AdjustTimes(new MockFileData(bytes.ToArray()), TimeAdjustments.All));
}

/// <summary>
Expand Down Expand Up @@ -1056,7 +1071,7 @@ public override void WriteAllText(string path, string contents, Encoding encodin
VerifyDirectoryExists(path);

MockFileData data = contents == null ? new MockFileData(new byte[0]) : new MockFileData(contents, encoding);
mockFileDataAccessor.AddFile(path, data);
mockFileDataAccessor.AddFile(path, mockFileDataAccessor.AdjustTimes(data, TimeAdjustments.All));
}

internal static string ReadAllBytes(byte[] contents, Encoding encoding)
Expand All @@ -1072,6 +1087,7 @@ private string ReadAllTextInternal(string path, Encoding encoding)
{
var mockFileData = mockFileDataAccessor.GetFile(path);
mockFileData.CheckFileAccess(path, FileAccess.Read);
mockFileDataAccessor.AdjustTimes(mockFileData, TimeAdjustments.LastAccessTime);
return ReadAllBytes(mockFileData.Contents, encoding);
}

Expand Down
10 changes: 7 additions & 3 deletions src/System.IO.Abstractions.TestingHelpers/MockFileStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public MockFileStream(
fileData = mockFileDataAccessor.GetFile(path);
fileData.CheckFileAccess(path, access);

mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);
var existingContents = fileData.Contents;
var keepExistingContents =
existingContents?.Length > 0 &&
Expand All @@ -60,7 +61,8 @@ public MockFileStream(
}

fileData = new MockFileData(new byte[] { });
fileData.CreationTime = fileData.LastWriteTime = fileData.LastAccessTime = DateTime.Now;
mockFileDataAccessor.AdjustTimes(fileData,
TimeAdjustments.CreationTime | TimeAdjustments.LastAccessTime);
mockFileDataAccessor.AddFile(path, fileData);
}

Expand All @@ -76,14 +78,16 @@ public MockFileStream(
/// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count)
{
fileData.LastAccessTime = DateTime.Now;
mockFileDataAccessor.AdjustTimes(fileData,
TimeAdjustments.LastAccessTime);
return base.Read(buffer, offset, count);
}

/// <inheritdoc />
public override void Write(byte[] buffer, int offset, int count)
{
fileData.LastWriteTime = fileData.LastAccessTime = DateTime.Now;
mockFileDataAccessor.AdjustTimes(fileData,
TimeAdjustments.LastAccessTime | TimeAdjustments.LastWriteTime);
base.Write(buffer, offset, count);
}

Expand Down
42 changes: 41 additions & 1 deletion src/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class MockFileSystem : FileSystemBase, IMockFileDataAccessor

private readonly IDictionary<string, FileSystemEntry> files;
private readonly PathVerifier pathVerifier;
[NonSerialized]
private Func<DateTime> dateTimeProvider = defaultDateTimeProvider;
private static Func<DateTime> defaultDateTimeProvider = () => DateTime.Now;

/// <inheritdoc />
public MockFileSystem() : this(null) { }
Expand Down Expand Up @@ -88,6 +91,19 @@ public MockFileSystem(IDictionary<string, MockFileData> files, string currentDir
/// <inheritdoc />
public PathVerifier PathVerifier => pathVerifier;

/// <summary>
/// Replaces the time provider with a mocked instance. This allows to influence the used time in tests.
/// <para />
/// If not set, the default implementation returns <see cref="DateTime.Now"/>.
/// </summary>
/// <param name="dateTimeProvider">The function that returns the current <see cref="DateTime"/>.</param>
/// <returns></returns>
public MockFileSystem MockTime(Func<DateTime> dateTimeProvider)
{
this.dateTimeProvider = dateTimeProvider ?? defaultDateTimeProvider;
return this;
}

private string FixPath(string path, bool checkCaps = false)
{
if (path == null)
Expand Down Expand Up @@ -124,6 +140,28 @@ private string GetPathWithCorrectDirectoryCapitalization(string fullPath)
return fullPath.TrimSlashes();
}

/// <inheritdoc />
public MockFileData AdjustTimes(MockFileData fileData, TimeAdjustments timeAdjustments)
{
var now = (dateTimeProvider ?? defaultDateTimeProvider)();
if ((timeAdjustments & TimeAdjustments.CreationTime) != TimeAdjustments.None)
{
fileData.CreationTime = now;
}

if ((timeAdjustments & TimeAdjustments.LastAccessTime) != TimeAdjustments.None)
{
fileData.LastAccessTime = now;
}

if ((timeAdjustments & TimeAdjustments.LastWriteTime) != TimeAdjustments.None)
{
fileData.LastWriteTime = now;
}

return fileData;
}

/// <inheritdoc />
public MockFileData GetFile(string path)
{
Expand All @@ -143,6 +181,7 @@ public void AddFile(string path, MockFileData mockFile)
var fixedPath = FixPath(path, true);
lock (files)
{
mockFile ??= new MockFileData(string.Empty);
var file = GetFile(fixedPath);

if (file != null)
Expand All @@ -155,6 +194,7 @@ public void AddFile(string path, MockFileData mockFile)
throw CommonExceptions.AccessDenied(path);
}
file.CheckFileAccess(fixedPath, FileAccess.Write);
mockFile.CreationTime = file.CreationTime;
}

var directoryPath = Path.GetDirectoryName(fixedPath);
Expand All @@ -164,7 +204,7 @@ public void AddFile(string path, MockFileData mockFile)
AddDirectory(directoryPath);
}

SetEntry(fixedPath, mockFile ?? new MockFileData(string.Empty));
SetEntry(fixedPath, mockFile);
}
}

Expand Down
30 changes: 30 additions & 0 deletions src/System.IO.Abstractions.TestingHelpers/TimeAdjustments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace System.IO.Abstractions.TestingHelpers
{
/// <summary>
/// Flags indicating which times to adjust for a <see cref="MockFileData"/>.
/// </summary>
[Flags]
public enum TimeAdjustments
{
/// <summary>
/// Adjusts no times on the <see cref="MockFileData"/>
/// </summary>
None = 0,
/// <summary>
/// Adjusts the <see cref="MockFileData.CreationTime"/>
/// </summary>
CreationTime = 1 << 0,
/// <summary>
/// Adjusts the <see cref="MockFileData.LastAccessTime"/>
/// </summary>
LastAccessTime = 1 << 1,
/// <summary>
/// Adjusts the <see cref="MockFileData.LastWriteTime"/>
/// </summary>
LastWriteTime = 1 << 2,
/// <summary>
/// Adjusts all times on the <see cref="MockFileData"/>
/// </summary>
All = ~0
}
}
Loading

0 comments on commit 7ecbd63

Please sign in to comment.