Index: Core/Common/test/Core.Common.TestUtil.Test/DirectoryDisposeHelperTest.cs =================================================================== diff -u -r18123986eb930726584d136a4f222c2bfde9ae25 -r28abea0fa843f62a5c0835cba5831cf2206abc5d --- Core/Common/test/Core.Common.TestUtil.Test/DirectoryDisposeHelperTest.cs (.../DirectoryDisposeHelperTest.cs) (revision 18123986eb930726584d136a4f222c2bfde9ae25) +++ Core/Common/test/Core.Common.TestUtil.Test/DirectoryDisposeHelperTest.cs (.../DirectoryDisposeHelperTest.cs) (revision 28abea0fa843f62a5c0835cba5831cf2206abc5d) @@ -21,6 +21,7 @@ using System; using System.IO; +using System.Security.AccessControl; using NUnit.Framework; namespace Core.Common.TestUtil.Test @@ -31,17 +32,47 @@ private static readonly TestDataPath testPath = TestDataPath.Core.Common.TestUtils; [Test] + public void Constructor_NullRoot_ThrowsArgumentNullException() + { + // Setup + string subfolder = "sub folder"; + + // Call + TestDelegate test = () => new DirectoryDisposeHelper(null, subfolder); + + // Assert + string paramName = Assert.Throws(test).ParamName; + Assert.AreEqual("rootFolder", paramName); + } + + [Test] + public void Constructor_NullSubfolder_ThrowsArgumentException() + { + // Setup + string rootfolder = "root folder"; + + // Call + TestDelegate test = () => new DirectoryDisposeHelper(rootfolder, null); + + // Assert + ArgumentException exception = Assert.Throws(test); + Assert.AreEqual("subFolders", exception.ParamName); + } + + [Test] public void Constructor_NotExistingFolder_CreatesFolder() { // Setup - string folderPath = TestHelper.GetTestDataPath(testPath, Path.GetRandomFileName()); - var folderExists = false; + string rootFolder = TestHelper.GetTestDataPath(testPath); + string subFolder = Path.GetRandomFileName(); + string folderPath = Path.Combine(rootFolder, subFolder); + bool folderExists = false; // Precondition Assert.IsFalse(Directory.Exists(folderPath), $"Precondition failed: Folder '{folderPath}' should not exist"); // Call - using (new DirectoryDisposeHelper(folderPath)) + using (new DirectoryDisposeHelper(rootFolder, subFolder)) { folderExists = Directory.Exists(folderPath); } @@ -56,18 +87,48 @@ } [Test] + public void Constructor_NotExistingFolders_CreatesFolders() + { + // Setup + string rootFolder = TestHelper.GetTestDataPath(testPath); + string subFolder = Path.GetRandomFileName(); + string subSubFolder = Path.GetRandomFileName(); + string folderPath = Path.Combine(rootFolder, subFolder, subSubFolder); + bool folderExists = false; + + // Precondition + Assert.IsFalse(Directory.Exists(folderPath), $"Precondition failed: Folder '{folderPath}' should not exist"); + + // Call + using (new DirectoryDisposeHelper(rootFolder, subFolder, subSubFolder)) + { + folderExists = Directory.Exists(folderPath); + } + + // Assert + Assert.IsTrue(folderExists); + if (Directory.Exists(folderPath)) + { + Directory.Delete(folderPath, true); + Assert.Fail($"Folder path '{folderPath}' was not removed."); + } + } + + [Test] public void Constructor_ExistingFolder_DoesNotThrowException() { // Setup - string folderPath = TestHelper.GetTestDataPath(testPath, Path.GetRandomFileName()); - DirectoryDisposeHelper disposeHelper = null; + string rootFolder = TestHelper.GetTestDataPath(testPath); + string subFolder = Path.GetRandomFileName(); + string folderPath = Path.Combine(rootFolder, subFolder); + DirectoryDisposeHelper disposeHelper = null; try { Directory.CreateDirectory(folderPath); // Call - TestDelegate test = () => disposeHelper = new DirectoryDisposeHelper(folderPath); + TestDelegate test = () => disposeHelper = new DirectoryDisposeHelper(rootFolder, subFolder); // Assert Assert.DoesNotThrow(test); @@ -88,12 +149,14 @@ } [Test] - [TestCase("/fo:der/")] - [TestCase("f*lder")] - public void Constructor_InvalidFolderPath_ThrowsArgumentException(string folderPath) + [TestCase("/fo:der/", "valid")] + [TestCase("valid", "/fo:der/")] + [TestCase("f*lder", "valid")] + [TestCase("valid", "f*lder")] + public void Constructor_InvalidFolderPath_ThrowsArgumentException(string rootFolder, string subfolder) { // Call - TestDelegate test = () => new DirectoryDisposeHelper(folderPath); + TestDelegate test = () => new DirectoryDisposeHelper(rootFolder, subfolder); // Assert Assert.Throws(test); @@ -103,12 +166,13 @@ public void Dispose_AlreadyDisposed_DoesNotThrowException() { // Setup - string folderPath = TestHelper.GetTestDataPath(testPath, Path.GetRandomFileName()); + string rootFolder = TestHelper.GetTestDataPath(testPath); + string subfolder = Path.GetRandomFileName(); // Call TestDelegate test = () => { - using (var directoryDisposeHelper = new DirectoryDisposeHelper(folderPath)) + using (var directoryDisposeHelper = new DirectoryDisposeHelper(rootFolder, subfolder)) { directoryDisposeHelper.Dispose(); } @@ -122,12 +186,14 @@ public void Dispose_FolderAlreadyRemoved_DoesNotThrowException() { // Setup - string folderPath = TestHelper.GetTestDataPath(testPath, Path.GetRandomFileName()); + string rootFolder = TestHelper.GetTestDataPath(testPath); + string subfolder = Path.GetRandomFileName(); + string folderPath = Path.Combine(rootFolder, subfolder); // Call TestDelegate test = () => { - using (new DirectoryDisposeHelper(folderPath)) + using (new DirectoryDisposeHelper(rootFolder, subfolder)) { Directory.Delete(folderPath, true); } @@ -136,5 +202,123 @@ // Assert Assert.DoesNotThrow(test); } + + [Test] + public void LockDirectory_ValidPath_LocksDirectory() + { + // Setup + string rootFolder = TestHelper.GetTestDataPath(testPath); + string subfolder = Path.GetRandomFileName(); + string folderPath = Path.Combine(rootFolder, subfolder); + + try + { + using (var disposeHelper = new DirectoryDisposeHelper(rootFolder, subfolder)) + { + // Call + disposeHelper.LockDirectory(FileSystemRights.Write); + + // Assert + Assert.IsFalse(IsDirectoryWritable(folderPath), $"'{folderPath}' is not locked for writing."); + } + + Assert.IsFalse(Directory.Exists(folderPath), $"'{folderPath}' should have been deleted."); + } + catch (Exception exception) + { + RemoveDirectoryAndFail(folderPath, exception); + } + } + + [Test] + public void LockDirectory_RightNotSupported_ThrowsInvalidOperationException() + { + // Setup + string rootFolder = TestHelper.GetTestDataPath(testPath); + string subfolder = Path.GetRandomFileName(); + string folderPath = Path.Combine(rootFolder, subfolder); + + try + { + using (var disposeHelper = new DirectoryDisposeHelper(rootFolder, subfolder)) + using (new DirectoryPermissionsRevoker(folderPath, FileSystemRights.Write)) + { + // Call + TestDelegate call = () => disposeHelper.LockDirectory(FileSystemRights.Synchronize); + + // Assert + InvalidOperationException exception = Assert.Throws(call); + Assert.AreEqual($"Unable to lock '{folderPath}'.", exception.Message); + Assert.IsNotNull(exception.InnerException); + } + + Assert.IsFalse(Directory.Exists(folderPath), $"'{folderPath}' should have been deleted."); + } + catch (Exception exception) + { + RemoveDirectoryAndFail(folderPath, exception); + } + } + + [Test] + public void LockDirectory_DirectoryAlreadyLocked_DoesNotThrowException() + { + // Setup + string rootFolder = TestHelper.GetTestDataPath(testPath); + string subfolder = Path.GetRandomFileName(); + string folderPath = Path.Combine(rootFolder, subfolder); + + try + { + using (var disposeHelper = new DirectoryDisposeHelper(rootFolder, subfolder)) + using (new DirectoryPermissionsRevoker(folderPath, FileSystemRights.Write)) + { + // Call + TestDelegate call = () => disposeHelper.LockDirectory(FileSystemRights.Write); + + // Assert + Assert.DoesNotThrow(call); + } + + Assert.IsFalse(Directory.Exists(folderPath), $"'{folderPath}' should have been deleted."); + } + catch (Exception exception) + { + RemoveDirectoryAndFail(folderPath, exception); + } + } + + private static void RemoveDirectoryAndFail(string folderPath, Exception exception) + { + try + { + Directory.Delete(folderPath); + } + catch + { + // Ignore + } + Assert.Fail(exception.Message, exception.InnerException); + } + + private static bool IsDirectoryWritable(string folderPath) + { + string filePath = Path.Combine(folderPath, Path.GetRandomFileName()); + try + { + using (File.OpenWrite(filePath)) {} + } + catch (SystemException) + { + return false; + } + + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + + return true; + } } } \ No newline at end of file Index: Core/Common/test/Core.Common.TestUtil.Test/DirectoryPermissionsRevokerTest.cs =================================================================== diff -u -r3af93a9631f5185c86a5f6f9d7d621bfd111e8e9 -r28abea0fa843f62a5c0835cba5831cf2206abc5d --- Core/Common/test/Core.Common.TestUtil.Test/DirectoryPermissionsRevokerTest.cs (.../DirectoryPermissionsRevokerTest.cs) (revision 3af93a9631f5185c86a5f6f9d7d621bfd111e8e9) +++ Core/Common/test/Core.Common.TestUtil.Test/DirectoryPermissionsRevokerTest.cs (.../DirectoryPermissionsRevokerTest.cs) (revision 28abea0fa843f62a5c0835cba5831cf2206abc5d) @@ -31,7 +31,7 @@ [TestFixture] public class DirectoryPermissionsRevokerTest { - private readonly string testWorkDir = Path.Combine(".", "DirectoryPermissionsRevokerTest"); + private readonly TestDataPath testWorkDir = TestDataPath.Core.Common.TestUtils; [Test] [TestCase(null)] @@ -65,21 +65,18 @@ { // Setup const FileSystemRights rights = FileSystemRights.Synchronize; - string accessDirectory = Path.Combine(testWorkDir, "Constructor_UnsupportedRight_ThrowsNotSupportedException", rights.ToString()); - Directory.CreateDirectory(accessDirectory); + string rootFolder = TestHelper.GetTestDataPath(testWorkDir); + string subfolder = "UnsupportedRight" + rights; + string folderPath = Path.Combine(rootFolder, subfolder); - try + using (new DirectoryDisposeHelper(rootFolder, subfolder)) { // Call - TestDelegate test = () => new DirectoryPermissionsRevoker(accessDirectory, rights); + TestDelegate test = () => new DirectoryPermissionsRevoker(folderPath, rights); // Assert Assert.Throws(test); } - finally - { - Directory.Delete(accessDirectory, true); - } } [Test] @@ -106,24 +103,21 @@ public void Constructor_ValidPathDenyRight_SetsDenyRight(FileSystemRights rights) { // Setup - string accessDirectory = Path.Combine(testWorkDir, "Constructor_ValidPathDenyRight_SetsDenyRight", rights.ToString()); - Directory.CreateDirectory(accessDirectory); + string rootFolder = TestHelper.GetTestDataPath(testWorkDir); + string subfolder = "ValidPathDenyRight_SetsDenyRight" + rights; + string folderPath = Path.Combine(rootFolder, subfolder); - try + using (new DirectoryDisposeHelper(rootFolder, subfolder)) { // Call - using (new DirectoryPermissionsRevoker(accessDirectory, rights)) + using (new DirectoryPermissionsRevoker(folderPath, rights)) { // Assert - AssertPathHasAccessRuleSet(accessDirectory, rights); + AssertPathHasAccessRuleSet(folderPath, rights); } - AssertPathHasAccessRuleNotSet(accessDirectory, rights); + AssertPathHasAccessRuleNotSet(folderPath, rights); } - finally - { - Directory.Delete(accessDirectory, true); - } } [Test] @@ -150,50 +144,57 @@ public void Dispose_RightAlreadySet_DoesNotRemoveRight(FileSystemRights rights) { // Setup - string accessDirectory = Path.Combine(testWorkDir, "Dispose_RightAlreadySet_DoesNotRemoveRight", rights.ToString()); - Directory.CreateDirectory(accessDirectory); + string rootFolder = TestHelper.GetTestDataPath(testWorkDir); + string subfolder = "RightAlreadySet" + rights; + string folderPath = Path.Combine(rootFolder, subfolder); - AddDirectoryAccessRule(accessDirectory, rights); + using (new DirectoryDisposeHelper(rootFolder, subfolder)) + { + AddDirectoryAccessRule(folderPath, rights); - // Precondition - AssertPathHasAccessRuleSet(accessDirectory, rights); + // Precondition + AssertPathHasAccessRuleSet(folderPath, rights); - try - { - // Call - using (new DirectoryPermissionsRevoker(accessDirectory, rights)) + try { - // Assert - AssertPathHasAccessRuleSet(accessDirectory, rights); - } + // Call + using (new DirectoryPermissionsRevoker(folderPath, rights)) + { + // Assert + AssertPathHasAccessRuleSet(folderPath, rights); + } - AssertPathHasAccessRuleSet(accessDirectory, rights); + AssertPathHasAccessRuleSet(folderPath, rights); + } + finally + { + RemoveDirectoryAccessRule(folderPath, rights); + } } - finally - { - RemoveDirectoryAccessRule(accessDirectory, rights); - Directory.Delete(accessDirectory, true); - } } [Test] public void Dispose_DirectoryAlreadyRemoved_DoesNotThrowException() { // Setup - string accessDirectory = Path.Combine(testWorkDir, "Deleted"); - Directory.CreateDirectory(accessDirectory); + string rootFolder = TestHelper.GetTestDataPath(testWorkDir); + string subfolder = "Deleted"; + string folderPath = Path.Combine(rootFolder, subfolder); - TestDelegate test = () => + using (new DirectoryDisposeHelper(rootFolder, subfolder)) { - // Call - using (new DirectoryPermissionsRevoker(accessDirectory, FileSystemRights.Write)) + TestDelegate test = () => { - Directory.Delete(accessDirectory, true); - } - }; + // Call + using (new DirectoryPermissionsRevoker(folderPath, FileSystemRights.Write)) + { + Directory.Delete(folderPath, true); + } + }; - // Assert - Assert.DoesNotThrow(test); + // Assert + Assert.DoesNotThrow(test); + } } #region Assert access rules @@ -202,16 +203,14 @@ { FileSystemRights supportedFileSystemRights = GetSupportedFileSystemRights(rights); FileSystemAccessRule fileSystemAccessRule = GetFirstFileSystemAccessRuleForRights(filePath, supportedFileSystemRights); - Assert.IsNull(fileSystemAccessRule, string.Format("Rights '{0} {1}' are set for '{2}'", - AccessControlType.Deny, supportedFileSystemRights, filePath)); + Assert.IsNull(fileSystemAccessRule, $"Rights '{AccessControlType.Deny} {supportedFileSystemRights}' are set for '{filePath}'"); } - private void AssertPathHasAccessRuleSet(string filePath, FileSystemRights rights) + private static void AssertPathHasAccessRuleSet(string filePath, FileSystemRights rights) { FileSystemRights supportedFileSystemRights = GetSupportedFileSystemRights(rights); FileSystemAccessRule fileSystemAccessRule = GetFirstFileSystemAccessRuleForRights(filePath, supportedFileSystemRights); - Assert.IsNotNull(fileSystemAccessRule, string.Format("Rights '{0} {1}' not set for '{2}'", - AccessControlType.Deny, (supportedFileSystemRights), filePath)); + Assert.IsNotNull(fileSystemAccessRule, $"Rights '{AccessControlType.Deny} {supportedFileSystemRights}' not set for '{filePath}'"); } private static FileSystemRights GetSupportedFileSystemRights(FileSystemRights rights) Index: Core/Common/test/Core.Common.TestUtil/DirectoryDisposeHelper.cs =================================================================== diff -u -r18123986eb930726584d136a4f222c2bfde9ae25 -r28abea0fa843f62a5c0835cba5831cf2206abc5d --- Core/Common/test/Core.Common.TestUtil/DirectoryDisposeHelper.cs (.../DirectoryDisposeHelper.cs) (revision 18123986eb930726584d136a4f222c2bfde9ae25) +++ Core/Common/test/Core.Common.TestUtil/DirectoryDisposeHelper.cs (.../DirectoryDisposeHelper.cs) (revision 28abea0fa843f62a5c0835cba5831cf2206abc5d) @@ -21,38 +21,74 @@ using System; using System.IO; +using System.Linq; +using System.Security.AccessControl; namespace Core.Common.TestUtil { /// /// This class can be used to set temporary folders while testing. - /// Disposing an instance of this class will delete the folder(s). + /// Disposing an instance of this class will delete the subfolder(s). /// /// /// The following is an example for how to use this class: /// - /// using(new DirectoryDisposeHelper("path")) { + /// using(new DirectoryDisposeHelper("root path", "sub folder to create" + /// [, "sub sub folder to create"])) { /// // Perform tests with folders /// } /// /// public class DirectoryDisposeHelper : IDisposable { - private readonly string folderPath; + private readonly string rootPathToTemp; + private readonly string fullPath; private bool disposed; + private DirectoryPermissionsRevoker directoryPermissionsRevoker; /// /// Creates a new instance of . /// - /// Paths that will be created, if the path is valid. - /// Thrown when could + /// Root folder to create the temporary folders in. + /// Path that will temporarily be created. + /// Thrown when is null. + /// Thrown when is null, empty, or could /// not be created by the system. - public DirectoryDisposeHelper(string folderPath) + public DirectoryDisposeHelper(string rootFolder, params string[] subFolders) { - this.folderPath = folderPath; + if (rootFolder == null) + { + throw new ArgumentNullException(nameof(rootFolder)); + } + if (subFolders == null || !subFolders.Any()) + { + throw new ArgumentException(@"Must have at least one sub folder.", nameof(subFolders)); + } + + rootPathToTemp = Path.Combine(rootFolder, subFolders[0]); + fullPath = Path.Combine(rootFolder, Path.Combine(subFolders)); CreatePath(); } + /// + /// Adds a of type to the access + /// rule set for the folder at . + /// + /// The right to deny. + /// Thrown when the directory could not be locked. + /// + public void LockDirectory(FileSystemRights rights) + { + try + { + directoryPermissionsRevoker = new DirectoryPermissionsRevoker(rootPathToTemp, rights); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Unable to lock '{rootPathToTemp}'.", exception); + } + } + public void Dispose() { Dispose(true); @@ -66,15 +102,22 @@ return; } + directoryPermissionsRevoker?.Dispose(); + try { - Directory.Delete(folderPath, true); + Directory.Delete(rootPathToTemp, true); } catch { // ignored } + if (disposing) + { + directoryPermissionsRevoker = null; + } + disposed = true; } @@ -86,12 +129,20 @@ { try { - Directory.CreateDirectory(folderPath); + Directory.CreateDirectory(fullPath); } - catch (Exception e) when (e is DirectoryNotFoundException || e is IOException || e is NotSupportedException || e is UnauthorizedAccessException) + catch (Exception e) when (e is DirectoryNotFoundException + || e is IOException + || e is NotSupportedException + || e is UnauthorizedAccessException) { throw new ArgumentException(e.Message, e); } } + + ~DirectoryDisposeHelper() + { + Dispose(false); + } } } \ No newline at end of file Index: Core/Common/test/Core.Common.TestUtil/DirectoryPermissionsRevoker.cs =================================================================== diff -u -r589249bddd12bd66da39205d408ff6e907220116 -r28abea0fa843f62a5c0835cba5831cf2206abc5d --- Core/Common/test/Core.Common.TestUtil/DirectoryPermissionsRevoker.cs (.../DirectoryPermissionsRevoker.cs) (revision 589249bddd12bd66da39205d408ff6e907220116) +++ Core/Common/test/Core.Common.TestUtil/DirectoryPermissionsRevoker.cs (.../DirectoryPermissionsRevoker.cs) (revision 28abea0fa843f62a5c0835cba5831cf2206abc5d) @@ -36,6 +36,7 @@ private readonly IList appliedFileSystemAccessRules = new List(); private readonly string folderPath; private readonly DirectoryInfo directoryInfo; + private bool disposed; /// /// Creates an instance of . @@ -44,6 +45,12 @@ /// /// The path of the file to change the right for. /// The right to deny. + /// Thrown when is null + /// or empty. + /// Thrown when + /// does not exist. + /// Thrown when the is + /// not supported to set on the folder. public DirectoryPermissionsRevoker(string folderPath, FileSystemRights rights) { if (string.IsNullOrWhiteSpace(folderPath)) @@ -68,14 +75,26 @@ public void Dispose() { - if (!Directory.Exists(folderPath)) + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) { return; } - foreach (var appliedFileSystemAccessRule in appliedFileSystemAccessRules) + + if (Directory.Exists(folderPath)) { - RevertDenyDirectoryInfoRight(appliedFileSystemAccessRule); + foreach (FileSystemAccessRule appliedFileSystemAccessRule in appliedFileSystemAccessRules) + { + RevertDenyDirectoryInfoRight(appliedFileSystemAccessRule); + } } + + disposed = true; } private void AddDenyDirectoryInfoRight(FileSystemRights rights) @@ -121,5 +140,10 @@ SecurityIdentifier id = WindowsIdentity.GetCurrent().User.AccountDomainSid; return new SecurityIdentifier(WellKnownSidType.WorldSid, id); } + + ~DirectoryPermissionsRevoker() + { + Dispose(false); + } } } \ No newline at end of file