Skip to content

Commit cad30b1

Browse files
WeiermannWeiermann
authored andcommitted
# Conflicts: # version.json
2 parents a31f0f2 + e146456 commit cad30b1

File tree

12 files changed

+316
-10
lines changed

12 files changed

+316
-10
lines changed

System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@ public void MockFileInfo_Exists_ShouldReturnFalseIfFileDoesNotExistInMemoryFileS
5050
Assert.IsFalse(result);
5151
}
5252

53+
[Test]
54+
public void MockFileInfo_Exists_ShouldRetunFalseIfPathLeadsToDirectory()
55+
{
56+
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
57+
{
58+
{ XFS.Path(@"c:\a\b\c.txt"), new MockFileData("Demo text content") },
59+
});
60+
var fileInfo = new MockFileInfo(fileSystem, XFS.Path(@"c:\a\b"));
61+
62+
var result = fileInfo.Exists;
63+
64+
Assert.IsFalse(result);
65+
}
66+
5367
[Test]
5468
public void MockFileInfo_Length_ShouldReturnLengthOfFileInMemoryFileSystem()
5569
{
@@ -82,6 +96,21 @@ public void MockFileInfo_Length_ShouldThrowFileNotFoundExceptionIfFileDoesNotExi
8296
Assert.AreEqual(XFS.Path(@"c:\foo.txt"), ex.FileName);
8397
}
8498

