Skip to content

Commit afe3609

Browse files
Erik Renesfgreinacher
authored andcommitted
Initial support for file locking
1 parent 413cd6a commit afe3609

4 files changed

Lines changed: 197 additions & 3 deletions

File tree

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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+
}
166+
}

System.IO.Abstractions.TestingHelpers/MockFile.cs

Lines changed: 12 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
}
@@ -354,7 +355,10 @@ public override void Move(string sourceFileName, string destFileName)
354355
{
355356
throw CommonExceptions.FileNotFound(sourceFileName);
356357
}
357-
358+
if (!sourceFile.AllowedFileShare.HasFlag(FileShare.Delete))
359+
{
360+
throw new IOException("The process cannot access the file because it is being used by another process.");
361+
}
358362
VerifyDirectoryExists(destFileName);
359363

360364
mockFileDataAccessor.AddFile(destFileName, new MockFileData(sourceFile.Contents));
@@ -404,8 +408,10 @@ private Stream OpenInternal(
404408
return Create(path);
405409
}
406410

407-
var length = mockFileDataAccessor.GetFile(path).Contents.Length;
411+
var mockFileData = mockFileDataAccessor.GetFile(path);
412+
mockFileData.CheckFileAccess(path, access);
408413

414+
var length = mockFileData.Contents.Length;
409415
MockFileStream.StreamType streamType = MockFileStream.StreamType.WRITE;
410416
if (access == FileAccess.Read)
411417
streamType = MockFileStream.StreamType.READ;
@@ -446,7 +452,7 @@ public override byte[] ReadAllBytes(string path)
446452
{
447453
throw CommonExceptions.FileNotFound(path);
448454
}
449-
455+
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
450456
return mockFileDataAccessor.GetFile(path).Contents;
451457
}
452458

@@ -458,6 +464,7 @@ public override string[] ReadAllLines(string path)
458464
{
459465
throw CommonExceptions.FileNotFound(path);
460466
}
467+
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
461468

462469
return mockFileDataAccessor
463470
.GetFile(path)
@@ -479,6 +486,7 @@ public override string[] ReadAllLines(string path, Encoding encoding)
479486
throw CommonExceptions.FileNotFound(path);
480487
}
481488

489+
mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
482490
return encoding
483491
.GetString(mockFileDataAccessor.GetFile(path).Contents)
484492
.SplitLines();
@@ -960,6 +968,7 @@ internal static string ReadAllBytes(byte[] contents, Encoding encoding)
960968
private string ReadAllTextInternal(string path, Encoding encoding)
961969
{
962970
var mockFileData = mockFileDataAccessor.GetFile(path);
971+
mockFileData.CheckFileAccess(path, FileAccess.Read);
963972
return ReadAllBytes(mockFileData.Contents, encoding);
964973
}
965974

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 new IOException($"The process cannot access the file '{path}' because it is being used by another process.");
181+
}
164182
}
165183
}

System.IO.Abstractions.TestingHelpers/MockFileSystem.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public void AddFile(string path, MockFileData mockFile)
129129
{
130130
throw CommonExceptions.AccessDenied(path);
131131
}
132+
file.CheckFileAccess(fixedPath, FileAccess.Write);
132133
}
133134

134135
var directoryPath = Path.GetDirectoryName(fixedPath);

0 commit comments

Comments
 (0)