Skip to content

Commit

Permalink
feat: set Creation, LastAccess and LastWrite time for files (#875)
Browse files Browse the repository at this point in the history
Implements #872 
Adjust `CreationTime`, `LastAccessTime` and `LastWriteTime` when interacting with files in the `MockFileSystem`.

Provides a means to mock the used DateTime by calling e.g.
```csharp
var fixedTime = new DateTime(2022, 01, 01);
var fileSystem = new MockFileSystem().MockTime(() => fixedTime);
// All times will now be set to the fixedTime.
```

Implementes the following logic in MockFile:
*All these cases are covered by unit tests in MockFileAdjustTimesTest*
- When creating files
   CreationTime, LastAccessTime and LastWriteTime are set to the current time
- When changing files (`WriteAllText`, `WriteAllBytes`, `AppendAllText`, `OpenWrite`, `Open`)
   LastAccessTime and LastWriteTime are set to the current time
   CreationTime is left unchanged
- When reading files (`ReadAllText`, `ReadAllLines`, `ReadAllBytes`, `OpenRead`)
   LastAccessTime is set to the current time
   CreationTime and LastWriteTime are left unchanged
- When setting attributes or ACLs (`SetAttributes`, `SetAccessControl`)
   LastAccessTime is set to the current time
   CreationTime and LastWriteTime are left unchanged
- When moving files (`Move`)
   LastAccessTime is set to the current time
   CreationTime and LastWriteTime are left unchanged
- When copying files (`Copy`)
   CreationTime and LastAccessTime of the copied file in the destination directory is set to the current time
   LastWriteTime and all times of the source file are left unchanged
  • Loading branch information
vbreuss authored Sep 16, 2022
1 parent d15a5a2 commit a53bd9a
Show file tree
Hide file tree
Showing 18 changed files with 703 additions and 50 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
12 changes: 6 additions & 6 deletions src/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ public override FileAttributes Attributes
/// <inheritdoc />
public override DateTime CreationTime
{
get { return GetMockFileDataForRead().CreationTime.DateTime; }
get { return GetMockFileDataForRead().CreationTime.LocalDateTime; }
set { GetMockFileDataForWrite().CreationTime = value; }
}

/// <inheritdoc />
public override DateTime CreationTimeUtc
{
get { return GetMockFileDataForRead().CreationTime.UtcDateTime; }
set { GetMockFileDataForWrite().CreationTime = value.ToLocalTime(); }
set { GetMockFileDataForWrite().CreationTime = value; }
}

/// <inheritdoc />
Expand Down Expand Up @@ -114,29 +114,29 @@ public override string FullName
/// <inheritdoc />
public override DateTime LastAccessTime
{
get { return GetMockFileDataForRead().LastAccessTime.DateTime; }
get { return GetMockFileDataForRead().LastAccessTime.LocalDateTime; }
set { GetMockFileDataForWrite().LastAccessTime = value; }
}

/// <inheritdoc />
public override DateTime LastAccessTimeUtc
{
get { return GetMockFileDataForRead().LastAccessTime.UtcDateTime; }
set { GetMockFileDataForWrite().LastAccessTime = value.ToLocalTime(); }
set { GetMockFileDataForWrite().LastAccessTime = value; }
}

/// <inheritdoc />
public override DateTime LastWriteTime
{
get { return GetMockFileDataForRead().LastWriteTime.DateTime; }
get { return GetMockFileDataForRead().LastWriteTime.LocalDateTime; }
set { GetMockFileDataForWrite().LastWriteTime = value; }
}

/// <inheritdoc />
public override DateTime LastWriteTimeUtc
{
get { return GetMockFileDataForRead().LastWriteTime.UtcDateTime; }
set { GetMockFileDataForWrite().LastWriteTime = value.ToLocalTime(); }
set { GetMockFileDataForWrite().LastWriteTime = value; }
}

#if FEATURE_FILE_SYSTEM_INFO_LINK_TARGET
Expand Down
44 changes: 30 additions & 14 deletions src/System.IO.Abstractions.TestingHelpers/MockFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,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 @@ -124,7 +125,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 @@ -151,6 +152,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 @@ -188,7 +190,7 @@ public override IFileSystemInfo CreateSymbolicLink(string path, string pathToTar
var sourceFileData = mockFileDataAccessor.GetFile(pathToTarget);
sourceFileData.CheckFileAccess(pathToTarget, FileAccess.Read);
var destFileData = new MockFileData(new byte[0]);
destFileData.CreationTime = destFileData.LastAccessTime = DateTime.Now;
mockFileDataAccessor.AdjustTimes(destFileData, TimeAdjustments.CreationTime | TimeAdjustments.LastAccessTime);
destFileData.LinkTarget = pathToTarget;
mockFileDataAccessor.AddFile(path, destFileData);

Expand Down Expand Up @@ -436,7 +438,7 @@ public override void Move(string sourceFileName, string destFileName)
VerifyDirectoryExists(destFileName);

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

#if FEATURE_FILE_MOVE_WITH_OVERWRITE
Expand Down Expand Up @@ -480,9 +482,9 @@ public override void Move(string sourceFileName, string destFileName, bool overw
throw CommonExceptions.ProcessCannotAccessFileInUse();
}
VerifyDirectoryExists(destFileName);

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

Expand Down Expand Up @@ -539,6 +541,12 @@ private Stream OpenInternal(

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

return new MockFileStream(mockFileDataAccessor, path, mode, access, options);
}
Expand Down Expand Up @@ -578,7 +586,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 @@ -590,10 +600,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 @@ -613,9 +624,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 @@ -713,6 +726,7 @@ public override void SetAccessControl(string path, FileSecurity fileSecurity)
}

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

Expand All @@ -736,6 +750,7 @@ public override void SetAttributes(string path, FileAttributes fileAttributes)
}
else
{
mockFileDataAccessor.AdjustTimes(possibleFileData, TimeAdjustments.LastAccessTime);
possibleFileData.Attributes = fileAttributes;
}
}
Expand Down Expand Up @@ -824,7 +839,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 @@ -1094,7 +1109,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 @@ -1110,6 +1125,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
27 changes: 23 additions & 4 deletions src/System.IO.Abstractions.TestingHelpers/MockFileData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ public class MockFileData
/// </summary>
private MockFileData()
{
// empty
var now = DateTime.UtcNow;
LastWriteTime = now;
LastAccessTime = now;
CreationTime = now;
}

