Skip to content

Commit a53bd9a

Browse files
authored
feat: set Creation, LastAccess and LastWrite time for files (#875)
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
1 parent d15a5a2 commit a53bd9a

18 files changed

+703
-50
lines changed

src/System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ namespace System.IO.Abstractions.TestingHelpers
88
/// </summary>
99
public interface IMockFileDataAccessor : IFileSystem
1010
{
11+
/// <summary>
12+
/// Adjust the times of the <paramref name="fileData"/>.
13+
/// </summary>
14+
/// <param name="fileData">The <see cref="MockFileData"/> for which the times should be adjusted.</param>
15+
/// <param name="timeAdjustments">The adjustments to make on the <see cref="MockFileData"/>.</param>
16+
/// <returns>The adjusted file.</returns>
17+
MockFileData AdjustTimes(MockFileData fileData, TimeAdjustments timeAdjustments);
18+
1119
/// <summary>
1220
/// Gets a file.
1321
/// </summary>

src/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ public override FileAttributes Attributes
6262
/// <inheritdoc />
6363
public override DateTime CreationTime
6464
{
65-
get { return GetMockFileDataForRead().CreationTime.DateTime; }
65+
get { return GetMockFileDataForRead().CreationTime.LocalDateTime; }
6666
set { GetMockFileDataForWrite().CreationTime = value; }
6767
}
6868

6969
/// <inheritdoc />
7070
public override DateTime CreationTimeUtc
7171
{
7272
get { return GetMockFileDataForRead().CreationTime.UtcDateTime; }
73-
set { GetMockFileDataForWrite().CreationTime = value.ToLocalTime(); }
73+
set { GetMockFileDataForWrite().CreationTime = value; }
7474
}
7575

7676
/// <inheritdoc />
@@ -114,29 +114,29 @@ public override string FullName
114114
/// <inheritdoc />
115115
public override DateTime LastAccessTime
116116
{
117-
get { return GetMockFileDataForRead().LastAccessTime.DateTime; }
117+
get { return GetMockFileDataForRead().LastAccessTime.LocalDateTime; }
118118
set { GetMockFileDataForWrite().LastAccessTime = value; }
119119
}
120120

121121
/// <inheritdoc />
122122
public override DateTime LastAccessTimeUtc
123123
{
124124
get { return GetMockFileDataForRead().LastAccessTime.UtcDateTime; }
125-
set { GetMockFileDataForWrite().LastAccessTime = value.ToLocalTime(); }
125+
set { GetMockFileDataForWrite().LastAccessTime = value; }
126126
}
127127

128128
/// <inheritdoc />
129129
public override DateTime LastWriteTime
130130
{
131-
get { return GetMockFileDataForRead().LastWriteTime.DateTime; }
131+
get { return GetMockFileDataForRead().LastWriteTime.LocalDateTime; }
132132
set { GetMockFileDataForWrite().LastWriteTime = value; }
133133
}
134134

135135
/// <inheritdoc />
136136
public override DateTime LastWriteTimeUtc
137137
{
138138
get { return GetMockFileDataForRead().LastWriteTime.UtcDateTime; }
139-
set { GetMockFileDataForWrite().LastWriteTime = value.ToLocalTime(); }
139+
set { GetMockFileDataForWrite().LastWriteTime = value; }
140140
}
141141

142142
#if FEATURE_FILE_SYSTEM_INFO_LINK_TARGET

src/System.IO.Abstractions.TestingHelpers/MockFile.cs

+30-14
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,13 @@ public override void AppendAllText(string path, string contents, Encoding encodi
5555
if (!mockFileDataAccessor.FileExists(path))
5656
{
5757
VerifyDirectoryExists(path);
58-
mockFileDataAccessor.AddFile(path, new MockFileData(contents, encoding));
58+
mockFileDataAccessor.AddFile(path, mockFileDataAccessor.AdjustTimes(new MockFileData(contents, encoding), TimeAdjustments.All));
5959
}
6060
else
6161
{
6262
var file = mockFileDataAccessor.GetFile(path);
6363
file.CheckFileAccess(path, FileAccess.Write);
64+
mockFileDataAccessor.AdjustTimes(file, TimeAdjustments.LastAccessTime | TimeAdjustments.LastWriteTime);
6465
var bytesToAppend = encoding.GetBytes(contents);
6566
file.Contents = file.Contents.Concat(bytesToAppend).ToArray();
6667
}
@@ -124,7 +125,7 @@ public override void Copy(string sourceFileName, string destFileName, bool overw
124125
var sourceFileData = mockFileDataAccessor.GetFile(sourceFileName);
125126
sourceFileData.CheckFileAccess(sourceFileName, FileAccess.Read);
126127
var destFileData = new MockFileData(sourceFileData);
127-
destFileData.CreationTime = destFileData.LastAccessTime = DateTime.Now;
128+
mockFileDataAccessor.AdjustTimes(destFileData, TimeAdjustments.CreationTime | TimeAdjustments.LastAccessTime);
128129
mockFileDataAccessor.AddFile(destFileName, destFileData);
129130
}
130131

@@ -151,6 +152,7 @@ private Stream CreateInternal(string path, FileAccess access, FileOptions option
151152
VerifyDirectoryExists(path);
152153

153154
var mockFileData = new MockFileData(new byte[0]);
155+
mockFileDataAccessor.AdjustTimes(mockFileData, TimeAdjustments.All);
154156
mockFileDataAccessor.AddFile(path, mockFileData);
155157
return OpenInternal(path, FileMode.Open, access, options);
156158
}
@@ -188,7 +190,7 @@ public override IFileSystemInfo CreateSymbolicLink(string path, string pathToTar
188190
var sourceFileData = mockFileDataAccessor.GetFile(pathToTarget);
189191
sourceFileData.CheckFileAccess(pathToTarget, FileAccess.Read);
190192
var destFileData = new MockFileData(new byte[0]);
191-
destFileData.CreationTime = destFileData.LastAccessTime = DateTime.Now;
193+
mockFileDataAccessor.AdjustTimes(destFileData, TimeAdjustments.CreationTime | TimeAdjustments.LastAccessTime);
192194
destFileData.LinkTarget = pathToTarget;
193195
mockFileDataAccessor.AddFile(path, destFileData);
194196

@@ -436,7 +438,7 @@ public override void Move(string sourceFileName, string destFileName)
436438
VerifyDirectoryExists(destFileName);
437439

438440
mockFileDataAccessor.RemoveFile(sourceFileName);
439-
mockFileDataAccessor.AddFile(destFileName, new MockFileData(sourceFile));
441+
mockFileDataAccessor.AddFile(destFileName, mockFileDataAccessor.AdjustTimes(new MockFileData(sourceFile), TimeAdjustments.LastAccessTime));
440442
}
441443

442444
#if FEATURE_FILE_MOVE_WITH_OVERWRITE
@@ -480,9 +482,9 @@ public override void Move(string sourceFileName, string destFileName, bool overw
480482
throw CommonExceptions.ProcessCannotAccessFileInUse();
481483
}
482484
VerifyDirectoryExists(destFileName);
483-
485+
484486
mockFileDataAccessor.RemoveFile(sourceFileName);
485-
mockFileDataAccessor.AddFile(destFileName, new MockFileData(sourceFile));
487+
mockFileDataAccessor.AddFile(destFileName, mockFileDataAccessor.AdjustTimes(new MockFileData(sourceFile), TimeAdjustments.LastAccessTime));
486488
}
487489
#endif
488490

@@ -539,6 +541,12 @@ private Stream OpenInternal(
539541

540542
var mockFileData = mockFileDataAccessor.GetFile(path);
541543
mockFileData.CheckFileAccess(path, access);
544+
var timeAdjustments = TimeAdjustments.LastAccessTime;
545+
if (access.HasFlag(FileAccess.Write))
546+
{
547+
timeAdjustments |= TimeAdjustments.LastWriteTime;
548+
}
549+
mockFileDataAccessor.AdjustTimes(mockFileData, timeAdjustments);
542550

543551
return new MockFileStream(mockFileDataAccessor, path, mode, access, options);
544552
}
@@ -578,7 +586,9 @@ public override byte[] ReadAllBytes(string path)
578586
throw CommonExceptions.FileNotFound(path);
579587
}
580588
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
581-
return mockFileDataAccessor.GetFile(path).Contents.ToArray();
589+
var fileData = mockFileDataAccessor.GetFile(path);
590+
mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);
591+
return fileData.Contents.ToArray();
582592
}
583593

