diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c49eb8654..000000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: csharp -mono: none -dotnet: 2.1.4 -script: - - dotnet build System.IO.Abstractions --framework netstandard1.4 - - dotnet build System.IO.Abstractions.TestingHelpers --framework netstandard1.4 - - dotnet test System.IO.Abstractions.TestingHelpers.Tests --framework netcoreapp2.0 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..2f1977e33 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/System.IO.Abstractions.TestingHelpers.Tests/bin/Debug/netcoreapp2.0/System.IO.Abstractions.TestingHelpers.Tests.dll", + "args": [], + "cwd": "${workspaceFolder}/System.IO.Abstractions.TestingHelpers.Tests", + // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ,] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..a4c910c67 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/System.IO.Abstractions.TestingHelpers.Tests/System.IO.Abstractions.TestingHelpers.Tests.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..220091947 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributor guide + +## Versioning + +This library uses [Nerdbank.GitVersioning](https://github.com/AArnott/Nerdbank.GitVersioning) for generating stable and reproducible version numbers. + +The base version is manually maintained in [the version config](version.json). Every build calculates its final version number based on the base version and the number of changes that occured since the last change to the version config. + +The base version represents the MAJOR and MINOR parts of [SemVer](https://semver.org). If a PR contains breaking changes or new features the base version has to be changed accordingly. If a PR solely contains minor changes (bug fixes, code improvements) nothing needs to be done as the PATCH number will automatically increment with each commit. + +## Branches / tags + +* `master` contains the latest sources - this is where we develop. +* `release` contains the sources for the latest version on `nuget.org` - this is where we deploy from. +* All versions on `nuget.org` have a matching GitHub release/tag + +### Release workflow + +1. Create a [PR from `master` to `release`](https://github.com/System-IO-Abstractions/System.IO.Abstractions/compare/release...master?expand=1) and wait for CI to finish. +1. Inspect CI run (test results, version number) +1. Merge PR and wait for deployment +1. Inspect newly created package versions on NuGet.org and newly created GitHub release diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..a4b4cac9e --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 64a1e40e2..4aa074087 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ [![NuGet](https://img.shields.io/nuget/v/System.IO.Abstractions.svg)](https://www.nuget.org/packages/System.IO.Abstractions) - -[![Windows build status](https://ci.appveyor.com/api/projects/status/em172apw1v5k70vq/branch/master?svg=true)](https://ci.appveyor.com/project/tathamoddie/system-io-abstractions/branch/master) on Windows - -[![Linux build status](https://travis-ci.org/System-IO-Abstractions/System.IO.Abstractions.svg?branch=master)](https://travis-ci.org/System-IO-Abstractions/System.IO.Abstractions) on Linux +[![Build status](https://ci.appveyor.com/api/projects/status/em172apw1v5k70vq/branch/master?svg=true)](https://ci.appveyor.com/project/tathamoddie/system-io-abstractions/branch/master) [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=System-IO-Abstractions/System.IO.Abstractions)](https://dependabot.com) --- diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs index 194dde31b..a850a157d 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryInfoTests.cs @@ -36,8 +36,8 @@ public static IEnumerable MockDirectoryInfo_Exists_Cases { get { - yield return new object[]{ XFS.Path(@"c:\temp\folder"), true }; - yield return new object[]{ XFS.Path(@"c:\temp\folder\notExistant"), false }; + yield return new object[] { XFS.Path(@"c:\temp\folder"), true }; + yield return new object[] { XFS.Path(@"c:\temp\folder\notExistant"), false }; } } @@ -152,8 +152,8 @@ public void MockDirectoryInfo_GetParent_ShouldReturnDirectoriesAndNamesWithSearc [Test] public void MockDirectoryInfo_EnumerateFiles_ShouldReturnAllFiles() { - // Arrange - var fileSystem = new MockFileSystem(new Dictionary + // Arrange + var fileSystem = new MockFileSystem(new Dictionary { //Files "above" in folder we're querying { XFS.Path(@"c:\temp\a.txt"), "" }, @@ -166,11 +166,11 @@ public void MockDirectoryInfo_EnumerateFiles_ShouldReturnAllFiles() { XFS.Path(@"c:\temp\folder\deeper\d.txt"), "" } }); - // Act - var directoryInfo = new MockDirectoryInfo(fileSystem, XFS.Path(@"c:\temp\folder")); + // Act + var directoryInfo = new MockDirectoryInfo(fileSystem, XFS.Path(@"c:\temp\folder")); - // Assert - Assert.AreEqual(new[]{"b.txt", "c.txt"}, directoryInfo.EnumerateFiles().ToList().Select(x => x.Name).ToArray()); + // Assert + Assert.AreEqual(new[] { "b.txt", "c.txt" }, directoryInfo.EnumerateFiles().ToList().Select(x => x.Name).ToArray()); } [Test] @@ -238,7 +238,7 @@ public void MockDirectoryInfo_Constructor_ShouldThrowArgumentNullException_IfArg var fileSystem = new MockFileSystem(); // Act - TestDelegate action = () => new MockDirectoryInfo(fileSystem, null); + TestDelegate action = () => new MockDirectoryInfo(fileSystem, null); // Assert var exception = Assert.Throws(action); @@ -272,20 +272,18 @@ public void MockDirectoryInfo_Constructor_ShouldThrowArgumentException_IfArgumen Assert.That(exception.Message, Does.StartWith("The path is not of a legal form.")); } - [Test] - public void MockDirectoryInfo_ToString_ShouldReturnDirectoryName() + [TestCase(@"c:\temp\folder\folder")] + [TestCase(@"..\..\..\Desktop")] + public void MockDirectoryInfo_ToString_ShouldReturnDirectoryName(string directoryName) { - var directoryPath = XFS.Path(@"c:\temp\folder\folder"); - // Arrange - var fileSystem = new MockFileSystem(); - var directoryInfo = new MockDirectoryInfo(fileSystem, directoryPath); + var directoryPath = XFS.Path(directoryName); // Act - var str = directoryInfo.ToString(); + var mockDirectoryInfo = new MockDirectoryInfo(new MockFileSystem(), directoryPath); // Assert - Assert.AreEqual(directoryPath, str); + Assert.AreEqual(directoryPath, mockDirectoryInfo.ToString()); } } } \ No newline at end of file diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs index 9cd17fa75..250241dd2 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs @@ -547,10 +547,10 @@ public void MockDirectory_CreateDirectory_ShouldWorkWithUNCPath() var fileSystem = new MockFileSystem(); // Act - fileSystem.Directory.CreateDirectory(XFS.Path(@"\\server\share\path\to\create", () => false)); + fileSystem.Directory.CreateDirectory(@"\\server\share\path\to\create"); // Assert - Assert.IsTrue(fileSystem.Directory.Exists(XFS.Path(@"\\server\share\path\to\create\", () => false))); + Assert.IsTrue(fileSystem.Directory.Exists(@"\\server\share\path\to\create\")); } [Test] @@ -561,7 +561,7 @@ public void MockDirectory_CreateDirectory_ShouldFailIfTryingToCreateUNCPathOnlyS var fileSystem = new MockFileSystem(); // Act - var ex = Assert.Throws(() => fileSystem.Directory.CreateDirectory(XFS.Path(@"\\server", () => false))); + var ex = Assert.Throws(() => fileSystem.Directory.CreateDirectory(@"\\server")); // Assert StringAssert.StartsWith("The UNC path should be of the form \\\\server\\share.", ex.Message); @@ -576,10 +576,10 @@ public void MockDirectory_CreateDirectory_ShouldSucceedIfTryingToCreateUNCPathSh var fileSystem = new MockFileSystem(); // Act - fileSystem.Directory.CreateDirectory(XFS.Path(@"\\server\share", () => false)); + fileSystem.Directory.CreateDirectory(@"\\server\share"); // Assert - Assert.IsTrue(fileSystem.Directory.Exists(XFS.Path(@"\\server\share\", () => false))); + Assert.IsTrue(fileSystem.Directory.Exists(@"\\server\share\")); } [Test] @@ -599,6 +599,7 @@ public void MockDirectory_Delete_ShouldDeleteDirectory() } [Test] + [WindowsOnly(WindowsSpecifics.CaseInsensitivity)] public void MockDirectory_Delete_ShouldDeleteDirectoryCaseInsensitively() { // Arrange @@ -614,6 +615,40 @@ public void MockDirectory_Delete_ShouldDeleteDirectoryCaseInsensitively() Assert.IsFalse(fileSystem.Directory.Exists(XFS.Path(@"c:\bar"))); } + [Test] + [UnixOnly(UnixSpecifics.CaseSensitivity)] + public void MockDirectory_Delete_ShouldThrowDirectoryNotFoundException_WhenSpecifiedWithInDifferentCase() + { + // Arrange + var fileSystem = new MockFileSystem(new Dictionary + { + { "/bar/foo.txt", new MockFileData("Demo text content") } + }); + + // Act + TestDelegate action = () => fileSystem.Directory.Delete("/BAR", true); + + // Assert + Assert.Throws(action); + } + + [Test] + [UnixOnly(UnixSpecifics.CaseSensitivity)] + public void MockDirectory_Delete_ShouldDeleteDirectoryCaseSensitively() + { + // Arrange + var fileSystem = new MockFileSystem(new Dictionary + { + { "/bar/foo.txt", new MockFileData("Demo text content") } + }); + + // Act + fileSystem.Directory.Delete("/bar", true); + + // Assert + Assert.IsFalse(fileSystem.Directory.Exists("/bar")); + } + [Test] public void MockDirectory_Delete_ShouldThrowDirectoryNotFoundException() { @@ -849,8 +884,8 @@ public void MockDirectory_GetLogicalDrives_Returns_LogicalDrives() else { Assert.AreEqual(2, drives.Length); - Assert.IsTrue(drives.Contains("c:\\")); - Assert.IsTrue(drives.Contains("d:\\")); + Assert.IsTrue(drives.Contains(@"C:\")); + Assert.IsTrue(drives.Contains(@"D:\")); } } #endif @@ -1184,11 +1219,12 @@ public void MockDirectory_GetCurrentDirectory_ShouldReturnValueFromFileSystemCon Assert.AreEqual(directory, actual); } - - + [Test] - public void MockDirectory_GetCurrentDirectory_ShouldReturnDefaultPathWhenNotSet() { - string directory = Path.GetTempPath(); + public void MockDirectory_GetCurrentDirectory_ShouldReturnDefaultPathWhenNotSet() + { + string directory = XFS.Path(@"C:\"); + var fileSystem = new MockFileSystem(); var actual = fileSystem.Directory.GetCurrentDirectory(); @@ -1249,14 +1285,9 @@ public void MockDirectory_GetParent_ShouldReturnADirectoryInfoIfPathDoesNotExist } [Test] + [WindowsOnly(WindowsSpecifics.StrictPathRules)] public void MockDirectory_GetParent_ShouldThrowArgumentExceptionIfPathHasIllegalCharacters() { - if (XFS.IsUnixPlatform()) - { - Assert.Pass("Path.GetInvalidChars() does not return anything on Mono"); - return; - } - // Arrange var fileSystem = new MockFileSystem(); @@ -1281,6 +1312,21 @@ public void MockDirectory_GetParent_ShouldReturnNullIfPathIsRoot() Assert.IsNull(actualResult); } + [Test] + [UnixOnly(UnixSpecifics.SlashRoot)] + public void MockDirectory_GetParent_ShouldReturnRootIfDirectoryIsInRoot() + { + // Arrange + var fileSystem = new MockFileSystem(); + fileSystem.AddDirectory("/bar"); + + // Act + var parent = fileSystem.Directory.GetParent("/bar"); + + // Assert + Assert.AreEqual("/", parent.FullName); + } + public static IEnumerable MockDirectory_GetParent_Cases { get diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockDriveInfoFactoryTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockDriveInfoFactoryTests.cs index 2b10ec883..c6b7d5900 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockDriveInfoFactoryTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockDriveInfoFactoryTests.cs @@ -26,7 +26,7 @@ public void MockDriveInfoFactory_GetDrives_ShouldReturnDrives() var actualNames = actualResults.Select(d => d.Name); // Assert - Assert.That(actualNames, Is.EquivalentTo(new[] { @"C:\", @"Z:\", @"D:\" })); + Assert.That(actualNames, Is.EquivalentTo(new[] { @"C:\", @"Z:\", @"d:\" })); } [Test] @@ -47,7 +47,7 @@ public void MockDriveInfoFactory_GetDrives_ShouldReturnDrivesWithNoDuplicates() var actualNames = actualResults.Select(d => d.Name); // Assert - Assert.That(actualNames, Is.EquivalentTo(new[] { @"C:\", @"Z:\", @"D:\" })); + Assert.That(actualNames, Is.EquivalentTo(new[] { @"C:\", @"Z:\", @"d:\" })); } [Test] @@ -67,7 +67,7 @@ public void MockDriveInfoFactory_GetDrives_ShouldReturnOnlyLocalDrives() var actualNames = actualResults.Select(d => d.Name); // Assert - Assert.That(actualNames, Is.EquivalentTo(new[] { @"C:\", @"Z:\", @"D:\" })); + Assert.That(actualNames, Is.EquivalentTo(new[] { @"C:\", @"Z:\", @"d:\" })); } [Test] diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockDriveInfoTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockDriveInfoTests.cs index 8b33fd16f..88524b2f4 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockDriveInfoTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockDriveInfoTests.cs @@ -21,7 +21,7 @@ public void MockDriveInfo_Constructor_ShouldInitializeLocalWindowsDrives(string var driveInfo = new MockDriveInfo(fileSystem, path); // Assert - Assert.AreEqual(@"C:\", driveInfo.Name); + Assert.AreEqual(@"c:\", driveInfo.Name); } [Test] @@ -35,7 +35,7 @@ public void MockDriveInfo_Constructor_ShouldInitializeLocalWindowsDrives_Special var driveInfo = new MockDriveInfo(fileSystem, "c"); // Assert - Assert.AreEqual(@"C:\", driveInfo.Name); + Assert.AreEqual(@"c:\", driveInfo.Name); } [TestCase(@"\\unc\share")] @@ -59,7 +59,7 @@ public void MockDriveInfo_RootDirectory_ShouldReturnTheDirectoryBase() var fileSystem = new MockFileSystem(); fileSystem.AddDirectory(XFS.Path(@"c:\Test")); var driveInfo = new MockDriveInfo(fileSystem, "c:"); - var expectedDirectory = XFS.Path(@"C:\"); + var expectedDirectory = XFS.Path(@"c:\"); // Act var actualDirectory = driveInfo.RootDirectory; @@ -67,5 +67,22 @@ public void MockDriveInfo_RootDirectory_ShouldReturnTheDirectoryBase() // Assert Assert.AreEqual(expectedDirectory, actualDirectory.FullName); } + + [TestCase("c:","c:\\")] + [TestCase("C:","C:\\")] + [TestCase("d:","d:\\")] + [TestCase("e:","e:\\")] + [TestCase("f:","f:\\")] + public void MockDriveInfo_ToString_ShouldReturnTheDrivePath(string path, string expectedPath) + { + // Arrange + var directoryPath = XFS.Path(path); + + // Act + var mockDriveInfo = new MockDriveInfo(new MockFileSystem(), directoryPath); + + // Assert + Assert.AreEqual(expectedPath, mockDriveInfo.ToString()); + } } } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileAppendAllTextTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileAppendAllTextTests.cs index 8cc5c85f2..50c1defbf 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileAppendAllTextTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileAppendAllTextTests.cs @@ -143,5 +143,16 @@ public void MockFile_AppendAllText_ShouldPersistNewTextWithCustomEncoding() expected, file.ReadAllBytes(path)); } + + [Test] + public void MockFile_AppendAllText_ShouldWorkWithRelativePath() + { + var file = "file.txt"; + var fileSystem = new MockFileSystem(); + + fileSystem.File.AppendAllText(file, "Foo"); + + Assert.That(fileSystem.File.Exists(file)); + } } } \ No newline at end of file diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileCopyTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileCopyTests.cs index c41b353d5..f351c5757 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileCopyTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileCopyTests.cs @@ -48,6 +48,28 @@ public void MockFile_Copy_ShouldCloneContents() Assert.AreEqual("Original", mockFileSystem.File.ReadAllText(destFileName)); } + [Test] + public void MockFile_Copy_ShouldCloneBinaryContents() + { + var sourceFileName = XFS.Path(@"c:\source\demo.bin"); + var destFileName = XFS.Path(@"c:\source\demo_copy.bin"); + + byte[] original = new byte[] { 0xC0 }; + var mockFileSystem = new MockFileSystem(); + mockFileSystem.AddFile(sourceFileName, new MockFileData(original)); + mockFileSystem.File.Copy(sourceFileName, destFileName); + + using (var stream = mockFileSystem.File.Open(sourceFileName, FileMode.Open, FileAccess.ReadWrite)) + { + var binaryWriter = new System.IO.BinaryWriter(stream); + + binaryWriter.Seek(0, SeekOrigin.Begin); + binaryWriter.Write("Modified"); + } + + CollectionAssert.AreEqual(original, mockFileSystem.File.ReadAllBytes(destFileName)); + } + [Test] public void MockFile_Copy_ShouldCreateFileAtNewDestination() { @@ -346,5 +368,18 @@ public void MockFile_Copy_ShouldThrowFileNotFoundExceptionWhenSourceDoesNotExist Assert.Throws(action); } + + [Test] + public void MockFile_Copy_ShouldWorkWithRelativePaths() + { + var sourceFile = "source_file.txt"; + var destinationFile = "destination_file.txt"; + var fileSystem = new MockFileSystem(); + + fileSystem.File.Create(sourceFile).Close(); + fileSystem.File.Copy(sourceFile, destinationFile); + + Assert.That(fileSystem.File.Exists(destinationFile)); + } } } \ No newline at end of file diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileCreateTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileCreateTests.cs index 9be6ff795..71cae6d86 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileCreateTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileCreateTests.cs @@ -155,12 +155,12 @@ public void MockFile_Create_ShouldThrowDirectoryNotFoundExceptionIfCreatingAndPa { // Arrange var fileSystem = new MockFileSystem(); + var file = XFS.Path("C:\\path\\NotFound.ext"); // Act - TestDelegate action = () => fileSystem.File.Create("C:\\Path\\NotFound.ext"); + TestDelegate action = () => fileSystem.File.Create(file); // Assert - Assert.IsFalse(fileSystem.Directory.Exists("C:\\path")); var exception = Assert.Throws(action); Assert.That(exception.Message, Does.StartWith("Could not find a part of the path")); } @@ -279,5 +279,16 @@ public void MockFile_Create_EncryptedOption_EncryptsFileWhenStreamIsClose() Assert.IsTrue(fileInfo.Attributes.HasFlag(FileAttributes.Encrypted)); } #endif + + [Test] + public void MockFile_Create_ShouldWorkWithRelativePath() + { + var relativeFile = "file.txt"; + var fileSystem = new MockFileSystem(); + + fileSystem.File.Create(relativeFile).Close(); + + Assert.That(fileSystem.File.Exists(relativeFile)); + } } } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileDeleteTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileDeleteTests.cs index c5a1e9c01..6f9fd93fc 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileDeleteTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileDeleteTests.cs @@ -1,6 +1,7 @@ namespace System.IO.Abstractions.TestingHelpers.Tests { - using NUnit.Framework; + using System.Collections.Generic; + using NUnit.Framework; using XFS = MockUnixSupport; @@ -10,7 +11,7 @@ public class MockFileDeleteTests public void MockFile_Delete_ShouldDeleteFile() { var fileSystem = new MockFileSystem(); - var path = XFS.Path("C:\\test"); + var path = XFS.Path("C:\\some_folder\\test"); var directory = fileSystem.Path.GetDirectoryName(path); fileSystem.AddFile(path, new MockFileData("Bla")); @@ -35,5 +36,28 @@ public void MockFile_Delete_ShouldThrowArgumentExceptionIfPathContainsOnlyWhites // Assert Assert.Throws(action); } + + [Test] + public void MockFile_Delete_ShouldThrowDirectoryNotFoundExceptionIfParentFolderAbsent() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path("C:\\test\\somefile.txt"); + + Assert.Throws(() => fileSystem.File.Delete(path)); + } + + [Test] + public void MockFile_Delete_ShouldSilentlyReturnIfNonExistingFileInExistingFolder() + { + var fileSystem = new MockFileSystem(new Dictionary() + { + { XFS.Path("C:\\temp\\exist.txt"), new MockFileData("foobar") }, + }); + + string filePath = XFS.Path("C:\\temp\\somefile.txt"); + + // Delete() returns void, so there is nothing to check here beside absense of an exception + Assert.DoesNotThrow(() => fileSystem.File.Delete(filePath)); + } } } \ No newline at end of file diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileExistsTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileExistsTests.cs index 868c249ba..3a4b37f1f 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileExistsTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileExistsTests.cs @@ -6,59 +6,70 @@ namespace System.IO.Abstractions.TestingHelpers.Tests using XFS = MockUnixSupport; - public class MockFileExistsTests { + public class MockFileExistsTests + { [Test] public void MockFile_Exists_ShouldReturnTrueForSamePath() { // Arrange var fileSystem = new MockFileSystem(new Dictionary { - { XFS.Path(@"c:\something\demo.txt"), new MockFileData("Demo text content") }, - { XFS.Path(@"c:\something\other.gif"), new MockFileData(new byte[] { 0x21, 0x58, 0x3f, 0xa9 }) } + { XFS.Path(@"C:\something\other.gif"), new MockFileData("gif content") } }); - var file = new MockFile(fileSystem); - // Act - var result = file.Exists(XFS.Path(@"c:\something\other.gif")); + var result = fileSystem.File.Exists(XFS.Path(@"C:\something\other.gif")); // Assert Assert.IsTrue(result); } [Test] + [WindowsOnly(WindowsSpecifics.CaseInsensitivity)] public void MockFile_Exists_ShouldReturnTrueForPathVaryingByCase() { // Arrange var fileSystem = new MockFileSystem(new Dictionary { - { XFS.Path(@"c:\something\demo.txt"), new MockFileData("Demo text content") }, - { XFS.Path(@"c:\something\other.gif"), new MockFileData(new byte[] { 0x21, 0x58, 0x3f, 0xa9 }) } + { @"C:\something\demo.txt", new MockFileData("Demo text content") } }); - var file = new MockFile(fileSystem); - // Act - var result = file.Exists(XFS.Path(@"c:\SomeThing\Other.gif")); + var result = fileSystem.File.Exists(@"C:\SomeThing\DEMO.txt"); // Assert Assert.IsTrue(result); } [Test] - public void MockFile_Exists_ShouldReturnFalseForEntirelyDifferentPath() + [UnixOnly(UnixSpecifics.CaseSensitivity)] + public void MockFile_Exists_ShouldReturnFalseForPathVaryingByCase() { // Arrange var fileSystem = new MockFileSystem(new Dictionary { - { XFS.Path(@"c:\something\demo.txt"), new MockFileData("Demo text content") }, - { XFS.Path(@"c:\something\other.gif"), new MockFileData(new byte[] { 0x21, 0x58, 0x3f, 0xa9 }) } + { "/something/demo.txt", new MockFileData("Demo text content") } }); - var file = new MockFile(fileSystem); + // Act + var result = fileSystem.File.Exists("/SomeThing/DEMO.txt"); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MockFile_Exists_ShouldReturnFalseForEntirelyDifferentPath() + { + // Arrange + var fileSystem = new MockFileSystem(new Dictionary + { + { XFS.Path(@"C:\something\demo.txt"), new MockFileData("Demo text content") }, + { XFS.Path(@"C:\something\other.gif"), new MockFileData("gif content") } + }); // Act - var result = file.Exists(XFS.Path(@"c:\SomeThing\DoesNotExist.gif")); + var result = fileSystem.File.Exists(XFS.Path(@"C:\SomeThing\DoesNotExist.gif")); // Assert Assert.IsFalse(result); @@ -67,9 +78,14 @@ public void MockFile_Exists_ShouldReturnFalseForEntirelyDifferentPath() [Test] public void MockFile_Exists_ShouldReturnFalseForNullPath() { - var file = new MockFile(new MockFileSystem()); + // Arrange + var fileSystem = new MockFileSystem(); - Assert.That(file.Exists(null), Is.False); + // Act + var result = fileSystem.File.Exists(null); + + // Assert + Assert.IsFalse(result); } [Test] @@ -78,14 +94,12 @@ public void MockFile_Exists_ShouldReturnFalseForDirectories() // Arrange var fileSystem = new MockFileSystem(new Dictionary { - { XFS.Path(@"c:\something\demo.txt"), new MockFileData("Demo text content") }, - { XFS.Path(@"c:\something\other.gif"), new MockFileData(new byte[] { 0x21, 0x58, 0x3f, 0xa9 }) } + { XFS.Path(@"C:\something\demo.txt"), new MockFileData("Demo text content") }, + { XFS.Path(@"C:\something\other.gif"), new MockFileData("gif content") } }); - var file = new MockFile(fileSystem); - // Act - var result = file.Exists(XFS.Path(@"c:\SomeThing\")); + var result = fileSystem.File.Exists(XFS.Path(@"C:\something\")); // Assert Assert.IsFalse(result); diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs index e75cb3cec..c3b2a03b6 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs @@ -167,7 +167,7 @@ public void MockFileInfo_IsReadOnly_ShouldSetReadOnlyAttributeOfFileInMemoryFile [Test] public void MockFileInfo_IsReadOnly_ShouldSetNotReadOnlyAttributeOfFileInMemoryFileSystem() { - var fileData = new MockFileData("Demo text content") {Attributes = FileAttributes.ReadOnly}; + var fileData = new MockFileData("Demo text content") { Attributes = FileAttributes.ReadOnly }; var fileSystem = new MockFileSystem(new Dictionary { { XFS.Path(@"c:\a.txt"), fileData } @@ -210,7 +210,7 @@ public void MockFileInfo_OpenWrite_ShouldAddDataToFileInMemoryFileSystem() { XFS.Path(@"c:\a.txt"), fileData } }); var fileInfo = new MockFileInfo(fileSystem, XFS.Path(@"c:\a.txt")); - var bytesToAdd = new byte[] {65, 66, 67, 68, 69}; + var bytesToAdd = new byte[] { 65, 66, 67, 68, 69 }; using (var file = fileInfo.OpenWrite()) @@ -491,6 +491,22 @@ public void MockFileInfo_CopyTo_ThrowsExceptionIfSourceDoesntExist() Assert.Throws(action); } + [TestCase(@"..\..\..\c.txt")] + [TestCase(@"c:\a\b\c.txt")] + [TestCase(@"c:\a\c.txt")] + [TestCase(@"c:\c.txt")] + public void MockFileInfo_ToString_ShouldReturnOriginalFilePath(string path) + { + //Arrange + var filePath = XFS.Path(path); + + //Act + var mockFileInfo = new MockFileInfo(new MockFileSystem(), filePath); + + //Assert + Assert.AreEqual(filePath, mockFileInfo.ToString()); + } + #if NET40 [Test] public void MockFileInfo_Replace_ShouldReplaceFileContents() diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileOpenTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileOpenTests.cs index 5e7a065a1..2042f1b34 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileOpenTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileOpenTests.cs @@ -249,14 +249,25 @@ public void MockFile_Open_ShouldThrowDirectoryNotFoundExceptionIfFileModeCreateA { // Arrange var fileSystem = new MockFileSystem(); + var file = XFS.Path("C:\\path\\NotFound.ext"); // Act - TestDelegate action = () => fileSystem.File.Open("C:\\Path\\NotFound.ext", FileMode.Create); + TestDelegate action = () => fileSystem.File.Open(file, FileMode.Create); // Assert - Assert.IsFalse(fileSystem.Directory.Exists("C:\\path")); var exception = Assert.Throws(action); Assert.That(exception.Message, Does.StartWith("Could not find a part of the path")); } + + [Test] + public void MockFile_OpenWrite_ShouldWorkWithRelativePath() + { + var file = "file.txt"; + var fileSystem = new MockFileSystem(); + + fileSystem.File.OpenWrite(file).Close(); + + Assert.That(fileSystem.File.Exists(file)); + } } } \ No newline at end of file diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs index b2a14abfb..89fe72551 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs @@ -28,8 +28,10 @@ public void MockFileStream_Flush_WritesByteToFile() [Test] public void MockFileStream_Dispose_ShouldNotResurrectFile() { + // path in this test case is a subject to Directory.GetParent(path) Linux issue + // https://github.com/System-IO-Abstractions/System.IO.Abstractions/issues/395 var fileSystem = new MockFileSystem(); - var path = XFS.Path("C:\\test"); + var path = XFS.Path("C:\\some_folder\\test"); var directory = fileSystem.Path.GetDirectoryName(path); fileSystem.AddFile(path, new MockFileData("Bla")); var stream = fileSystem.File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Delete); @@ -104,5 +106,20 @@ public void MockFileStream_Dispose_MultipleCallsDontThrow() // Assert Assert.DoesNotThrow(() => stream.Dispose()); } + + [Test] + public void MockFileStream_Dispose_OperationsAfterDisposeThrow() + { + var fileSystem = new MockFileSystem(); + var path = XFS.Path("C:\\test"); + fileSystem.AddFile(path, new MockFileData(new byte[0])); + var stream = fileSystem.FileInfo.FromFileName(path).OpenWrite(); + + // Act + stream.Dispose(); + + // Assert + Assert.Throws(() => stream.WriteByte(0)); + } } } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs index 6ddcd4fba..108ef2fe7 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Reflection; using System.Text; @@ -16,7 +17,7 @@ public void MockFileSystem_GetFile_ShouldReturnNullWhenFileIsNotRegistered() var fileSystem = new MockFileSystem(new Dictionary { { @"c:\something\demo.txt", new MockFileData("Demo\r\ntext\ncontent\rvalue") }, - { @"c:\something\other.gif", new MockFileData(new byte[] { 0x21, 0x58, 0x3f, 0xa9 }) } + { @"c:\something\other.gif", new MockFileData("gif content") } }); var result = fileSystem.GetFile(@"c:\something\else.txt"); @@ -31,7 +32,7 @@ public void MockFileSystem_GetFile_ShouldReturnFileRegisteredInConstructor() var fileSystem = new MockFileSystem(new Dictionary { { @"c:\something\demo.txt", file1 }, - { @"c:\something\other.gif", new MockFileData(new byte[] { 0x21, 0x58, 0x3f, 0xa9 }) } + { @"c:\something\other.gif", new MockFileData("gif content") } }); var result = fileSystem.GetFile(@"c:\something\demo.txt"); @@ -40,13 +41,14 @@ public void MockFileSystem_GetFile_ShouldReturnFileRegisteredInConstructor() } [Test] + [WindowsOnly(WindowsSpecifics.CaseInsensitivity)] public void MockFileSystem_GetFile_ShouldReturnFileRegisteredInConstructorWhenPathsDifferByCase() { var file1 = new MockFileData("Demo\r\ntext\ncontent\rvalue"); var fileSystem = new MockFileSystem(new Dictionary { { @"c:\something\demo.txt", file1 }, - { @"c:\something\other.gif", new MockFileData(new byte[] { 0x21, 0x58, 0x3f, 0xa9 }) } + { @"c:\something\other.gif", new MockFileData("gif content") } }); var result = fileSystem.GetFile(@"c:\SomeThing\DEMO.txt"); @@ -54,15 +56,47 @@ public void MockFileSystem_GetFile_ShouldReturnFileRegisteredInConstructorWhenPa Assert.AreEqual(file1, result); } + [Test] + [UnixOnly(UnixSpecifics.CaseSensitivity)] + public void MockFileSystem_GetFile_ShouldNotReturnFileRegisteredInConstructorWhenPathsDifferByCase() + { + var fileSystem = new MockFileSystem(new Dictionary + { + { "/something/demo.txt", new MockFileData("Demo\r\ntext\ncontent\rvalue") }, + { "/something/other.gif", new MockFileData("gif content") } + }); + + var result = fileSystem.GetFile("/SomeThing/DEMO.txt"); + + Assert.IsNull(result); + } + + [Test] + public void MockFileSystem_AddFile_ShouldHandleUnnormalizedSlashes() + { + var path = XFS.Path(@"c:\d1\d2\file.txt"); + var alternatePath = XFS.Path("c:/d1/d2/file.txt"); + var alternateParentPath = XFS.Path("c://d1//d2/"); + var fs = new MockFileSystem(); + fs.AddFile(path, new MockFileData("Hello")); + + var fileCount = fs.Directory.GetFiles(alternateParentPath).Length; + var fileExists = fs.File.Exists(alternatePath); + + Assert.AreEqual(1, fileCount); + Assert.IsTrue(fileExists); + } + [Test] public void MockFileSystem_AddFile_ShouldHandleNullFileDataAsEmpty() { + var path = XFS.Path(@"c:\something\nullish.txt"); var fileSystem = new MockFileSystem(new Dictionary { - { @"c:\something\nullish.txt", null } + { path, null } }); - var result = fileSystem.File.ReadAllText(@"c:\SomeThing\nullish.txt"); + var result = fileSystem.File.ReadAllText(path); Assert.IsEmpty(result, "Null MockFileData should be allowed for and result in an empty file."); } @@ -70,7 +104,7 @@ public void MockFileSystem_AddFile_ShouldHandleNullFileDataAsEmpty() [Test] public void MockFileSystem_AddFile_ShouldRepaceExistingFile() { - const string path = @"c:\some\file.txt"; + var path = XFS.Path(@"c:\some\file.txt"); const string existingContent = "Existing content"; var fileSystem = new MockFileSystem(new Dictionary { @@ -156,6 +190,7 @@ public void MockFileSystem_AddFile_ShouldMatchCapitalization_PerfectMatch() } [Test] + [WindowsOnly(WindowsSpecifics.CaseInsensitivity)] public void MockFileSystem_AddFile_ShouldMatchCapitalization_PartialMatch() { var fileSystem = new MockFileSystem(); @@ -174,6 +209,7 @@ public void MockFileSystem_AddFile_ShouldMatchCapitalization_PartialMatch() } [Test] + [WindowsOnly(WindowsSpecifics.CaseInsensitivity)] public void MockFileSystem_AddFile_ShouldMatchCapitalization_PartialMatch_FurtherLeft() { var fileSystem = new MockFileSystem(); @@ -292,5 +328,51 @@ public void MockFileSystem_GetFiles_ThrowsArgumentExceptionForInvalidCharacters( // Assert Assert.Throws(getFilesWithInvalidCharacterInPath); } + + [Test] + [TestCase(null)] + [TestCase(@"C:\somepath")] + public void MockFileSystem_DefaultState_CurrentDirectoryExists(string currentDirectory) + { + var fs = new MockFileSystem(null, currentDirectory); + + var actualCurrentDirectory = fs.DirectoryInfo.FromDirectoryName("."); + + Assert.IsTrue(actualCurrentDirectory.Exists); + } + + [Test] + public void MockFileSystem_FileSystemWatcher_ShouldBeAssignable() + { + var path = XFS.Path(@"C:\root"); + var fileSystem = new MockFileSystem {FileSystemWatcher = new TestFileSystemWatcherFactory()}; + var watcher = fileSystem.FileSystemWatcher.FromPath(path); + Assert.AreEqual(path, watcher.Path); + } + + private class TestFileSystemWatcherFactory : IFileSystemWatcherFactory + { + public FileSystemWatcherBase CreateNew() => new TestFileSystemWatcher(null); + public FileSystemWatcherBase FromPath(string path) => new TestFileSystemWatcher(path); + } + + private class TestFileSystemWatcher : FileSystemWatcherBase + { + public TestFileSystemWatcher(string path) => Path = path; + public override string Path { get; set; } + public override bool IncludeSubdirectories { get; set; } + public override bool EnableRaisingEvents { get; set; } + public override string Filter { get; set; } + public override int InternalBufferSize { get; set; } + public override NotifyFilters NotifyFilter { get; set; } +#if NET40 + public override ISite Site { get; set; } + public override ISynchronizeInvoke SynchronizingObject { get; set; } + public override void BeginInit() {} + public override void EndInit() {} +#endif + public override WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType) => default(WaitForChangedResult); + public override WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout) => default(WaitForChangedResult); + } } } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemWatcherFactoryTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemWatcherFactoryTests.cs index 53cc83ec5..49696508a 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemWatcherFactoryTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemWatcherFactoryTests.cs @@ -1,8 +1,5 @@ using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using XFS = System.IO.Abstractions.TestingHelpers.MockUnixSupport; namespace System.IO.Abstractions.TestingHelpers.Tests { @@ -10,43 +7,18 @@ namespace System.IO.Abstractions.TestingHelpers.Tests public class MockFileSystemWatcherFactoryTests { [Test] - public void MockFileSystemWatcherFactory_CreateNew_ShouldReturnNonNullMockWatcher() + public void MockFileSystemWatcherFactory_CreateNew_ShouldThrowNotImplementedException() { - // Arrange var factory = new MockFileSystemWatcherFactory(); - - // Act - var result = factory.CreateNew(); - - // Assert - Assert.IsNotNull(result); + Assert.Throws(() => factory.CreateNew()); } [Test] - public void MockFileSystemWatcherFactory_FromPath_ShouldReturnNonNullMockWatcher() + public void MockFileSystemWatcherFactory_FromPath_ShouldThrowNotImplementedException() { - // Arrange + var path = XFS.Path(@"y:\test"); var factory = new MockFileSystemWatcherFactory(); - - // Act - var result = factory.FromPath(@"y:\test"); - - // Assert - Assert.IsNotNull(result); - } - - [Test] - public void MockFileSystemWatcherFactory_FromPath_ShouldReturnWatcherForSpecifiedPath() - { - // Arrange - const string path = @"z:\test"; - var factory = new MockFileSystemWatcherFactory(); - - // Act - var result = factory.FromPath(path); - - // Assert - Assert.AreEqual(path, result.Path); + Assert.Throws(() => factory.FromPath(path)); } } } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockFileTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockFileTests.cs index 2468c9824..d26a382bd 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockFileTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockFileTests.cs @@ -482,9 +482,14 @@ public void MockFile_Delete_Should_RemoveFiles() [Test] public void MockFile_Delete_No_File_Does_Nothing() - { - string filePath = XFS.Path(@"c:\something\demo.txt"); - var fileSystem = new MockFileSystem(); + { + var fileSystem = new MockFileSystem(new Dictionary() + { + { XFS.Path(@"c:\something\exist.txt"), new MockFileData("Demo text content") }, + }); + + string filePath = XFS.Path(@"c:\something\not_exist.txt"); + fileSystem.File.Delete(filePath); } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/MockUnixSupportTests.cs b/System.IO.Abstractions.TestingHelpers.Tests/MockUnixSupportTests.cs index 30dd6c0de..2641e431b 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/MockUnixSupportTests.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/MockUnixSupportTests.cs @@ -2,19 +2,23 @@ namespace System.IO.Abstractions.TestingHelpers.Tests { + using XFS = MockUnixSupport; + [TestFixture] public class MockUnixSupportTests { [Test] + [UnixOnly(UnixSpecifics.SlashRoot)] public void Should_Convert_Backslashes_To_Slashes_On_Unix() { - Assert.AreEqual("/test/", MockUnixSupport.Path(@"\test\", () => true)); + Assert.AreEqual("/test/", XFS.Path(@"\test\")); } [Test] + [UnixOnly(UnixSpecifics.SlashRoot)] public void Should_Remove_Drive_Letter_On_Unix() { - Assert.AreEqual("/test/", MockUnixSupport.Path(@"c:\test\", () => true)); + Assert.AreEqual("/test/", XFS.Path(@"c:\test\")); } } } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/System.IO.Abstractions.TestingHelpers.Tests.csproj b/System.IO.Abstractions.TestingHelpers.Tests/System.IO.Abstractions.TestingHelpers.Tests.csproj index ab213d6ef..2b48a0d01 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/System.IO.Abstractions.TestingHelpers.Tests.csproj +++ b/System.IO.Abstractions.TestingHelpers.Tests/System.IO.Abstractions.TestingHelpers.Tests.csproj @@ -1,6 +1,7 @@  - net40;netcoreapp2.0 + netcoreapp2.0 + $(TargetFrameworks);net40 0.0.0.1 The unit tests for our pre-built mocks @@ -10,6 +11,7 @@ System.IO.Abstractions.TestingHelpers.Tests ../StrongName.snk false + true Full @@ -39,13 +41,13 @@ - - + + - - + + diff --git a/System.IO.Abstractions.TestingHelpers.Tests/UnixSpecifics.cs b/System.IO.Abstractions.TestingHelpers.Tests/UnixSpecifics.cs index 6e81e34c8..bf7cd4f52 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/UnixSpecifics.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/UnixSpecifics.cs @@ -2,6 +2,8 @@ { internal static class UnixSpecifics { - public const string SlashRoot = "Filesystem root is just '/' in Unix"; + public const string SlashRoot = "Filesystem root is just '/' on Unix"; + + public const string CaseSensitivity = "Paths are case-sensitive on Unix"; } } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/WindowsOnlyAttribute.cs b/System.IO.Abstractions.TestingHelpers.Tests/WindowsOnlyAttribute.cs index d14431f3f..49be416ed 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/WindowsOnlyAttribute.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/WindowsOnlyAttribute.cs @@ -16,7 +16,7 @@ public WindowsOnlyAttribute(string reason) public void BeforeTest(ITest test) { - if (MockUnixSupport.IsUnixPlatform()) + if (!MockUnixSupport.IsWindowsPlatform()) { Assert.Inconclusive(reason); } diff --git a/System.IO.Abstractions.TestingHelpers.Tests/WindowsSpecifics.cs b/System.IO.Abstractions.TestingHelpers.Tests/WindowsSpecifics.cs index a44dea526..86bde580e 100644 --- a/System.IO.Abstractions.TestingHelpers.Tests/WindowsSpecifics.cs +++ b/System.IO.Abstractions.TestingHelpers.Tests/WindowsSpecifics.cs @@ -9,5 +9,7 @@ internal static class WindowsSpecifics public const string UNCPaths = "UNC paths are a Windows-only concept"; public const string StrictPathRules = "Windows has stricter path rules than other platforms"; + + public const string CaseInsensitivity = "Paths are case-insensitive on Windows"; } } diff --git a/System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs b/System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs index fefb8ede4..6c2c6350a 100644 --- a/System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs +++ b/System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs @@ -54,6 +54,8 @@ public interface IMockFileDataAccessor /// IEnumerable AllDirectories { get; } + StringOperations StringOperations { get; } + FileBase File { get; } DirectoryBase Directory { get; } IFileInfoFactory FileInfo {get; } diff --git a/System.IO.Abstractions.TestingHelpers/MockDirectory.cs b/System.IO.Abstractions.TestingHelpers/MockDirectory.cs index 250b9d541..c38d0ce4a 100644 --- a/System.IO.Abstractions.TestingHelpers/MockDirectory.cs +++ b/System.IO.Abstractions.TestingHelpers/MockDirectory.cs @@ -11,17 +11,19 @@ namespace System.IO.Abstractions.TestingHelpers [Serializable] public class MockDirectory : DirectoryBase { - private readonly FileBase fileBase; - private readonly IMockFileDataAccessor mockFileDataAccessor; - private string currentDirectory; - public MockDirectory(IMockFileDataAccessor mockFileDataAccessor, FileBase fileBase, string currentDirectory) : base(mockFileDataAccessor?.FileSystem) + // This constructor is retained to avoid breaking change + public MockDirectory(IMockFileDataAccessor mockFileDataAccessor, FileBase fileBase, string currentDirectory) : + this(mockFileDataAccessor, currentDirectory) + { + } + + public MockDirectory(IMockFileDataAccessor mockFileDataAccessor, string currentDirectory) : base(mockFileDataAccessor?.FileSystem) { this.currentDirectory = currentDirectory; this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); - this.fileBase = fileBase; } public override DirectoryInfoBase CreateDirectory(string path) @@ -35,6 +37,7 @@ public override DirectoryInfoBase CreateDirectory(string path, DirectorySecurity return CreateDirectoryInternal(path, directorySecurity); } #endif + private DirectoryInfoBase CreateDirectoryInternal(string path, DirectorySecurity directorySecurity) { if (path == null) @@ -74,7 +77,7 @@ public override void Delete(string path, bool recursive) path = mockFileDataAccessor.Path.GetFullPath(path).TrimSlashes(); var affectedPaths = mockFileDataAccessor .AllPaths - .Where(p => p.StartsWith(path, StringComparison.OrdinalIgnoreCase)) + .Where(p => mockFileDataAccessor.StringOperations.StartsWith(p, path)) .ToList(); if (!affectedPaths.Any()) @@ -99,7 +102,7 @@ public override bool Exists(string path) { path = path.TrimSlashes(); path = mockFileDataAccessor.Path.GetFullPath(path); - return mockFileDataAccessor.AllDirectories.Any(p => p.Equals(path, StringComparison.OrdinalIgnoreCase)); + return mockFileDataAccessor.AllDirectories.Any(p => mockFileDataAccessor.StringOperations.Equals(p, path)); } catch (Exception) { @@ -128,12 +131,12 @@ public override DirectorySecurity GetAccessControl(string path, AccessControlSec public override DateTime GetCreationTime(string path) { - return fileBase.GetCreationTime(path); + return mockFileDataAccessor.File.GetCreationTime(path); } public override DateTime GetCreationTimeUtc(string path) { - return fileBase.GetCreationTimeUtc(path); + return mockFileDataAccessor.File.GetCreationTimeUtc(path); } public override string GetCurrentDirectory() @@ -174,45 +177,55 @@ public override string[] GetFiles(string path, string searchPattern) } public override string[] GetFiles(string path, string searchPattern, SearchOption searchOption) + { + return GetFilesInternal(mockFileDataAccessor.AllFiles, path, searchPattern, searchOption); + } + + private string[] GetFilesInternal( + IEnumerable files, + string path, + string searchPattern, + SearchOption searchOption) { if (path == null) { throw new ArgumentNullException(nameof(path)); } - + if (path.Any(c => Path.GetInvalidPathChars().Contains(c))) { throw new ArgumentException("Invalid character(s) in path", nameof(path)); } + CheckSearchPattern(searchPattern); + path = path.TrimSlashes(); + path = path.NormalizeSlashes(); + if (!Exists(path)) { - throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, StringResources.Manager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION"), path)); + throw new DirectoryNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + StringResources.Manager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION"), + path)); } - return GetFilesInternal(mockFileDataAccessor.AllFiles, path, searchPattern, searchOption); - } - - private string[] GetFilesInternal(IEnumerable files, string path, string searchPattern, SearchOption searchOption) - { - CheckSearchPattern(searchPattern); - path = path.TrimSlashes(); path = EnsureAbsolutePath(path); - if (!path.EndsWith(Path.DirectorySeparatorChar.ToString()) - && !path.EndsWith(Path.AltDirectorySeparatorChar.ToString())) + if (!path.EndsWith(Path.DirectorySeparatorChar.ToString())) { path += Path.DirectorySeparatorChar; } - bool isUnix = XFS.IsUnixPlatform(); + var isUnix = XFS.IsUnixPlatform(); - string allDirectoriesPattern = isUnix + var allDirectoriesPattern = isUnix ? @"([^<>:""/|?*]*/)*" : @"([^<>:""/\\|?*]*\\)*"; string fileNamePattern; string pathPatternSpecial = null; + if (searchPattern == "*") { fileNamePattern = isUnix ? @"[^/]*?/?" : @"[^\\]*?\\?"; @@ -244,22 +257,9 @@ private string[] GetFilesInternal(IEnumerable files, string path, string searchOption == SearchOption.AllDirectories ? allDirectoriesPattern : string.Empty, fileNamePattern); - return files - .Where(p => - { - if (Regex.IsMatch(p, pathPattern)) - { - return true; - } - - if (pathPatternSpecial != null && Regex.IsMatch(p, pathPatternSpecial)) - { - return true; - } - - return false; - }) + .Where(p => Regex.IsMatch(p, pathPattern) + || (pathPatternSpecial != null && Regex.IsMatch(p, pathPatternSpecial))) .ToArray(); } @@ -278,22 +278,22 @@ public override string[] GetFileSystemEntries(string path, string searchPattern) public override DateTime GetLastAccessTime(string path) { - return fileBase.GetLastAccessTime(path); + return mockFileDataAccessor.File.GetLastAccessTime(path); } public override DateTime GetLastAccessTimeUtc(string path) { - return fileBase.GetLastAccessTimeUtc(path); + return mockFileDataAccessor.File.GetLastAccessTimeUtc(path); } public override DateTime GetLastWriteTime(string path) { - return fileBase.GetLastWriteTime(path); + return mockFileDataAccessor.File.GetLastWriteTime(path); } public override DateTime GetLastWriteTimeUtc(string path) { - return fileBase.GetLastWriteTimeUtc(path); + return mockFileDataAccessor.File.GetLastWriteTimeUtc(path); } #if NET40 @@ -302,7 +302,7 @@ public override string[] GetLogicalDrives() return mockFileDataAccessor .AllDirectories .Select(d => new MockDirectoryInfo(mockFileDataAccessor, d).Root.FullName) - .Select(r => r.ToLowerInvariant()) + .Select(r => mockFileDataAccessor.StringOperations.ToUpper(r)) .Distinct() .ToArray(); } @@ -320,19 +320,22 @@ public override DirectoryInfoBase GetParent(string path) throw new ArgumentException(StringResources.Manager.GetString("PATH_CANNOT_BE_THE_EMPTY_STRING_OR_ALL_WHITESPACE"), "path"); } - if (MockPath.HasIllegalCharacters(path, false)) + if (mockFileDataAccessor.PathVerifier.HasIllegalCharacters(path, false)) { throw new ArgumentException("Path contains invalid path characters.", "path"); } var absolutePath = mockFileDataAccessor.Path.GetFullPath(path); - var sepAsString = string.Format(CultureInfo.InvariantCulture, "{0}", mockFileDataAccessor.Path.DirectorySeparatorChar); - + var sepAsString = mockFileDataAccessor.Path.DirectorySeparatorChar.ToString(); var lastIndex = 0; + if (absolutePath != sepAsString) { - var startIndex = absolutePath.EndsWith(sepAsString, StringComparison.OrdinalIgnoreCase) ? absolutePath.Length - 1 : absolutePath.Length; + var startIndex = mockFileDataAccessor.StringOperations.EndsWith(absolutePath, sepAsString) + ? absolutePath.Length - 1 + : absolutePath.Length; lastIndex = absolutePath.LastIndexOf(mockFileDataAccessor.Path.DirectorySeparatorChar, startIndex - 1); + if (lastIndex < 0) { return null; @@ -340,13 +343,25 @@ public override DirectoryInfoBase GetParent(string path) } var parentPath = absolutePath.Substring(0, lastIndex); + if (string.IsNullOrEmpty(parentPath)) { + // On the Unix platform, the parent of a path consisting of a slash followed by + // non-slashes is the root, '/'. + if (XFS.IsUnixPlatform()) + { + absolutePath = absolutePath.TrimSlashes(); + + if (absolutePath.Length > 1 && absolutePath.LastIndexOf(mockFileDataAccessor.Path.DirectorySeparatorChar) == 0) + { + return new MockDirectoryInfo(mockFileDataAccessor, mockFileDataAccessor.Path.DirectorySeparatorChar.ToString()); + } + } + return null; } - var parent = new MockDirectoryInfo(mockFileDataAccessor, parentPath); - return parent; + return new MockDirectoryInfo(mockFileDataAccessor, parentPath); } public override void Move(string sourceDirName, string destDirName) @@ -354,14 +369,15 @@ public override void Move(string sourceDirName, string destDirName) var fullSourcePath = mockFileDataAccessor.Path.GetFullPath(sourceDirName).TrimSlashes(); var fullDestPath = mockFileDataAccessor.Path.GetFullPath(destDirName).TrimSlashes(); - if (string.Equals(fullSourcePath, fullDestPath, StringComparison.OrdinalIgnoreCase)) + if (mockFileDataAccessor.StringOperations.Equals(fullSourcePath, fullDestPath)) { throw new IOException("Source and destination path must be different."); } var sourceRoot = mockFileDataAccessor.Path.GetPathRoot(fullSourcePath); var destinationRoot = mockFileDataAccessor.Path.GetPathRoot(fullDestPath); - if (!string.Equals(sourceRoot, destinationRoot, StringComparison.OrdinalIgnoreCase)) + + if (!mockFileDataAccessor.StringOperations.Equals(sourceRoot, destinationRoot)) { throw new IOException("Source and destination path must have identical roots. Move will not work across volumes."); } @@ -395,12 +411,12 @@ public override void SetAccessControl(string path, DirectorySecurity directorySe public override void SetCreationTime(string path, DateTime creationTime) { - fileBase.SetCreationTime(path, creationTime); + mockFileDataAccessor.File.SetCreationTime(path, creationTime); } public override void SetCreationTimeUtc(string path, DateTime creationTimeUtc) { - fileBase.SetCreationTimeUtc(path, creationTimeUtc); + mockFileDataAccessor.File.SetCreationTimeUtc(path, creationTimeUtc); } public override void SetCurrentDirectory(string path) @@ -410,22 +426,22 @@ public override void SetCurrentDirectory(string path) public override void SetLastAccessTime(string path, DateTime lastAccessTime) { - fileBase.SetLastAccessTime(path, lastAccessTime); + mockFileDataAccessor.File.SetLastAccessTime(path, lastAccessTime); } public override void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc) { - fileBase.SetLastAccessTimeUtc(path, lastAccessTimeUtc); + mockFileDataAccessor.File.SetLastAccessTimeUtc(path, lastAccessTimeUtc); } public override void SetLastWriteTime(string path, DateTime lastWriteTime) { - fileBase.SetLastWriteTime(path, lastWriteTime); + mockFileDataAccessor.File.SetLastWriteTime(path, lastWriteTime); } public override void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc) { - fileBase.SetLastWriteTimeUtc(path, lastWriteTimeUtc); + mockFileDataAccessor.File.SetLastWriteTimeUtc(path, lastWriteTimeUtc); } public override IEnumerable EnumerateDirectories(string path) @@ -445,17 +461,10 @@ public override IEnumerable EnumerateDirectories(string path, string sea public override IEnumerable EnumerateDirectories(string path, string searchPattern, SearchOption searchOption) { mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(path, "path"); - path = path.TrimSlashes(); path = mockFileDataAccessor.Path.GetFullPath(path); - - if (!Exists(path)) - { - throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, StringResources.Manager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION"), path)); - } - - var dirs = GetFilesInternal(mockFileDataAccessor.AllDirectories, path, searchPattern, searchOption); - return dirs.Where(p => string.Compare(p, path, StringComparison.OrdinalIgnoreCase) != 0); + return GetFilesInternal(mockFileDataAccessor.AllDirectories, path, searchPattern, searchOption) + .Where(p => !mockFileDataAccessor.StringOperations.Equals(p, path)); } public override IEnumerable EnumerateFiles(string path) @@ -501,7 +510,7 @@ private string EnsureAbsolutePath(string path) : Path.Combine(GetCurrentDirectory(), path); } - static void CheckSearchPattern(string searchPattern) + private void CheckSearchPattern(string searchPattern) { if (searchPattern == null) { @@ -511,15 +520,17 @@ static void CheckSearchPattern(string searchPattern) const string TWO_DOTS = ".."; Func createException = () => new ArgumentException(@"Search pattern cannot contain "".."" to move up directories and can be contained only internally in file/directory names, as in ""a..b"".", searchPattern); - if (searchPattern.EndsWith(TWO_DOTS, StringComparison.OrdinalIgnoreCase)) + if (mockFileDataAccessor.StringOperations.EndsWith(searchPattern, TWO_DOTS)) { throw createException(); } - int position; - if ((position = searchPattern.IndexOf(TWO_DOTS, StringComparison.OrdinalIgnoreCase)) >= 0) + var position = mockFileDataAccessor.StringOperations.IndexOf(searchPattern, TWO_DOTS); + + if (position >= 0) { var characterAfterTwoDots = searchPattern[position + 2]; + if (characterAfterTwoDots == Path.DirectorySeparatorChar || characterAfterTwoDots == Path.AltDirectorySeparatorChar) { throw createException(); diff --git a/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs b/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs index 90f2ce9a1..0a9b651bc 100644 --- a/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs +++ b/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Security.AccessControl; @@ -10,6 +9,7 @@ public class MockDirectoryInfo : DirectoryInfoBase { private readonly IMockFileDataAccessor mockFileDataAccessor; private readonly string directoryPath; + private readonly string originalPath; /// /// Initializes a new instance of the class. @@ -21,6 +21,7 @@ public MockDirectoryInfo(IMockFileDataAccessor mockFileDataAccessor, string dire { this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); + originalPath = directoryPath; directoryPath = mockFileDataAccessor.Path.GetFullPath(directoryPath); this.directoryPath = directoryPath.TrimSlashes(); @@ -73,7 +74,8 @@ public override string FullName get { var root = mockFileDataAccessor.Path.GetPathRoot(directoryPath); - if (string.Equals(directoryPath, root, StringComparison.OrdinalIgnoreCase)) + + if (mockFileDataAccessor.StringOperations.Equals(directoryPath, root)) { // drives have the trailing slash return directoryPath; @@ -283,11 +285,6 @@ public override DirectoryInfoBase Root } } - public override string ToString() - { - return FullName; - } - private MockFileData GetMockFileDataForRead() { return mockFileDataAccessor.GetFile(directoryPath) ?? MockFileData.NullObject; @@ -298,5 +295,10 @@ private MockFileData GetMockFileDataForWrite() return mockFileDataAccessor.GetFile(directoryPath) ?? throw new FileNotFoundException(StringResources.Manager.GetString("COULD_NOT_FIND_FILE_EXCEPTION"), directoryPath); } + + public override string ToString() + { + return originalPath; + } } } diff --git a/System.IO.Abstractions.TestingHelpers/MockDriveInfo.cs b/System.IO.Abstractions.TestingHelpers/MockDriveInfo.cs index 918c147f0..61ec413b4 100644 --- a/System.IO.Abstractions.TestingHelpers/MockDriveInfo.cs +++ b/System.IO.Abstractions.TestingHelpers/MockDriveInfo.cs @@ -7,10 +7,7 @@ public class MockDriveInfo : DriveInfoBase public MockDriveInfo(IMockFileDataAccessor mockFileDataAccessor, string name) : base(mockFileDataAccessor?.FileSystem) { - if (mockFileDataAccessor == null) - { - throw new ArgumentNullException(nameof(mockFileDataAccessor)); - } + this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); if (name == null) { @@ -18,32 +15,25 @@ public MockDriveInfo(IMockFileDataAccessor mockFileDataAccessor, string name) : } const string DRIVE_SEPARATOR = @":\"; - if (name.Length == 1) - { - name = char.ToUpperInvariant(name[0]) + DRIVE_SEPARATOR; - } - else if (name.Length == 2 && name[1] == ':') - { - name = char.ToUpperInvariant(name[0]) + DRIVE_SEPARATOR; - } - else if (name.Length == 3 && name.EndsWith(DRIVE_SEPARATOR, StringComparison.Ordinal)) + + if (name.Length == 1 + || (name.Length == 2 && name[1] == ':') + || (name.Length == 3 && mockFileDataAccessor.StringOperations.EndsWith(name, DRIVE_SEPARATOR))) { - name = char.ToUpperInvariant(name[0]) + DRIVE_SEPARATOR; + name = name[0] + DRIVE_SEPARATOR; } else { - MockPath.CheckInvalidPathChars(name); + mockFileDataAccessor.PathVerifier.CheckInvalidPathChars(name); name = mockFileDataAccessor.Path.GetPathRoot(name); - if (string.IsNullOrEmpty(name) || name.StartsWith(@"\\", StringComparison.Ordinal)) + if (string.IsNullOrEmpty(name) || mockFileDataAccessor.StringOperations.StartsWith(name, @"\\")) { throw new ArgumentException( @"Object must be a root directory (""C:\"") or a drive letter (""C"")."); } } - this.mockFileDataAccessor = mockFileDataAccessor; - Name = name; IsReady = true; } @@ -58,11 +48,15 @@ public override DirectoryInfoBase RootDirectory { get { - var directory = mockFileDataAccessor.DirectoryInfo.FromDirectoryName(Name); - return directory; + return mockFileDataAccessor.DirectoryInfo.FromDirectoryName(Name); } } + public override string ToString() + { + return Name; + } + public new long TotalFreeSpace { get; protected set; } public new long TotalSize { get; protected set; } public override string VolumeLabel { get; set; } diff --git a/System.IO.Abstractions.TestingHelpers/MockDriveInfoFactory.cs b/System.IO.Abstractions.TestingHelpers/MockDriveInfoFactory.cs index 00a8212dc..bd1596a00 100644 --- a/System.IO.Abstractions.TestingHelpers/MockDriveInfoFactory.cs +++ b/System.IO.Abstractions.TestingHelpers/MockDriveInfoFactory.cs @@ -14,7 +14,7 @@ public MockDriveInfoFactory(IMockFileDataAccessor mockFileSystem) public DriveInfoBase[] GetDrives() { - var driveLetters = new HashSet(new DriveEqualityComparer()); + var driveLetters = new HashSet(new DriveEqualityComparer(mockFileSystem)); foreach (var path in mockFileSystem.AllPaths) { var pathRoot = mockFileSystem.Path.GetPathRoot(path); @@ -47,12 +47,12 @@ public DriveInfoBase FromDriveName(string driveName) private string NormalizeDriveName(string driveName) { - if (driveName.Length == 3 && driveName.EndsWith(@":\", StringComparison.OrdinalIgnoreCase)) + if (driveName.Length == 3 && mockFileSystem.StringOperations.EndsWith(driveName, @":\")) { - return char.ToUpperInvariant(driveName[0]) + @":\"; + return mockFileSystem.StringOperations.ToUpper(driveName[0]) + @":\"; } - if (driveName.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase)) + if (mockFileSystem.StringOperations.StartsWith(driveName, @"\\")) { return null; } @@ -62,34 +62,27 @@ private string NormalizeDriveName(string driveName) private class DriveEqualityComparer : IEqualityComparer { - public bool Equals(string x, string y) - { - if (ReferenceEquals(x, y)) - { - return true; - } + private readonly IMockFileDataAccessor mockFileSystem; - if (ReferenceEquals(x, null)) - { - return false; - } - - if (ReferenceEquals(y, null)) - { - return false; - } + public DriveEqualityComparer(IMockFileDataAccessor mockFileSystem) + { + this.mockFileSystem = mockFileSystem ?? throw new ArgumentNullException(nameof(mockFileSystem)); + } - if (x[1] == ':' && y[1] == ':') - { - return char.ToUpperInvariant(x[0]) == char.ToUpperInvariant(y[0]); - } + public bool Equals(string x, string y) + { + return ReferenceEquals(x, y) || + (HasDrivePrefix(x) && HasDrivePrefix(y) && mockFileSystem.StringOperations.Equals(x[0], y[0])); + } - return false; + private static bool HasDrivePrefix(string x) + { + return x != null && x.Length >= 2 && x[1] == ':'; } public int GetHashCode(string obj) { - return obj.ToUpperInvariant().GetHashCode(); + return mockFileSystem.StringOperations.ToUpper(obj).GetHashCode(); } } } diff --git a/System.IO.Abstractions.TestingHelpers/MockFile.cs b/System.IO.Abstractions.TestingHelpers/MockFile.cs index 1322e2e29..a2d24d91f 100644 --- a/System.IO.Abstractions.TestingHelpers/MockFile.cs +++ b/System.IO.Abstractions.TestingHelpers/MockFile.cs @@ -10,12 +10,10 @@ namespace System.IO.Abstractions.TestingHelpers public class MockFile : FileBase { private readonly IMockFileDataAccessor mockFileDataAccessor; - private readonly MockPath mockPath; public MockFile(IMockFileDataAccessor mockFileDataAccessor) : base(mockFileDataAccessor?.FileSystem) { this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); - mockPath = new MockPath(mockFileDataAccessor); } public override void AppendAllLines(string path, IEnumerable contents) @@ -57,12 +55,7 @@ public override void AppendAllText(string path, string contents, Encoding encodi if (!mockFileDataAccessor.FileExists(path)) { - var dir = mockFileDataAccessor.Path.GetDirectoryName(path); - if (!mockFileDataAccessor.Directory.Exists(dir)) - { - throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, StringResources.Manager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION"), path)); - } - + VerifyDirectoryExists(path); mockFileDataAccessor.AddFile(path, new MockFileData(contents, encoding)); } else @@ -112,11 +105,7 @@ public override void Copy(string sourceFileName, string destFileName, bool overw throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, StringResources.Manager.GetString("COULD_NOT_FIND_FILE_EXCEPTION"), sourceFileName)); } - var directoryNameOfDestination = mockPath.GetDirectoryName(destFileName); - if (!mockFileDataAccessor.Directory.Exists(directoryNameOfDestination)) - { - throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, StringResources.Manager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION"), destFileName)); - } + VerifyDirectoryExists(destFileName); var fileExists = mockFileDataAccessor.FileExists(destFileName); if (fileExists) @@ -155,16 +144,7 @@ private Stream CreateInternal(string path, int bufferSize, FileOptions options, } mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(path, nameof(path)); - var directoryPath = mockPath.GetDirectoryName(path); - - if (!mockFileDataAccessor.Directory.Exists(directoryPath)) - { - throw new DirectoryNotFoundException( - string.Format( - CultureInfo.InvariantCulture, - StringResources.Manager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION"), - path)); - } + VerifyDirectoryExists(path); var mockFileData = new MockFileData(new byte[0]) { @@ -191,6 +171,11 @@ public override void Delete(string path) { mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(path, "path"); + // We mimic exact behavior of the standard File.Delete() method + // which throws exception only if the folder does not exist, + // but silently returns if deleting a non-existing file in an existing folder. + VerifyDirectoryExists(path); + mockFileDataAccessor.RemoveFile(path); } @@ -564,15 +549,13 @@ public override void Replace(string sourceFileName, string destinationFileName, throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, StringResources.Manager.GetString("COULD_NOT_FIND_FILE_EXCEPTION"), destinationFileName)); } - var mockFile = new MockFile(mockFileDataAccessor); - if (destinationBackupFileName != null) { - mockFile.Copy(destinationFileName, destinationBackupFileName, true); + Copy(destinationFileName, destinationBackupFileName, overwrite: true); } - mockFile.Delete(destinationFileName); - mockFile.Move(sourceFileName, destinationFileName); + Delete(destinationFileName); + Move(sourceFileName, destinationFileName); } #endif @@ -988,10 +971,16 @@ private void VerifyValueIsNotNull(object value, string parameterName) private void VerifyDirectoryExists(string path) { - DirectoryInfoBase dir = mockFileDataAccessor.Directory.GetParent(path); - if (!dir.Exists) + var pathOps = mockFileDataAccessor.Path; + var dir = pathOps.GetDirectoryName(pathOps.GetFullPath(path)); + + if (!mockFileDataAccessor.Directory.Exists(dir)) { - throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, StringResources.Manager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION"), dir)); + throw new DirectoryNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + StringResources.Manager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION"), + path)); } } } diff --git a/System.IO.Abstractions.TestingHelpers/MockFileData.cs b/System.IO.Abstractions.TestingHelpers/MockFileData.cs index 6d433bc28..15aa84298 100644 --- a/System.IO.Abstractions.TestingHelpers/MockFileData.cs +++ b/System.IO.Abstractions.TestingHelpers/MockFileData.cs @@ -96,7 +96,7 @@ public MockFileData(MockFileData template) accessControl = template.accessControl; Attributes = template.Attributes; - TextContents = template.TextContents; + Contents = template.Contents.ToArray(); CreationTime = template.CreationTime; LastAccessTime = template.LastAccessTime; LastWriteTime = template.LastWriteTime; diff --git a/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs b/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs index 298d054be..257f3317c 100644 --- a/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs +++ b/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs @@ -168,7 +168,7 @@ public override FileInfoBase CopyTo(string destFileName, bool overwrite) { return this; } - new MockFile(mockFileSystem).Copy(FullName, destFileName, overwrite); + mockFileSystem.File.Copy(FullName, destFileName, overwrite); return mockFileSystem.FileInfo.FromFileName(destFileName); } @@ -242,7 +242,7 @@ public override Stream OpenRead() public override StreamReader OpenText() { - return new StreamReader(OpenRead()); + return new StreamReader(OpenRead()); } public override Stream OpenWrite() @@ -296,7 +296,7 @@ public override bool IsReadOnly set { if (MockFileData == null) throw new FileNotFoundException("File not found", path); - if(value) + if (value) MockFileData.Attributes |= FileAttributes.ReadOnly; else MockFileData.Attributes &= ~FileAttributes.ReadOnly; @@ -311,5 +311,10 @@ public override long Length return MockFileData.Contents.Length; } } + + public override string ToString() + { + return path; + } } } diff --git a/System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/System.IO.Abstractions.TestingHelpers/MockFileStream.cs index 68095d85a..7e3d2f291 100644 --- a/System.IO.Abstractions.TestingHelpers/MockFileStream.cs +++ b/System.IO.Abstractions.TestingHelpers/MockFileStream.cs @@ -7,7 +7,12 @@ public class MockFileStream : MemoryStream private readonly string path; private readonly bool canWrite = true; private readonly FileOptions options; + +#if NET40 + private bool closed; +#else private bool disposed; +#endif public enum StreamType { @@ -64,8 +69,14 @@ public MockFileStream( #if NET40 public override void Close() { + if (closed) + { + return; + } InternalFlush(); + base.Close(); OnClose(); + closed = true; } #else protected override void Dispose(bool disposing) diff --git a/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs b/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs index 186dcebbe..cd3d15f64 100644 --- a/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs +++ b/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs @@ -2,13 +2,16 @@ using System.Globalization; using System.Linq; using System.Reflection; -using XFS = System.IO.Abstractions.TestingHelpers.MockUnixSupport; namespace System.IO.Abstractions.TestingHelpers { + using XFS = MockUnixSupport; + [Serializable] public class MockFileSystem : IFileSystem, IMockFileDataAccessor { + private const string DEFAULT_CURRENT_DIRECTORY = @"C:\"; + private readonly IDictionary files; [NonSerialized] private readonly PathVerifier pathVerifier; @@ -19,16 +22,16 @@ public MockFileSystem(IDictionary files, string currentDir { if (string.IsNullOrEmpty(currentDirectory)) { - currentDirectory = IO.Path.GetTempPath(); + currentDirectory = XFS.Path(DEFAULT_CURRENT_DIRECTORY); } + StringOperations = new StringOperations(XFS.IsUnixPlatform()); pathVerifier = new PathVerifier(this); + this.files = new Dictionary(StringOperations.Comparer); - this.files = new Dictionary(StringComparer.OrdinalIgnoreCase); - Path = new MockPath(this); File = new MockFile(this); - Directory = new MockDirectory(this, File, currentDirectory); + Directory = new MockDirectory(this, currentDirectory); FileInfo = new MockFileInfoFactory(this); FileStream = new MockFileStreamFactory(this); DirectoryInfo = new MockDirectoryInfoFactory(this); @@ -42,26 +45,23 @@ public MockFileSystem(IDictionary files, string currentDir AddFile(entry.Key, entry.Value); } } + + if (!FileExists(currentDirectory)) + { + AddDirectory(currentDirectory); + } } + public StringOperations StringOperations { get; } public FileBase File { get; } - public DirectoryBase Directory { get; } - public IFileInfoFactory FileInfo { get; } - public IFileStreamFactory FileStream { get; } - public PathBase Path { get; } - public IDirectoryInfoFactory DirectoryInfo { get; } - public IDriveInfoFactory DriveInfo { get; } - - public IFileSystemWatcherFactory FileSystemWatcher { get; } - + public IFileSystemWatcherFactory FileSystemWatcher { get; set; } public IFileSystem FileSystem => this; - public PathVerifier PathVerifier => pathVerifier; private string FixPath(string path, bool checkCaps = false) @@ -93,7 +93,7 @@ private string GetPathWithCorrectDirectoryCapitalization(string fullPath) if (Directory.Exists(leftHalf)) { leftHalf = Path.GetFullPath(leftHalf).TrimSlashes(); - string baseDirectory = AllDirectories.First(dir => dir.Equals(leftHalf, StringComparison.OrdinalIgnoreCase)); + string baseDirectory = AllDirectories.First(dir => StringOperations.Equals(dir, leftHalf)); return baseDirectory + Path.DirectorySeparatorChar + rightHalf; } } @@ -145,7 +145,7 @@ public void AddFile(string path, MockFileData mockFile) public void AddDirectory(string path) { var fixedPath = FixPath(path, true); - var separator = XFS.Separator(); + var separator = Path.DirectorySeparatorChar.ToString(); lock (files) { @@ -154,15 +154,15 @@ public void AddDirectory(string path) throw new UnauthorizedAccessException(string.Format(CultureInfo.InvariantCulture, StringResources.Manager.GetString("ACCESS_TO_THE_PATH_IS_DENIED"), fixedPath)); var lastIndex = 0; - - bool isUnc = - fixedPath.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase) || - fixedPath.StartsWith(@"//", StringComparison.OrdinalIgnoreCase); + var isUnc = + StringOperations.StartsWith(fixedPath, @"\\") || + StringOperations.StartsWith(fixedPath, @"//"); if (isUnc) { //First, confirm they aren't trying to create '\\server\' - lastIndex = fixedPath.IndexOf(separator, 2, StringComparison.OrdinalIgnoreCase); + lastIndex = StringOperations.IndexOf(fixedPath, separator, 2); + if (lastIndex < 0) throw new ArgumentException(@"The UNC path should be of the form \\server\share.", "path"); @@ -172,7 +172,7 @@ public void AddDirectory(string path) */ } - while ((lastIndex = fixedPath.IndexOf(separator, lastIndex + 1, StringComparison.OrdinalIgnoreCase)) > -1) + while ((lastIndex = StringOperations.IndexOf(fixedPath, separator, lastIndex + 1)) > -1) { var segment = fixedPath.Substring(0, lastIndex + 1); if (!Directory.Exists(segment)) @@ -181,7 +181,7 @@ public void AddDirectory(string path) } } - var s = fixedPath.EndsWith(separator, StringComparison.OrdinalIgnoreCase) ? fixedPath : fixedPath + separator; + var s = StringOperations.EndsWith(fixedPath, separator) ? fixedPath : fixedPath + separator; SetEntry(s, new MockDirectoryData()); } } @@ -227,12 +227,12 @@ public void MoveDirectory(string sourcePath, string destPath) lock (files) { var affectedPaths = files.Keys - .Where(p => p.StartsWith(sourcePath, StringComparison.OrdinalIgnoreCase)) + .Where(p => StringOperations.StartsWith(p, sourcePath)) .ToList(); foreach(var path in affectedPaths) { - var newPath = path.Replace(sourcePath, destPath, StringComparison.OrdinalIgnoreCase); + var newPath = StringOperations.Replace(path, sourcePath, destPath); files[newPath] = files[path]; files.Remove(path); } diff --git a/System.IO.Abstractions.TestingHelpers/MockFileSystemWatcher.cs b/System.IO.Abstractions.TestingHelpers/MockFileSystemWatcher.cs deleted file mode 100644 index a1204412d..000000000 --- a/System.IO.Abstractions.TestingHelpers/MockFileSystemWatcher.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.ComponentModel; - -namespace System.IO.Abstractions.TestingHelpers -{ - public class MockFileSystemWatcher : FileSystemWatcherBase - { - public override bool IncludeSubdirectories { get; set; } - public override bool EnableRaisingEvents { get; set; } - public override string Filter { get; set; } - public override int InternalBufferSize { get; set; } - public override NotifyFilters NotifyFilter { get; set; } - public override string Path { get; set; } -#if NET40 - public override ISite Site { get; set; } - public override ISynchronizeInvoke SynchronizingObject { get; set; } -#endif - -#if NET40 - public override void BeginInit() - { - } - - public override void EndInit() - { - } -#endif - - public override WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType) - { - throw new NotImplementedException(); - } - - public override WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout) - { - throw new NotImplementedException(); - } - } -} diff --git a/System.IO.Abstractions.TestingHelpers/MockFileSystemWatcherFactory.cs b/System.IO.Abstractions.TestingHelpers/MockFileSystemWatcherFactory.cs index 14a494074..fa832ba13 100644 --- a/System.IO.Abstractions.TestingHelpers/MockFileSystemWatcherFactory.cs +++ b/System.IO.Abstractions.TestingHelpers/MockFileSystemWatcherFactory.cs @@ -1,21 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace System.IO.Abstractions.TestingHelpers +namespace System.IO.Abstractions.TestingHelpers { [Serializable] public class MockFileSystemWatcherFactory : IFileSystemWatcherFactory { - public FileSystemWatcherBase CreateNew() - { - return new MockFileSystemWatcher(); - } + public FileSystemWatcherBase CreateNew() => + throw new NotImplementedException(StringResources.Manager.GetString("FILE_SYSTEM_WATCHER_NOT_IMPLEMENTED_EXCEPTION")); - public FileSystemWatcherBase FromPath(string path) - { - return new MockFileSystemWatcher {Path = path}; - } + public FileSystemWatcherBase FromPath(string path) => + throw new NotImplementedException(StringResources.Manager.GetString("FILE_SYSTEM_WATCHER_NOT_IMPLEMENTED_EXCEPTION")); } } diff --git a/System.IO.Abstractions.TestingHelpers/MockPath.cs b/System.IO.Abstractions.TestingHelpers/MockPath.cs index ae44854ea..a1929b457 100644 --- a/System.IO.Abstractions.TestingHelpers/MockPath.cs +++ b/System.IO.Abstractions.TestingHelpers/MockPath.cs @@ -12,8 +12,6 @@ public class MockPath : PathWrapper { private readonly IMockFileDataAccessor mockFileDataAccessor; - private static readonly char[] InvalidAdditionalPathChars = { '*', '?' }; - public MockPath(IMockFileDataAccessor mockFileDataAccessor) : base(mockFileDataAccessor?.FileSystem) { this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); @@ -34,8 +32,8 @@ public override string GetFullPath(string path) path = path.Replace(AltDirectorySeparatorChar, DirectorySeparatorChar); bool isUnc = - path.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase) || - path.StartsWith(@"//", StringComparison.OrdinalIgnoreCase); + mockFileDataAccessor.StringOperations.StartsWith(path, @"\\") || + mockFileDataAccessor.StringOperations.StartsWith(path, @"//"); string root = GetPathRoot(path); @@ -58,7 +56,8 @@ public override string GetFullPath(string path) throw new ArgumentException(@"The UNC path should be of the form \\server\share.", "path"); } } - else if (@"\".Equals(root, StringComparison.OrdinalIgnoreCase) || @"/".Equals(root, StringComparison.OrdinalIgnoreCase)) + else if (mockFileDataAccessor.StringOperations.Equals(@"\", root) || + mockFileDataAccessor.StringOperations.Equals(@"/", root)) { // absolute path on the current drive or volume pathSegments = GetSegments(GetPathRoot(mockFileDataAccessor.Directory.GetCurrentDirectory()), path); @@ -69,9 +68,9 @@ public override string GetFullPath(string path) } // unc paths need at least two segments, the others need one segment - bool isUnixRooted = - mockFileDataAccessor.Directory.GetCurrentDirectory() - .StartsWith(string.Format(CultureInfo.InvariantCulture, "{0}", DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); + var isUnixRooted = mockFileDataAccessor.StringOperations.StartsWith( + mockFileDataAccessor.Directory.GetCurrentDirectory(), + string.Format(CultureInfo.InvariantCulture, "{0}", DirectorySeparatorChar)); var minPathSegments = isUnc ? 2 @@ -80,7 +79,7 @@ public override string GetFullPath(string path) var stack = new Stack(); foreach (var segment in pathSegments) { - if ("..".Equals(segment, StringComparison.OrdinalIgnoreCase)) + if (mockFileDataAccessor.StringOperations.Equals("..", segment)) { // only pop, if afterwards are at least the minimal amount of path segments if (stack.Count > minPathSegments) @@ -88,7 +87,7 @@ public override string GetFullPath(string path) stack.Pop(); } } - else if (".".Equals(segment, StringComparison.OrdinalIgnoreCase)) + else if (mockFileDataAccessor.StringOperations.Equals(".", segment)) { // ignore . } @@ -137,33 +136,5 @@ public override string GetTempFileName() return fullPath; } - - internal static bool HasIllegalCharacters(string path, bool checkAdditional) - { - if (path == null) - { - throw new ArgumentNullException(nameof(path)); - } - - if (checkAdditional) - { - return path.IndexOfAny(Path.GetInvalidPathChars().Concat(InvalidAdditionalPathChars).ToArray()) >= 0; - } - - return path.IndexOfAny(Path.GetInvalidPathChars()) >= 0; - } - - internal static void CheckInvalidPathChars(string path, bool checkAdditional = false) - { - if (path == null) - { - throw new ArgumentNullException(nameof(path)); - } - - if (HasIllegalCharacters(path, checkAdditional)) - { - throw new ArgumentException(StringResources.Manager.GetString("ILLEGAL_CHARACTERS_IN_PATH_EXCEPTION")); - } - } } } diff --git a/System.IO.Abstractions.TestingHelpers/MockUnixSupport.cs b/System.IO.Abstractions.TestingHelpers/MockUnixSupport.cs index cb3321c91..d96acd332 100644 --- a/System.IO.Abstractions.TestingHelpers/MockUnixSupport.cs +++ b/System.IO.Abstractions.TestingHelpers/MockUnixSupport.cs @@ -4,28 +4,13 @@ namespace System.IO.Abstractions.TestingHelpers { public static class MockUnixSupport { - public static string Path(string path, Func isUnixF = null) - { - var isUnix = isUnixF ?? IsUnixPlatform; + private static readonly Regex pathTransform = new Regex(@"^[a-zA-Z]:(?.*)$"); - if (isUnix()) - { - path = Regex.Replace(path, @"^[a-zA-Z]:(?.*)$", "${path}"); - path = path.Replace(@"\", "/"); - } + public static string Path(string path) => IsUnixPlatform() + ? pathTransform.Replace(path, "${path}").Replace(@"\", "/") + : path; - return path; - } - - public static string Separator(Func isUnixF = null) - { - var isUnix = isUnixF ?? IsUnixPlatform; - return isUnix() ? "/" : @"\"; - } - - public static bool IsUnixPlatform() - { - return IO.Path.DirectorySeparatorChar == '/'; - } + public static bool IsUnixPlatform() => IO.Path.DirectorySeparatorChar == '/'; + public static bool IsWindowsPlatform() => IO.Path.DirectorySeparatorChar == '\\'; } } diff --git a/System.IO.Abstractions.TestingHelpers/PathVerifier.cs b/System.IO.Abstractions.TestingHelpers/PathVerifier.cs index 43cc8adf3..a1d11cfdd 100644 --- a/System.IO.Abstractions.TestingHelpers/PathVerifier.cs +++ b/System.IO.Abstractions.TestingHelpers/PathVerifier.cs @@ -2,8 +2,11 @@ namespace System.IO.Abstractions.TestingHelpers { + using XFS = MockUnixSupport; + public class PathVerifier { + private static readonly char[] AdditionalInvalidPathChars = { '*', '?' }; private readonly IMockFileDataAccessor _mockFileDataAccessor; internal PathVerifier(IMockFileDataAccessor mockFileDataAccessor) @@ -28,12 +31,9 @@ public void IsLegalAbsoluteOrRelative(string path, string paramName) throw new ArgumentException(StringResources.Manager.GetString("THE_PATH_IS_NOT_OF_A_LEGAL_FORM"), paramName); } - if (!MockUnixSupport.IsUnixPlatform()) + if (XFS.IsWindowsPlatform() && !IsValidUseOfVolumeSeparatorChar(path)) { - if (!IsValidUseOfVolumeSeparatorChar(path)) - { - throw new NotSupportedException(StringResources.Manager.GetString("THE_PATH_IS_NOT_OF_A_LEGAL_FORM")); - } + throw new NotSupportedException(StringResources.Manager.GetString("THE_PATH_IS_NOT_OF_A_LEGAL_FORM")); } if (ExtractFileName(path).IndexOfAny(_mockFileDataAccessor.Path.GetInvalidFileNameChars()) > -1) @@ -42,7 +42,8 @@ public void IsLegalAbsoluteOrRelative(string path, string paramName) } var filePath = ExtractFilePath(path); - if (MockPath.HasIllegalCharacters(filePath, false)) + + if (HasIllegalCharacters(filePath, checkAdditional: false)) { throw new ArgumentException(StringResources.Manager.GetString("ILLEGAL_CHARACTERS_IN_PATH_EXCEPTION")); } @@ -68,5 +69,35 @@ private string ExtractFilePath(string fullFileName) _mockFileDataAccessor.Path.AltDirectorySeparatorChar); return string.Join(_mockFileDataAccessor.Path.DirectorySeparatorChar.ToString(), extractFilePath.Take(extractFilePath.Length - 1)); } + + public bool HasIllegalCharacters(string path, bool checkAdditional) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + var invalidPathChars = _mockFileDataAccessor.Path.GetInvalidPathChars(); + + if (checkAdditional) + { + return path.IndexOfAny(invalidPathChars.Concat(AdditionalInvalidPathChars).ToArray()) >= 0; + } + + return path.IndexOfAny(invalidPathChars) >= 0; + } + + public void CheckInvalidPathChars(string path, bool checkAdditional = false) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + if (HasIllegalCharacters(path, checkAdditional)) + { + throw new ArgumentException(StringResources.Manager.GetString("ILLEGAL_CHARACTERS_IN_PATH_EXCEPTION")); + } + } } } diff --git a/System.IO.Abstractions.TestingHelpers/Properties/Resources.resx b/System.IO.Abstractions.TestingHelpers/Properties/Resources.resx index 5e8750581..13097ff13 100644 --- a/System.IO.Abstractions.TestingHelpers/Properties/Resources.resx +++ b/System.IO.Abstractions.TestingHelpers/Properties/Resources.resx @@ -144,4 +144,7 @@ Could not find file '{0}'. - \ No newline at end of file + + 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. + + diff --git a/System.IO.Abstractions.TestingHelpers/StringExtensions.cs b/System.IO.Abstractions.TestingHelpers/StringExtensions.cs index b4a58919f..f8640c861 100644 --- a/System.IO.Abstractions.TestingHelpers/StringExtensions.cs +++ b/System.IO.Abstractions.TestingHelpers/StringExtensions.cs @@ -1,9 +1,12 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Linq; using System.Text; namespace System.IO.Abstractions.TestingHelpers { + using XFS = MockUnixSupport; + public static class StringExtensions { [Pure] @@ -66,14 +69,14 @@ public static string TrimSlashes(this string path) var trimmed = path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - if (MockUnixSupport.IsUnixPlatform() + if (XFS.IsUnixPlatform() && (path[0] == Path.DirectorySeparatorChar || path[0] == Path.AltDirectorySeparatorChar) && trimmed == "") { return Path.DirectorySeparatorChar.ToString(); } - if (!MockUnixSupport.IsUnixPlatform() + if (XFS.IsWindowsPlatform() && trimmed.Length == 2 && char.IsLetter(trimmed[0]) && trimmed[1] == ':') @@ -83,5 +86,39 @@ public static string TrimSlashes(this string path) return trimmed; } + + [Pure] + public static string NormalizeSlashes(this string path) + { + path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + var sep = Path.DirectorySeparatorChar.ToString(); + var doubleSep = sep + sep; + + var prefixSeps = new string(path.TakeWhile(c => c == Path.DirectorySeparatorChar).ToArray()); + path = path.Substring(prefixSeps.Length); + + // UNC Paths start with double slash but no reason + // to have more than 2 slashes at the start of a path + if (XFS.IsWindowsPlatform() && prefixSeps.Length > 2) + { + prefixSeps = prefixSeps.Substring(0, 2); + } + else if (prefixSeps.Length > 1) + { + prefixSeps = prefixSeps.Substring(0, 1); + } + + while (true) + { + var newPath = path.Replace(doubleSep, sep); + + if (path == newPath) + { + return prefixSeps + path; + } + + path = newPath; + } + } } } \ No newline at end of file diff --git a/System.IO.Abstractions.TestingHelpers/StringOperations.cs b/System.IO.Abstractions.TestingHelpers/StringOperations.cs new file mode 100644 index 000000000..289426a8c --- /dev/null +++ b/System.IO.Abstractions.TestingHelpers/StringOperations.cs @@ -0,0 +1,29 @@ +namespace System.IO.Abstractions.TestingHelpers +{ + [Serializable] + public class StringOperations + { + private readonly bool caseSensitive; + private readonly StringComparison comparison; + + public StringOperations(bool caseSensitive) + { + this.caseSensitive = caseSensitive; + comparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + } + + public StringComparer Comparer => caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + public bool StartsWith(string s, string prefix) => s.StartsWith(prefix, comparison); + public bool EndsWith(string s, string suffix) => s.EndsWith(suffix, comparison); + public bool Equals(string x, string y) => string.Equals(x, y, comparison); + public bool Equals(char x, char y) => caseSensitive ? x == y : char.ToUpper(x) == char.ToUpper(y); + public int IndexOf(string s, string substring) => s.IndexOf(substring, comparison); + public int IndexOf(string s, string substring, int startIndex) => s.IndexOf(substring, startIndex, comparison); + public bool Contains(string s, string substring) => s.IndexOf(substring, comparison) >= 0; + public string Replace(string s, string oldValue, string newValue) => s.Replace(oldValue, newValue, comparison); + public char ToLower(char c) => caseSensitive ? c : char.ToLower(c); + public char ToUpper(char c) => caseSensitive ? c : char.ToUpper(c); + public string ToLower(string s) => caseSensitive ? s : s.ToLower(); + public string ToUpper(string s) => caseSensitive ? s : s.ToUpper(); + } +} diff --git a/System.IO.Abstractions.TestingHelpers/System.IO.Abstractions.TestingHelpers.csproj b/System.IO.Abstractions.TestingHelpers/System.IO.Abstractions.TestingHelpers.csproj index b2dea9e9b..7c83ae1bd 100644 --- a/System.IO.Abstractions.TestingHelpers/System.IO.Abstractions.TestingHelpers.csproj +++ b/System.IO.Abstractions.TestingHelpers/System.IO.Abstractions.TestingHelpers.csproj @@ -1,8 +1,8 @@ - net40;netstandard1.4;netstandard2.0; + netstandard1.4;netstandard2.0 + $(TargetFrameworks);net40 System.IO.Abstractions.TestingHelpers - 0.0.0.1 A set of pre-built mocks to help when testing file system interactions. System.IO.Abstractions @@ -36,4 +36,12 @@ + + + + runtime; build; native; contentfiles; analyzers + all + + + diff --git a/System.IO.Abstractions/System.IO.Abstractions.csproj b/System.IO.Abstractions/System.IO.Abstractions.csproj index 01be60823..5f8b13f8b 100644 --- a/System.IO.Abstractions/System.IO.Abstractions.csproj +++ b/System.IO.Abstractions/System.IO.Abstractions.csproj @@ -1,8 +1,8 @@ - net40;netstandard1.4;netstandard2.0; - 0.0.0.1 + netstandard1.4;netstandard2.0 + $(TargetFrameworks);net40 Tatham Oddie A set of abstractions to help make file system interactions testable. @@ -11,7 +11,8 @@ https://github.com/System-IO-Abstractions/System.IO.Abstractions/blob/master/LICENSE https://github.com/System-IO-Abstractions/System.IO.Abstractions testing - true + true + $(NoWarn);CS1591;CS1574 @@ -27,7 +28,7 @@ 4.5.0 - + 4.5.0 @@ -47,4 +48,12 @@ + + + + runtime; build; native; contentfiles; analyzers + all + + + diff --git a/after.System.IO.Abstractions.sln.targets b/after.System.IO.Abstractions.sln.targets new file mode 100644 index 000000000..cf28d8236 --- /dev/null +++ b/after.System.IO.Abstractions.sln.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 0c6c946fd..6e709c648 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,36 +1,33 @@ -image: Visual Studio 2017 +image: +- Visual Studio 2017 +- Ubuntu + configuration: Release +# Don't start CI for branches that already have a PR skip_branch_with_pr: true +# Don't start CI when tags are pushed skip_tags: true +# Limit CI to master/release branches (and PRs targeting them) branches: only: - master - -version: 2.1.0.{build} - -pull_requests: - do_not_increment_build_number: true - -dotnet_csproj: - patch: true - file: '**\*.csproj' - version: '{version}' - package_version: '{version}' + - release environment: INHERITDOC_VERSION: 1.2.2.1 TEMP_DIR: c:\temp + APPVEYOR_YML_DISABLE_PS_LINUX: true install: # Temporarily install InheritDoc using the NuGet CLI -- nuget install inheritdoc -Version %INHERITDOC_VERSION% -OutputDirectory %TEMP_DIR% +- cmd: nuget install inheritdoc -Version %INHERITDOC_VERSION% -OutputDirectory %TEMP_DIR% -before_build: +before_build: - nuget restore - + build: publish_nuget: true @@ -38,11 +35,26 @@ before_package: # This step replaces the inheritdoc tags in the xml documentation with documentation from mscorlib.xml - ps: '& $env:TEMP_DIR\InheritDoc.$env:INHERITDOC_VERSION\tools\InheritDoc.exe -b $env:APPVEYOR_BUILD_FOLDER -g "c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.X\mscorlib.xml" -o' -deploy: -- provider: NuGet - api_key: - secure: lUmSV95QwsQtvPkHT02PT5fswmVcW0oMIJvOGEincUNnUNtTe3DOIjVEOpC0CMkQ - skip_symbols: false - artifact: /.*\.nupkg/ - on: - branch: master +# Perform deployments only for Windows job +for: +- + matrix: + only: + - image: Visual Studio 2017 + deploy: + - provider: NuGet + api_key: + secure: z0Zh4VyDdeKlFrTEnYbhqDc0xw9M5ahv/fQJsShk3HqmUWjQ/bh4M/r6ZWZgY/ZB + skip_symbols: false + artifact: /.*\.nupkg/ + on: + branch: release + + - provider: GitHub + tag: v$(appveyor_build_version) + prerelease: false + artifact: /.*\.nupkg/ + auth_token: + secure: Rrk1zp93EpCyxec/GXKnRnJjX7vU+ClNZEBnxbM+1j6l+C56qSV7B/fUrVsJuZYV + on: + branch: release diff --git a/version.json b/version.json new file mode 100644 index 000000000..292054aac --- /dev/null +++ b/version.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "3.0", + "assemblyVersion": { + "precision": "major" + }, + "publicReleaseRefSpec": [ + "^refs/heads/release$" + ], + "cloudBuild": { + "buildNumber": { + "enabled": true, + "setVersionVariables": true + } + } +}