99+
[Test]
100+
public void MockFileInfo_Length_ShouldThrowFileNotFoundExceptionIfPathLeadsToDirectory()
101+
{
102+
const string fileContent = "Demo text content";
103+
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
104+
{
105+
{ XFS.Path(@"c:\a\b\c.txt"), new MockFileData(fileContent) },
106+
});
107+
var fileInfo = new MockFileInfo(fileSystem, XFS.Path(@"c:\a\b"));
108+
109+
var ex = Assert.Throws<FileNotFoundException>(() => fileInfo.Length.ToString(CultureInfo.InvariantCulture));
110+
111+
Assert.AreEqual(XFS.Path(@"c:\a\b"), ex.FileName);
112+
}
113+
85114
[Test]
86115
public void MockFileInfo_CreationTimeUtc_ShouldReturnCreationTimeUtcOfFileInMemoryFileSystem()
87116
{
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
namespace System.IO.Abstractions.TestingHelpers.Tests
2+
{
3+
using Collections.Generic;
4+
5+
using NUnit.Framework;
6+
7+
using XFS = MockUnixSupport;
8+
class MockFileLockTests
9+
{
10+
[Test]
11+
public void MockFile_Lock_FileShareNoneThrows()
12+
{
13+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
14+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
15+
{
16+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
17+
});
18+
19+
Assert.Throws(typeof(IOException), () => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
20+
}
21+
[Test]
22+
public void MockFile_Lock_FileShareReadDoesNotThrowOnRead()
23+
{
24+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
25+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
26+
{
27+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Read }}
28+
});
29+
30+
Assert.DoesNotThrow(() => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
31+
}
32+
[Test]
33+
public void MockFile_Lock_FileShareReadThrowsOnWrite()
34+
{
35+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
36+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
37+
{
38+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Read }}
39+
});
40+
41+
Assert.Throws(typeof(IOException), () => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Write, FileShare.Read));
42+
}
43+
[Test]
44+
public void MockFile_Lock_FileShareWriteThrowsOnRead()
45+
{
46+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
47+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
48+
{
49+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Write }}
50+
});
51+
52+
Assert.Throws(typeof(IOException), () => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
53+
}
54+
[Test]
55+
public void MockFile_Lock_FileShareWriteDoesNotThrowOnWrite()
56+
{
57+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
58+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
59+
{
60+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Write }}
61+
});
62+
63+
Assert.DoesNotThrow(() => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Write, FileShare.Read));
64+
}
65+
66+
67+
[Test]
68+
public void MockFile_Lock_FileShareNoneThrowsOnOpenRead()
69+
{
70+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
71+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
72+
{
73+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
74+
});
75+
76+
var exception = Assert.Throws(typeof(IOException), () => filesystem.File.OpenRead(filepath));
77+
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
78+
}
79+
[Test]
80+
public void MockFile_Lock_FileShareNoneThrowsOnWriteAllLines()
81+
{
82+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
83+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
84+
{
85+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
86+
});
87+
88+
var exception = Assert.Throws(typeof(IOException), () => filesystem.File.WriteAllLines(filepath, new string[] { "hello", "world" }));
89+
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
90+
}
91+
[Test]
92+
public void MockFile_Lock_FileShareNoneThrowsOnReadAllLines()
93+
{
94+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
95+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
96+
{
97+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
98+
});
99+
100+
var exception = Assert.Throws(typeof(IOException), () => filesystem.File.ReadAllLines(filepath));
101+
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
102+
}
103+
[Test]
104+
public void MockFile_Lock_FileShareNoneThrowsOnReadAllText()
105+
{
106+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
107+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
108+
{
109+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
110+
});
111+
112+
var exception = Assert.Throws(typeof(IOException), () => filesystem.File.ReadAllText(filepath));
113+
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
114+
}
115+
[Test]
116+
public void MockFile_Lock_FileShareNoneThrowsOnReadAllBytes()
117+
{
118+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
119+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
120+
{
121+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
122+
});
123+
124+
var exception = Assert.Throws(typeof(IOException), () => filesystem.File.ReadAllBytes(filepath));
125+
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
126+
}
127+
[Test]
128+
public void MockFile_Lock_FileShareNoneThrowsOnAppendLines()
129+
{
130+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
131+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
132+
{
133+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
134+
});
135+
136+
var exception = Assert.Throws(typeof(IOException), () => filesystem.File.AppendAllLines(filepath, new string[] { "hello", "world" }));
137+
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
138+
}
139+
140+
[Test]
141+
public void MockFile_Lock_FileShareNoneThrowsFileMove()
142+
{
143+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
144+
string target = XFS.Path(@"c:\something\does\notexist.txt");
145+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
146+
{
147+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
148+
});
149+
150+
var exception = Assert.Throws(typeof(IOException), () => filesystem.File.Move(filepath, target));
151+
Assert.That(exception.Message, Is.EqualTo("The process cannot access the file because it is being used by another process."));
152+
}
153+
[Test]
154+
public void MockFile_Lock_FileShareDeleteDoesNotThrowFileMove()
155+
{
156+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
157+
string target = XFS.Path(@"c:\something\does\notexist.txt");
158+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
159+
{
160+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Delete }}
161+
});
162+
163+
Assert.DoesNotThrow(() => filesystem.File.Move(filepath, target));
164+
}
165+
[Test]
166+
public void MockFile_Lock_FileShareNoneThrowsDelete()
167+
{
168+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
169+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
170+
{
171+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
172+
});
173+
174+
var exception = Assert.Throws(typeof(IOException), () => filesystem.File.Delete(filepath));
175+
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
176+
}
177+
[Test]
178+
public void MockFile_Lock_FileShareDeleteDoesNotThrowDelete()
179+
{
180+
string filepath = XFS.Path(@"c:\something\does\exist.txt");
181+
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
182+
{
183+
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Delete }}
184+
});
185+
186+
Assert.DoesNotThrow(() => filesystem.File.Delete(filepath));
187+
}
188+
}
189+
}