584594
/// <inheritdoc />
@@ -590,10 +600,11 @@ public override string[] ReadAllLines(string path)
590600
{
591601
throw CommonExceptions.FileNotFound(path);
592602
}
593-
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
603+
var fileData = mockFileDataAccessor.GetFile(path);
604+
fileData.CheckFileAccess(path, FileAccess.Read);
605+
mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);
594606

595-
return mockFileDataAccessor
596-
.GetFile(path)
607+
return fileData
597608
.TextContents
598609
.SplitLines();
599610
}
@@ -613,9 +624,11 @@ public override string[] ReadAllLines(string path, Encoding encoding)
613624
throw CommonExceptions.FileNotFound(path);
614625
}
615626

616-
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
627+
var fileData = mockFileDataAccessor.GetFile(path);
628+
fileData.CheckFileAccess(path, FileAccess.Read);
629+
mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);
617630

618-
using (var ms = new MemoryStream(mockFileDataAccessor.GetFile(path).Contents))
631+
using (var ms = new MemoryStream(fileData.Contents))
619632
using (var sr = new StreamReader(ms, encoding))
620633
{
621634
return sr.ReadToEnd().SplitLines();
@@ -713,6 +726,7 @@ public override void SetAccessControl(string path, FileSecurity fileSecurity)
713726
}
714727

