Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions System.IO.Abstractions.TestingHelpers.Tests/MockFileLockTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
namespace System.IO.Abstractions.TestingHelpers.Tests
{
using Collections.Generic;

using NUnit.Framework;

using XFS = MockUnixSupport;
class MockFileLockTests
{
[Test]
public void MockFile_Lock_FileShareNoneThrows()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

Assert.Throws(typeof(IOException), () => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
[Test]
public void MockFile_Lock_FileShareReadDoesNotThrowOnRead()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Read }}
});

Assert.DoesNotThrow(() => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
[Test]
public void MockFile_Lock_FileShareReadThrowsOnWrite()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Read }}
});

Assert.Throws(typeof(IOException), () => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Write, FileShare.Read));
}
[Test]
public void MockFile_Lock_FileShareWriteThrowsOnRead()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Write }}
});

Assert.Throws(typeof(IOException), () => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
[Test]
public void MockFile_Lock_FileShareWriteDoesNotThrowOnWrite()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Write }}
});

Assert.DoesNotThrow(() => filesystem.File.Open(filepath, FileMode.Open, FileAccess.Write, FileShare.Read));
}


[Test]
public void MockFile_Lock_FileShareNoneThrowsOnOpenRead()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

var exception = Assert.Throws(typeof(IOException), () => filesystem.File.OpenRead(filepath));
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
}
[Test]
public void MockFile_Lock_FileShareNoneThrowsOnWriteAllLines()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

var exception = Assert.Throws(typeof(IOException), () => filesystem.File.WriteAllLines(filepath, new string[] { "hello", "world" }));
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
}
[Test]
public void MockFile_Lock_FileShareNoneThrowsOnReadAllLines()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

var exception = Assert.Throws(typeof(IOException), () => filesystem.File.ReadAllLines(filepath));
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
}
[Test]
public void MockFile_Lock_FileShareNoneThrowsOnReadAllText()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

var exception = Assert.Throws(typeof(IOException), () => filesystem.File.ReadAllText(filepath));
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
}
[Test]
public void MockFile_Lock_FileShareNoneThrowsOnReadAllBytes()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

var exception = Assert.Throws(typeof(IOException), () => filesystem.File.ReadAllBytes(filepath));
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
}
[Test]
public void MockFile_Lock_FileShareNoneThrowsOnAppendLines()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

var exception = Assert.Throws(typeof(IOException), () => filesystem.File.AppendAllLines(filepath, new string[] { "hello", "world" }));
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
}

[Test]
public void MockFile_Lock_FileShareNoneThrowsFileMove()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
string target = XFS.Path(@"c:\something\does\notexist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

var exception = Assert.Throws(typeof(IOException), () => filesystem.File.Move(filepath, target));
Assert.That(exception.Message, Is.EqualTo("The process cannot access the file because it is being used by another process."));
}
[Test]
public void MockFile_Lock_FileShareDeleteDoesNotThrowFileMove()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
string target = XFS.Path(@"c:\something\does\notexist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Delete }}
});

Assert.DoesNotThrow(() => filesystem.File.Move(filepath, target));
}
[Test]
public void MockFile_Lock_FileShareNoneThrowsDelete()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.None }}
});

var exception = Assert.Throws(typeof(IOException), () => filesystem.File.Delete(filepath));
Assert.That(exception.Message, Is.EqualTo($"The process cannot access the file '{filepath}' because it is being used by another process."));
}
[Test]
public void MockFile_Lock_FileShareDeleteDoesNotThrowDelete()
{
string filepath = XFS.Path(@"c:\something\does\exist.txt");
var filesystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ filepath, new MockFileData("I'm here") { AllowedFileShare = FileShare.Delete }}
});

Assert.DoesNotThrow(() => filesystem.File.Delete(filepath));
}
}
}
5 changes: 5 additions & 0 deletions System.IO.Abstractions.TestingHelpers/CommonExceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,10 @@ public static ArgumentException IllegalCharactersInPath(string paramName = null)

public static Exception InvalidUncPath(string paramName) =>
new ArgumentException(@"The UNC path should be of the form \\server\share.", paramName);