/// <summary>
Expand Down Expand Up @@ -78,6 +81,7 @@ public MockFileData(string textContents, Encoding encoding)
/// <param name="contents">The actual content.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="contents"/> is <see langword="null" />.</exception>
public MockFileData(byte[] contents)
: this()
{
Contents = contents ?? throw new ArgumentNullException(nameof(contents));
}
Expand Down Expand Up @@ -126,17 +130,32 @@ public string TextContents
/// <summary>
/// Gets or sets the date and time the <see cref="MockFileData"/> was created.
/// </summary>
public DateTimeOffset CreationTime { get; set; } = new DateTimeOffset(2010, 01, 02, 00, 00, 00, TimeSpan.FromHours(4));
public DateTimeOffset CreationTime
{
get { return creationTime; }
set{ creationTime = value.ToUniversalTime(); }
}
private DateTimeOffset creationTime;

/// <summary>
/// Gets or sets the date and time of the <see cref="MockFileData"/> was last accessed to.
/// </summary>
public DateTimeOffset LastAccessTime { get; set; } = new DateTimeOffset(2010, 02, 04, 00, 00, 00, TimeSpan.FromHours(4));
public DateTimeOffset LastAccessTime
{
get { return lastAccessTime; }
set { lastAccessTime = value.ToUniversalTime(); }
}
private DateTimeOffset lastAccessTime;

/// <summary>
/// Gets or sets the date and time of the <see cref="MockFileData"/> was last written to.
/// </summary>
public DateTimeOffset LastWriteTime { get; set; } = new DateTimeOffset(2010, 01, 04, 00, 00, 00, TimeSpan.FromHours(4));
public DateTimeOffset LastWriteTime
{
get { return lastWriteTime; }
set { lastWriteTime = value.ToUniversalTime(); }
}
private DateTimeOffset lastWriteTime;

#if FEATURE_FILE_SYSTEM_INFO_LINK_TARGET
/// <summary>
Expand Down
29 changes: 19 additions & 10 deletions src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Runtime.Versioning;
using System.Security.AccessControl;
using System.Text;

namespace System.IO.Abstractions.TestingHelpers
{
Expand Down Expand Up @@ -60,12 +59,12 @@ public override DateTime CreationTime
get
{
var mockFileData = GetMockFileDataForRead();
return mockFileData.CreationTime.DateTime;
return mockFileData.CreationTime.LocalDateTime;
}
set
{
var mockFileData = GetMockFileDataForWrite();
mockFileData.CreationTime = value;
mockFileData.CreationTime = AdjustUnspecifiedKind(value, DateTimeKind.Local);
}
}

Expand All @@ -80,7 +79,7 @@ public override DateTime CreationTimeUtc
set
{
var mockFileData = GetMockFileDataForWrite();
mockFileData.CreationTime = value.ToLocalTime();
mockFileData.CreationTime = AdjustUnspecifiedKind(value, DateTimeKind.Utc);
}
}

Expand Down Expand Up @@ -117,12 +116,12 @@ public override DateTime LastAccessTime
get
{
var mockFileData = GetMockFileDataForRead();
return mockFileData.LastAccessTime.DateTime;
return mockFileData.LastAccessTime.LocalDateTime;
}
set
{
var mockFileData = GetMockFileDataForWrite();
mockFileData.LastAccessTime = value;
mockFileData.LastAccessTime = AdjustUnspecifiedKind(value, DateTimeKind.Local);
}
}

Expand All @@ -137,7 +136,7 @@ public override DateTime LastAccessTimeUtc
set
{
var mockFileData = GetMockFileDataForWrite();
mockFileData.LastAccessTime = value;
mockFileData.LastAccessTime = AdjustUnspecifiedKind(value, DateTimeKind.Utc);
}
}

Expand All @@ -147,12 +146,12 @@ public override DateTime LastWriteTime
get
{
var mockFileData = GetMockFileDataForRead();
return mockFileData.LastWriteTime.DateTime;
return mockFileData.LastWriteTime.LocalDateTime;
}
set
{
var mockFileData = GetMockFileDataForWrite();
mockFileData.LastWriteTime = value;
mockFileData.LastWriteTime = AdjustUnspecifiedKind(value, DateTimeKind.Local);
}
}

Expand All @@ -167,7 +166,7 @@ public override DateTime LastWriteTimeUtc
set
{
var mockFileData = GetMockFileDataForWrite();
mockFileData.LastWriteTime = value.ToLocalTime();
mockFileData.LastWriteTime = AdjustUnspecifiedKind(value, DateTimeKind.Utc);
}
}

Expand Down Expand Up @@ -381,6 +380,16 @@ public override string ToString()
return originalPath;
}

private static DateTime AdjustUnspecifiedKind(DateTime time, DateTimeKind fallbackKind)
{
if (time.Kind == DateTimeKind.Unspecified)
{
return DateTime.SpecifyKind(time, fallbackKind);
}

return time;
}

private MockFileData GetMockFileDataForRead()
{
if (refreshOnNextRead)
Expand Down
Loading

0 comments on commit a53bd9a

Please sign in to comment.