System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,46 @@ public void MockFileStream_Constructor_ReadTypeNotWritable()
7676
Assert.IsFalse(stream.CanWrite);
7777
Assert.Throws<NotSupportedException>(() => stream.WriteByte(1));
7878
}
79+
80+
[Test]
81+
[TestCase(FileShare.None, MockFileStream.StreamType.READ)]
82+
[TestCase(FileShare.None, MockFileStream.StreamType.WRITE)]
83+
[TestCase(FileShare.None, MockFileStream.StreamType.APPEND)]
84+
[TestCase(FileShare.None, MockFileStream.StreamType.TRUNCATE)]
85+
[TestCase(FileShare.Read, MockFileStream.StreamType.WRITE)]
86+
[TestCase(FileShare.Read, MockFileStream.StreamType.APPEND)]
87+
[TestCase(FileShare.Read, MockFileStream.StreamType.TRUNCATE)]
88+
[TestCase(FileShare.Write, MockFileStream.StreamType.READ)]
89+
public void MockFileStream_Constructor_Insufficient_FileShare_Throws_Exception(FileShare allowedFileShare, MockFileStream.StreamType streamType)
90+
{
91+
var filePath = @"C:\locked.txt";
92+
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
93+
{
94+
{ filePath, new MockFileData("cannot access") { AllowedFileShare = allowedFileShare } }
95+
});
96+
97+
Assert.Throws<IOException>(() => new MockFileStream(fileSystem, filePath, streamType));
98+
}
99+
100+
[Test]
101+
[TestCase(FileShare.Read, MockFileStream.StreamType.READ)]
102+
[TestCase(FileShare.Read | FileShare.Write, MockFileStream.StreamType.READ)]
103+
[TestCase(FileShare.Read | FileShare.Write, MockFileStream.StreamType.APPEND)]
104+
[TestCase(FileShare.Read | FileShare.Write, MockFileStream.StreamType.TRUNCATE)]
105+
[TestCase(FileShare.ReadWrite, MockFileStream.StreamType.READ)]
106+
[TestCase(FileShare.ReadWrite, MockFileStream.StreamType.WRITE)]
107+
[TestCase(FileShare.ReadWrite, MockFileStream.StreamType.APPEND)]
108+
[TestCase(FileShare.ReadWrite, MockFileStream.StreamType.TRUNCATE)]
109+
public void MockFileStream_Constructor_Sufficient_FileShare_Does_Not_Throw_Exception(FileShare allowedFileShare, MockFileStream.StreamType streamType)
110+
{
111+
var filePath = @"C:\locked.txt";
112+
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
113+
{
114+
{ filePath, new MockFileData("cannot access") { AllowedFileShare = allowedFileShare } }
115+
});
116+
117+
Assert.DoesNotThrow(() => new MockFileStream(fileSystem, filePath, streamType));
118+
}
79119

80120
[Test]
81121
public void MockFileStream_Close_MultipleCallsDontThrow()

System.IO.Abstractions.TestingHelpers/CommonExceptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,10 @@ public static ArgumentException IllegalCharactersInPath(string paramName = null)
5454

5555
public static Exception InvalidUncPath(string paramName) =>
5656
new ArgumentException(@"The UNC path should be of the form \\server\share.", paramName);
57+
58+
public static IOException ProcessCannotAccessFileInUse(string paramName = null) =>
59+
paramName != null
60+
? new IOException(string.Format(StringResources.Manager.GetString("PROCESS_CANNOT_ACCESS_FILE_IN_USE_WITH_FILENAME"), paramName))
61+
: new IOException(StringResources.Manager.GetString("PROCESS_CANNOT_ACCESS_FILE_IN_USE"));
5762
}
5863
}

System.IO.Abstractions.TestingHelpers/MockFile.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public override void AppendAllText(string path, string contents, Encoding encodi
6161
else
6262
{
6363
var file = mockFileDataAccessor.GetFile(path);
64+
file.CheckFileAccess(path, FileAccess.Write);
6465
var bytesToAppend = encoding.GetBytes(contents);
6566
file.Contents = file.Contents.Concat(bytesToAppend).ToArray();
6667
}
@@ -176,6 +177,12 @@ public override void Delete(string path)
176177
// but silently returns if deleting a non-existing file in an existing folder.
177178
VerifyDirectoryExists(path);
178179

180+
var file = mockFileDataAccessor.GetFile(path);
181+
if (file != null && !file.AllowedFileShare.HasFlag(FileShare.Delete))
182+
{
183+
throw CommonExceptions.ProcessCannotAccessFileInUse(path);
184+
}
185+
179186
mockFileDataAccessor.RemoveFile(path);
180187
}
181188