public static IOException ProcessCannotAccessFileInUse(string paramName = null) =>
paramName != null
? new IOException(string.Format(StringResources.Manager.GetString("PROCESS_CANNOT_ACCESS_FILE_IN_USE_WITH_FILENAME"), paramName))
: new IOException(StringResources.Manager.GetString("PROCESS_CANNOT_ACCESS_FILE_IN_USE"));
}
}
21 changes: 18 additions & 3 deletions System.IO.Abstractions.TestingHelpers/MockFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public override void AppendAllText(string path, string contents, Encoding encodi
else
{
var file = mockFileDataAccessor.GetFile(path);
file.CheckFileAccess(path, FileAccess.Write);
var bytesToAppend = encoding.GetBytes(contents);
file.Contents = file.Contents.Concat(bytesToAppend).ToArray();
}
Expand Down Expand Up @@ -176,6 +177,12 @@ public override void Delete(string path)
// but silently returns if deleting a non-existing file in an existing folder.
VerifyDirectoryExists(path);

var file = mockFileDataAccessor.GetFile(path);
if (file != null && !file.AllowedFileShare.HasFlag(FileShare.Delete))
{
throw CommonExceptions.ProcessCannotAccessFileInUse(path);
}

mockFileDataAccessor.RemoveFile(path);
}

Expand Down Expand Up @@ -354,7 +361,10 @@ public override void Move(string sourceFileName, string destFileName)
{
throw CommonExceptions.FileNotFound(sourceFileName);
}

if (!sourceFile.AllowedFileShare.HasFlag(FileShare.Delete))
{
throw CommonExceptions.ProcessCannotAccessFileInUse();
}
VerifyDirectoryExists(destFileName);

mockFileDataAccessor.AddFile(destFileName, new MockFileData(sourceFile.Contents));
Expand Down Expand Up @@ -404,8 +414,10 @@ private Stream OpenInternal(
return Create(path);
}

var length = mockFileDataAccessor.GetFile(path).Contents.Length;
var mockFileData = mockFileDataAccessor.GetFile(path);
mockFileData.CheckFileAccess(path, access);

var length = mockFileData.Contents.Length;
MockFileStream.StreamType streamType = MockFileStream.StreamType.WRITE;
if (access == FileAccess.Read)
streamType = MockFileStream.StreamType.READ;
Expand Down Expand Up @@ -446,7 +458,7 @@ public override byte[] ReadAllBytes(string path)
{
throw CommonExceptions.FileNotFound(path);
}

mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
return mockFileDataAccessor.GetFile(path).Contents;
}

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

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

mockFileDataAccessor.GetFile(path).CheckFileAccess(path, FileAccess.Read);
return encoding
.GetString(mockFileDataAccessor.GetFile(path).Contents)
.SplitLines();
Expand Down Expand Up @@ -960,6 +974,7 @@ internal static string ReadAllBytes(byte[] contents, Encoding encoding)
private string ReadAllTextInternal(string path, Encoding encoding)
{
var mockFileData = mockFileDataAccessor.GetFile(path);
mockFileData.CheckFileAccess(path, FileAccess.Read);
return ReadAllBytes(mockFileData.Contents, encoding);
}

Expand Down
18 changes: 18 additions & 0 deletions System.IO.Abstractions.TestingHelpers/MockFileData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,23 @@ public FileSecurity AccessControl
}
set { accessControl = value; }
}

/// <summary>
/// Gets or sets the File sharing mode for this file, this allows you to lock a file for reading or writing.
/// </summary>
public FileShare AllowedFileShare { get; set; } = FileShare.ReadWrite | FileShare.Delete;
/// <summary>
/// Checks whether the file is accessible for this type of FileAccess.
/// MockfileData can be configured to have FileShare.None, which indicates it is locked by a 'different process'.
///
/// If the file is 'locked by a different process', an IOException will be thrown.
/// </summary>
/// <param name="path">The path is used in the IOException message to match the message in real life situations</param>
/// <param name="access">The access type to check</param>
internal void CheckFileAccess(string path, FileAccess access)
{
if (!AllowedFileShare.HasFlag((FileShare)access))
throw CommonExceptions.ProcessCannotAccessFileInUse(path);
}
}
}
1 change: 1 addition & 0 deletions System.IO.Abstractions.TestingHelpers/MockFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ public void AddFile(string path, MockFileData mockFile)
{
throw CommonExceptions.AccessDenied(path);
}
file.CheckFileAccess(fixedPath, FileAccess.Write);
}

var directoryPath = Path.GetDirectoryName(fixedPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,10 @@
<data name="FILE_SYSTEM_WATCHER_NOT_IMPLEMENTED_EXCEPTION" xml:space="preserve">
<value>MockFileSystem does not have a built-in FileSystemWatcher implementation. You must provide your own mock or implementation of IFileSystemWatcherFactory and assign it to MockFileSystem.FileSystemWatcher.</value>
</data>
<data name="PROCESS_CANNOT_ACCESS_FILE_IN_USE" xml:space="preserve">
<value>The process cannot access the file because it is being used by another process.</value>
</data>
<data name="PROCESS_CANNOT_ACCESS_FILE_IN_USE_WITH_FILENAME" xml:space="preserve">
<value>The process cannot access the file '{0}' because it is being used by another process.</value>
</data>
</root>