715728
var fileData = mockFileDataAccessor.GetFile(path);
729+
mockFileDataAccessor.AdjustTimes(fileData, TimeAdjustments.LastAccessTime);
716730
fileData.AccessControl = fileSecurity;
717731
}
718732

@@ -736,6 +750,7 @@ public override void SetAttributes(string path, FileAttributes fileAttributes)
736750
}
737751
else
738752
{
753+
mockFileDataAccessor.AdjustTimes(possibleFileData, TimeAdjustments.LastAccessTime);
739754
possibleFileData.Attributes = fileAttributes;
740755
}
741756
}
@@ -824,7 +839,7 @@ public override void WriteAllBytes(string path, byte[] bytes)
824839
mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(path, "path");
825840
VerifyDirectoryExists(path);
826841

827-
mockFileDataAccessor.AddFile(path, new MockFileData(bytes.ToArray()));
842+
mockFileDataAccessor.AddFile(path, mockFileDataAccessor.AdjustTimes(new MockFileData(bytes.ToArray()), TimeAdjustments.All));
828843
}
829844

830845
/// <summary>
@@ -1094,7 +1109,7 @@ public override void WriteAllText(string path, string contents, Encoding encodin
10941109
VerifyDirectoryExists(path);
10951110

10961111
MockFileData data = contents == null ? new MockFileData(new byte[0]) : new MockFileData(contents, encoding);
1097-
mockFileDataAccessor.AddFile(path, data);
1112+
mockFileDataAccessor.AddFile(path, mockFileDataAccessor.AdjustTimes(data, TimeAdjustments.All));
10981113
}
10991114

11001115
internal static string ReadAllBytes(byte[] contents, Encoding encoding)
@@ -1110,6 +1125,7 @@ private string ReadAllTextInternal(string path, Encoding encoding)
11101125
{
11111126
var mockFileData = mockFileDataAccessor.GetFile(path);
11121127
mockFileData.CheckFileAccess(path, FileAccess.Read);
1128+
mockFileDataAccessor.AdjustTimes(mockFileData, TimeAdjustments.LastAccessTime);
11131129
return ReadAllBytes(mockFileData.Contents, encoding);
11141130
}
11151131

src/System.IO.Abstractions.TestingHelpers/MockFileData.cs

+23-4
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ public class MockFileData
4949
/// </summary>
5050
private MockFileData()
5151
{
52-
// empty
52+
var now = DateTime.UtcNow;
53+
LastWriteTime = now;
54+
LastAccessTime = now;
55+
CreationTime = now;
5356
}
5457

5558
/// <summary>
@@ -78,6 +81,7 @@ public MockFileData(string textContents, Encoding encoding)
7881
/// <param name="contents">The actual content.</param>
7982
/// <exception cref="ArgumentNullException">Thrown if <paramref name="contents"/> is <see langword="null" />.</exception>
8083
public MockFileData(byte[] contents)
84+
: this()
8185
{
8286
Contents = contents ?? throw new ArgumentNullException(nameof(contents));
8387
}
@@ -126,17 +130,32 @@ public string TextContents
126130
/// <summary>
127131
/// Gets or sets the date and time the <see cref="MockFileData"/> was created.
128132
/// </summary>
129-
public DateTimeOffset CreationTime { get; set; } = new DateTimeOffset(2010, 01, 02, 00, 00, 00, TimeSpan.FromHours(4));
133+
public DateTimeOffset CreationTime
134+
{
135+
get { return creationTime; }
136+
set{ creationTime = value.ToUniversalTime(); }
137+
}
138+
private DateTimeOffset creationTime;
130139

