From 992c1d0f55142a680b9341351e14736f27077d38 Mon Sep 17 00:00:00 2001 From: danbystrom Date: Fri, 13 Nov 2015 10:47:42 +0100 Subject: [PATCH 1/3] Added two constructors to MockFileData to allow for lazy loading of the file content. When mocking the file system, it is commonly populated with a large number of files, where only a few will be relevant for each test suite. By allowing the files content to be lazy loaded, a huge amount of memory and disk reads can be saved. --- .../MockFileDataLazyLoadContentTests.cs | 111 ++++++++++++++++++ TestHelpers.Tests/TestHelpers.Tests.csproj | 1 + TestingHelpers/MockFileData.cs | 40 ++++++- 3 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs diff --git a/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs b/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs new file mode 100644 index 000000000..e7b3abf6b --- /dev/null +++ b/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs @@ -0,0 +1,111 @@ +using System.Linq; +using NUnit.Framework; + +namespace System.IO.Abstractions.TestingHelpers.Tests +{ + [TestFixture] + public class MockFileDataLazyLoadContentTests + { + [Test] + public void MockFileData_AcceptsFuncAndReturnsContents() + { + var contents = Enumerable.Range(0, 100).Select(_ => (byte)_).ToArray(); + var x = new MockFileData(() => contents); + Assert.AreEqual(x.Contents, contents); + } + + [Test] + public void MockFileData_LazyLoadFuncIsCalledOnDemand() + { + var contents = Enumerable.Range(19, 42).Select(_ => (byte)_).ToArray(); + var lazyLoadFuncHasBeenCalled = false; + var x = new MockFileData(() => + { + lazyLoadFuncHasBeenCalled = true; + return contents; + }); + Assert.IsFalse(lazyLoadFuncHasBeenCalled); + Assert.AreEqual(x.Contents, contents); + Assert.IsTrue(lazyLoadFuncHasBeenCalled); + } + + [Test] + public void MockFileData_FuncConstructorThrowsOnNullArgument() + { + Assert.Throws(() => new MockFileData((Func)null)); + } + + } + + [TestFixture] + public class MockFileDataInsertFileInfoObjectTests + { + private IFileSystem destinationFileSystem; + private FileInfoBase destinationFileInfo; + + private const string sourceFileName = @"a:\c.bin"; + private const string destinationFileName = @"b:\c\d.bin"; + private readonly byte[] fileContent = Enumerable.Range(3, 14).Select(_ => (byte)_).ToArray(); + private const FileAttributes attributes = FileAttributes.Hidden; + private readonly DateTime dateCreated = DateTime.Now.AddHours(-3); + private readonly DateTime dateLastWritten = DateTime.Now.AddHours(-2); + private readonly DateTime dateLastAccessed = DateTime.Now.AddHours(-1); + + [TestFixtureSetUp] + public void MockFileData_AcceptsFuncAndReturnsContents() + { + // create a file in a source file system + var sourceFileSystem = new MockFileSystem(); + sourceFileSystem.File.WriteAllBytes(sourceFileName, fileContent); + sourceFileSystem.File.SetAttributes(sourceFileName, attributes); + sourceFileSystem.File.SetCreationTime(sourceFileName, dateCreated); + sourceFileSystem.File.SetLastWriteTime(sourceFileName, dateLastWritten); + sourceFileSystem.File.SetLastAccessTime(sourceFileName, dateLastAccessed); + var sourceFile = sourceFileSystem.DirectoryInfo.FromDirectoryName(Path.GetDirectoryName(sourceFileName)).GetFiles().Single(); + + // create a new destination file system and copy the source file tehre + var dfs = new MockFileSystem(); + dfs.AddFile(destinationFileName, new MockFileData(sourceFileSystem, sourceFile)); + destinationFileInfo = dfs.DirectoryInfo.FromDirectoryName(Path.GetDirectoryName(destinationFileName)).GetFiles().Single(); + destinationFileSystem = dfs; + } + + [Test] + public void TestThatFileHasRightName() + { + Assert.AreEqual(destinationFileName, destinationFileInfo.FullName); + } + + [Test] + public void TestThatFileHasRightContent() + { + Assert.AreEqual(fileContent, destinationFileSystem.File.ReadAllBytes(destinationFileInfo.FullName)); + } + + [Test] + public void TestThatFileHasRightAttributes() + { + Assert.AreEqual(attributes, destinationFileInfo.Attributes); + } + + [Test] + public void TestThatFileHasRightCreationTime() + { + Assert.AreEqual(dateCreated, destinationFileInfo.CreationTime); + } + + [Test] + public void TestThatFileHasRightWriteTime() + { + Assert.AreEqual(dateLastWritten, destinationFileInfo.LastWriteTime); + } + + [Test] + public void TestThatFileHasRightAccessTime() + { + Assert.AreEqual(dateLastAccessed, destinationFileInfo.LastAccessTime); + } + + } + +} diff --git a/TestHelpers.Tests/TestHelpers.Tests.csproj b/TestHelpers.Tests/TestHelpers.Tests.csproj index 0e5cd6409..c62a17478 100644 --- a/TestHelpers.Tests/TestHelpers.Tests.csproj +++ b/TestHelpers.Tests/TestHelpers.Tests.csproj @@ -105,6 +105,7 @@ + diff --git a/TestingHelpers/MockFileData.cs b/TestingHelpers/MockFileData.cs index 4265e034e..ca19dcb8b 100644 --- a/TestingHelpers/MockFileData.cs +++ b/TestingHelpers/MockFileData.cs @@ -27,6 +27,7 @@ public class MockFileData /// The actual contents of the file. /// private byte[] contents; + private readonly Func lazyLoadContent; /// /// The date and time the was created. @@ -67,7 +68,7 @@ private MockFileData() /// The textual content encoded into bytes with . public MockFileData(string textContents) : this(DefaultEncoding.GetBytes(textContents)) - {} + { } /// /// Initializes a new instance of the class with the content of using the encoding of . @@ -97,11 +98,44 @@ public MockFileData(byte[] contents) } /// - /// Gets or sets the byte contents of the . + /// Allows insertion of a file where the content is lazy loaded through a Func. /// + /// Func to return the file's content the first time it is requested. + public MockFileData(Func lazyLoadContent) + { + if (lazyLoadContent == null) + { + throw new ArgumentNullException("lazyLoadContent"); + } + this.lazyLoadContent = lazyLoadContent; + } + + /// + /// Allows insertion of a FileSystemInfoBase object into a new file system. Attributes and time stamps + /// are copied, and the content will be lazy loaded the first time it is touched. + /// + /// The source file system where the file info originates + /// The source file to be copied + public MockFileData(IFileSystem fileSystem, FileSystemInfoBase fileInfo) + { + if (fileSystem == null) + { + throw new ArgumentNullException("fileSystem"); + } + if (fileInfo == null) + { + throw new ArgumentNullException("fileInfo"); + } + creationTime = fileInfo.CreationTime; + lastAccessTime = fileInfo.LastAccessTime; + lastWriteTime = fileInfo.LastWriteTime; + attributes = fileInfo.Attributes; + lazyLoadContent = () => fileSystem.File.ReadAllBytes(fileInfo.FullName); + } + public byte[] Contents { - get { return contents; } + get { return contents ?? (contents = lazyLoadContent()); } set { contents = value; } } From a4fb49968a888501e12f2092113c143fa8b7e247 Mon Sep 17 00:00:00 2001 From: danbystrom Date: Fri, 13 Nov 2015 11:35:22 +0100 Subject: [PATCH 2/3] corrected typo --- TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs b/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs index e7b3abf6b..6899c0fa2 100644 --- a/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs +++ b/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs @@ -63,7 +63,7 @@ public void MockFileData_AcceptsFuncAndReturnsContents() sourceFileSystem.File.SetLastAccessTime(sourceFileName, dateLastAccessed); var sourceFile = sourceFileSystem.DirectoryInfo.FromDirectoryName(Path.GetDirectoryName(sourceFileName)).GetFiles().Single(); - // create a new destination file system and copy the source file tehre + // create a new destination file system and copy the source file there var dfs = new MockFileSystem(); dfs.AddFile(destinationFileName, new MockFileData(sourceFileSystem, sourceFile)); destinationFileInfo = dfs.DirectoryInfo.FromDirectoryName(Path.GetDirectoryName(destinationFileName)).GetFiles().Single(); From 8f96541179761dd09aae60d7e98f8f93466cf15a Mon Sep 17 00:00:00 2001 From: danbystrom Date: Thu, 19 Nov 2015 14:22:40 +0100 Subject: [PATCH 3/3] added tests to ensure that directory browsning does not touch file content until the length of the files is retrieved --- .../MockFileDataLazyLoadContentTests.cs | 52 +++++++++++++++++++ TestingHelpers/MockFileData.cs | 5 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs b/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs index 6899c0fa2..0d5c8a011 100644 --- a/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs +++ b/TestHelpers.Tests/MockFileDataLazyLoadContentTests.cs @@ -37,6 +37,58 @@ public void MockFileData_FuncConstructorThrowsOnNullArgument() } + [TestFixture] + public class MockFileDataLazyLoadAndDirectoryBrowsing + { + private class ContentCalledException : Exception + { + public ContentCalledException() : base("Content was called") + { + } + } + + private IFileSystem fileSystem; + + [SetUp] + public void SetUp() + { + var mockFileData = new MockFileData(() => + { + throw new ContentCalledException(); + }); + fileSystem = new MockFileSystem(); + ((MockFileSystem)fileSystem).AddFile(@"b:\thefile.useless", mockFileData); + } + + [Test] + public void TestThatFileExist() + { + Assert.IsTrue(fileSystem.File.Exists(@"b:\thefile.useless")); + } + + [Test] + public void TestThatDirectoryGetFilesDoesNotLoadContent() + { + Assert.AreEqual(1, fileSystem.Directory.GetFiles(@"B:\").Length); + } + + [Test] + public void TestThatDirectoryInfoGetFilesDoesNotLoadContent() + { + Assert.AreEqual(1, fileSystem.DirectoryInfo.FromDirectoryName(@"B:\").GetFiles().Length); + } + + [Test] + public void TestThatContentIsBeingUsedOnDemandWhenTheFileSizeIsRetrievedThruFileInfoObject() + { + var file = fileSystem.DirectoryInfo.FromDirectoryName(@"B:\").GetFiles().Single(); + long fileLength = -1; + Assert.Throws(typeof(ContentCalledException), () => fileLength = file.Length); + Assert.AreEqual(-1, fileLength); + } + + } + [TestFixture] public class MockFileDataInsertFileInfoObjectTests { diff --git a/TestingHelpers/MockFileData.cs b/TestingHelpers/MockFileData.cs index ca19dcb8b..5b956eefb 100644 --- a/TestingHelpers/MockFileData.cs +++ b/TestingHelpers/MockFileData.cs @@ -68,7 +68,7 @@ private MockFileData() /// The textual content encoded into bytes with . public MockFileData(string textContents) : this(DefaultEncoding.GetBytes(textContents)) - { } + {} /// /// Initializes a new instance of the class with the content of using the encoding of . @@ -133,6 +133,9 @@ public MockFileData(IFileSystem fileSystem, FileSystemInfoBase fileInfo) lazyLoadContent = () => fileSystem.File.ReadAllBytes(fileInfo.FullName); } + /// + /// Gets or sets the byte contents of the . + /// public byte[] Contents { get { return contents ?? (contents = lazyLoadContent()); }