@@ -354,7 +361,10 @@ public override void Move(string sourceFileName, string destFileName)
354361
{
355362
throw CommonExceptions.FileNotFound(sourceFileName);
356363
}
357-
364+
if (!sourceFile.AllowedFileShare.HasFlag(FileShare.Delete))
365+
{
366+
throw CommonExceptions.ProcessCannotAccessFileInUse();
367+
}
358368
VerifyDirectoryExists(destFileName);
359369

360370
mockFileDataAccessor.AddFile(destFileName, new MockFileData(sourceFile.Contents));
@@ -404,8 +414,10 @@ private Stream OpenInternal(
404414
return Create(path);
405415
}
406416

407-
var length = mockFileDataAccessor.GetFile(path).Contents.Length;
417+
var mockFileData = mockFileDataAccessor.GetFile(path);
418+
mockFileData.CheckFileAccess(path, access);
408419

420+
var length = mockFileData.Contents.Length;
409421
MockFileStream.StreamType streamType = MockFileStream.StreamType.WRITE;
410422
if (access == FileAccess.Read)
411423
streamType = MockFileStream.StreamType.READ;
@@ -446,7 +458,7 @@ public override byte[] ReadAllBytes(string path)
446458
{
447459
throw CommonExceptions.FileNotFound(path);
448460
}
449-
461+
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
450462
return mockFileDataAccessor.GetFile(path).Contents;
451463
}
452464

@@ -458,6 +470,7 @@ public override string[] ReadAllLines(string path)
458470
{
459471
throw CommonExceptions.FileNotFound(path);
460472
}
473+
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
461474

462475
return mockFileDataAccessor
463476
.GetFile(path)
@@ -479,6 +492,7 @@ public override string[] ReadAllLines(string path, Encoding encoding)
479492
throw CommonExceptions.FileNotFound(path);
480493
}
481494

495+
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
482496
return encoding
483497
.GetString(mockFileDataAccessor.GetFile(path).Contents)
484498
.SplitLines();
@@ -960,6 +974,7 @@ internal static string ReadAllBytes(byte[] contents, Encoding encoding)
960974
private string ReadAllTextInternal(string path, Encoding encoding)
961975
{
962976
var mockFileData = mockFileDataAccessor.GetFile(path);
977+
mockFileData.CheckFileAccess(path, FileAccess.Read);
963978
return ReadAllBytes(mockFileData.Contents, encoding);
964979
}
965980

System.IO.Abstractions.TestingHelpers/MockFileData.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,23 @@ public FileSecurity AccessControl
161161
}
162162
set { accessControl = value; }
163163
}
164+
165+
/// <summary>
166+
/// Gets or sets the File sharing mode for this file, this allows you to lock a file for reading or writing.
167+
/// </summary>
168+
public FileShare AllowedFileShare { get; set; } = FileShare.ReadWrite | FileShare.Delete;
169+
/// <summary>
170+
/// Checks whether the file is accessible for this type of FileAccess.
171+
/// MockfileData can be configured to have FileShare.None, which indicates it is locked by a 'different process'.
172+
///
173+
/// If the file is 'locked by a different process', an IOException will be thrown.
174+
/// </summary>
175+
/// <param name="path">The path is used in the IOException message to match the message in real life situations</param>
176+
/// <param name="access">The access type to check</param>
177+
internal void CheckFileAccess(string path, FileAccess access)
178+
{
179+
if (!AllowedFileShare.HasFlag((FileShare)access))
180+
throw CommonExceptions.ProcessCannotAccessFileInUse(path);
181+
}
164182
}
165183
}

0 commit comments

Comments
 (0)