131140
/// <summary>
132141
/// Gets or sets the date and time of the <see cref="MockFileData"/> was last accessed to.
133142
/// </summary>
134-
public DateTimeOffset LastAccessTime { get; set; } = new DateTimeOffset(2010, 02, 04, 00, 00, 00, TimeSpan.FromHours(4));
143+
public DateTimeOffset LastAccessTime
144+
{
145+
get { return lastAccessTime; }
146+
set { lastAccessTime = value.ToUniversalTime(); }
147+
}
148+
private DateTimeOffset lastAccessTime;
135149

136150
/// <summary>
137151
/// Gets or sets the date and time of the <see cref="MockFileData"/> was last written to.
138152
/// </summary>
139-
public DateTimeOffset LastWriteTime { get; set; } = new DateTimeOffset(2010, 01, 04, 00, 00, 00, TimeSpan.FromHours(4));
153+
public DateTimeOffset LastWriteTime
154+
{
155+
get { return lastWriteTime; }
156+
set { lastWriteTime = value.ToUniversalTime(); }
157+
}
158+
private DateTimeOffset lastWriteTime;
140159

141160
#if FEATURE_FILE_SYSTEM_INFO_LINK_TARGET
142161
/// <summary>

src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Runtime.Versioning;
22
using System.Security.AccessControl;
3-
using System.Text;
43

54
namespace System.IO.Abstractions.TestingHelpers
65
{
@@ -60,12 +59,12 @@ public override DateTime CreationTime
6059
get
6160
{
6261
var mockFileData = GetMockFileDataForRead();
63-
return mockFileData.CreationTime.DateTime;
62+
return mockFileData.CreationTime.LocalDateTime;
6463
}
6564
set
6665
{
6766
var mockFileData = GetMockFileDataForWrite();
68-
mockFileData.CreationTime = value;
67+
mockFileData.CreationTime = AdjustUnspecifiedKind(value, DateTimeKind.Local);
6968
}
7069
}
7170

@@ -80,7 +79,7 @@ public override DateTime CreationTimeUtc
8079
set
8180
{
8281
var mockFileData = GetMockFileDataForWrite();
83-
mockFileData.CreationTime = value.ToLocalTime();
82+
mockFileData.CreationTime = AdjustUnspecifiedKind(value, DateTimeKind.Utc);
8483
}
8584
}
8685

@@ -117,12 +116,12 @@ public override DateTime LastAccessTime
117116
get
118117
{
119118
var mockFileData = GetMockFileDataForRead();
120-
return mockFileData.LastAccessTime.DateTime;
119+
return mockFileData.LastAccessTime.LocalDateTime;
121120
}
122121
set
123122
{
124123
var mockFileData = GetMockFileDataForWrite();
125-
mockFileData.LastAccessTime = value;
124+
mockFileData.LastAccessTime = AdjustUnspecifiedKind(value, DateTimeKind.Local);
126125
}
127126
}
128127

@@ -137,7 +136,7 @@ public override DateTime LastAccessTimeUtc
137136
set
138137
{
139138
var mockFileData = GetMockFileDataForWrite();
140-
mockFileData.LastAccessTime = value;
139+
mockFileData.LastAccessTime = AdjustUnspecifiedKind(value, DateTimeKind.Utc);
141140
}
142141
}
143142

@@ -147,12 +146,12 @@ public override DateTime LastWriteTime
147146
get
148147
{
149148
var mockFileData = GetMockFileDataForRead();
150-
return mockFileData.LastWriteTime.DateTime;
149+
return mockFileData.LastWriteTime.LocalDateTime;
151150
}
152151
set
153152
{
154153
var mockFileData = GetMockFileDataForWrite();
155-
mockFileData.LastWriteTime = value;
154+
mockFileData.LastWriteTime = AdjustUnspecifiedKind(value, DateTimeKind.Local);
156155
}
157156
}
158157

@@ -167,7 +166,7 @@ public override DateTime LastWriteTimeUtc
167166
set
168167
{
169168
var mockFileData = GetMockFileDataForWrite();
170-
mockFileData.LastWriteTime = value.ToLocalTime();
169+
mockFileData.LastWriteTime = AdjustUnspecifiedKind(value, DateTimeKind.Utc);
171170
}
172171
}
173172

@@ -381,6 +380,16 @@ public override string ToString()
381380
return originalPath;
382381
}
383382

383+
private static DateTime AdjustUnspecifiedKind(DateTime time, DateTimeKind fallbackKind)
384+
{
385+
if (time.Kind == DateTimeKind.Unspecified)
386+
{
387+
return DateTime.SpecifyKind(time, fallbackKind);
388+
}
389+
390+
return time;
391+
}
392+
384393
private MockFileData GetMockFileDataForRead()
385394
{
386395
if (refreshOnNextRead)

0 commit comments

Comments
 (0)