diff --git a/Directory.Build.props b/Directory.Build.props index d0039ca..f4e293e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ True True True - 2.2.2 + 2.3.0 Kotz Copyright © Kotz 2022 https://github.com/Kaoticz/Kotz.Utilities diff --git a/Kotz.Extensions/KotzUtilities.cs b/Kotz.Extensions/KotzUtilities.cs new file mode 100644 index 0000000..adb8710 --- /dev/null +++ b/Kotz.Extensions/KotzUtilities.cs @@ -0,0 +1,404 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Kotz.Extensions; + +/// +/// A collection of utility methods. +/// +public static class KotzUtilities +{ + private static readonly string _programVerifier = (OperatingSystem.IsWindows()) ? "where" : "which"; + private static readonly string _envPathSeparator = (OperatingSystem.IsWindows()) ? ";" : ":"; + private static readonly EnvironmentVariableTarget _envTarget = (OperatingSystem.IsWindows()) + ? EnvironmentVariableTarget.User + : EnvironmentVariableTarget.Process; + + /// + /// Adds a directory path to the PATH environment variable. + /// + /// The absolute path to a directory. + /// + /// On Windows, this method needs to be called once and the dependencies will be available for the user forever.
+ /// On Unix systems, it's only possible to add to the PATH on a process basis, so this method needs to be called + /// at least once everytime the application is executed. + ///
+ /// if got successfully added to the PATH envar, otherwise. + /// + /// + public static bool AddPathToPATHEnvar(string directoryUri) + { + ArgumentException.ThrowIfNullOrEmpty(directoryUri, nameof(directoryUri)); + + if (File.Exists(directoryUri)) + throw new ArgumentException("Parameter must point to a directory, not a file.", nameof(directoryUri)); + + var envPathValue = Environment.GetEnvironmentVariable("PATH", _envTarget) ?? string.Empty; + + // If directoryPath is already in the PATH envar, don't add it again. + if (envPathValue.Contains(directoryUri, StringComparison.Ordinal)) + return false; + + var newPathEnvValue = envPathValue + _envPathSeparator + directoryUri; + + // Add path to Windows' user envar, so it persists across reboots. + if (OperatingSystem.IsWindows()) + Environment.SetEnvironmentVariable("PATH", newPathEnvValue, EnvironmentVariableTarget.User); + + // Add path to the current process' envar, so the program can see the dependencies. + Environment.SetEnvironmentVariable("PATH", newPathEnvValue, EnvironmentVariableTarget.Process); + + return true; + } + + /// + /// Checks if this application can write to the specified directory. + /// + /// The absolute path to a directory. + /// if writing is allowed, otherwise. + /// + /// + public static bool HasWritePermissionAt(ReadOnlySpan directoryUri) + { + var tempFileUri = Path.Join(directoryUri, Path.GetRandomFileName() + ".tmp"); + + try + { + using var fileStream = File.Create(tempFileUri); + return true; + } + catch (UnauthorizedAccessException) + { + return false; + } + finally + { + TryDeleteFile(tempFileUri); + } + } + + + /// + /// Checks if a program exists at the specified absolute path or the PATH environment variable. + /// + /// The name of the program. + /// if the program exists, otherwise. + /// + /// + public static bool ProgramExists(string programName) + { + ArgumentException.ThrowIfNullOrEmpty(programName, nameof(programName)); + + using var process = StartProcess(_programVerifier, programName, false, false); + process.WaitForExit(); + + return process.ExitCode is 0; + } + + + /// + /// Starts the specified program in the background. + /// + /// + /// The name of the program in the PATH environment variable, + /// or the absolute path to its executable. + /// + /// The arguments to the program. + /// Determines whether Standard Output should be redirected. + /// Determines whether Standard Error should be redirected. + /// + /// The parameter is not escaped, you can either escape it yourself or + /// use instead. + /// + /// The process of the specified program. + /// + /// + /// Occurs when does not exist. + /// Occurs when the process fails to execute. + public static Process StartProcess(string program, string arguments = "", bool redirectStdout = true, bool redirectStderr = true) + { + ArgumentException.ThrowIfNullOrEmpty(program, nameof(program)); + ArgumentNullException.ThrowIfNull(arguments, nameof(arguments)); + + return Process.Start(new ProcessStartInfo() + { + FileName = program, + Arguments = arguments, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = redirectStdout, + RedirectStandardError = redirectStderr + }) ?? throw new InvalidOperationException($"Failed spawing process for: {program} {arguments}"); + } + + + /// + /// Starts the specified program in the background. + /// + /// + /// The name of the program in the PATH environment variable, + /// or the absolute path to its executable. + /// + /// The arguments to the program. + /// Determines whether Standard Output should be redirected. + /// Determines whether Standard Error should be redirected. + /// The get automatically escaped. + /// The process of the specified program. + /// + /// + /// Occurs when does not exist. + /// Occurs when the process fails to execute. + public static Process StartProcess(string program, IEnumerable arguments, bool redirectStdout = true, bool redirectStderr = true) + { + ArgumentException.ThrowIfNullOrEmpty(program, nameof(program)); + ArgumentNullException.ThrowIfNull(arguments, nameof(arguments)); + + var processInfo = new ProcessStartInfo() + { + FileName = program, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = redirectStdout, + RedirectStandardError = redirectStderr + }; + + foreach (var argument in arguments) + processInfo.ArgumentList.Add(argument); + + return Process.Start(processInfo) + ?? throw new InvalidOperationException($"Failed spawing process for: {program} {string.Join(' ', processInfo.ArgumentList)}"); + } + + /// + /// Safely creates a object with the specified method. + /// + /// The factory method that creates the object. + /// The created object or if creation does not succeed. + /// The exception thrown by the or if creation of the object succeeds. + /// The type of the object being created. + /// if the object was successfully created, otherwise. + public static bool TryCreate(Func factory, [MaybeNullWhen(false)] out T result, [MaybeNullWhen(true)] out Exception exception) + { + try + { + result = factory(); + exception = default; + return true; + } + catch (Exception ex) + { + result = default; + exception = ex; + return false; + } + } + + /// + /// Safely deletes a file or directory. + /// + /// The absolute path to the File System Object. + /// + /// to remove directories, subdirectories, and files in the path, otherwise. + /// This is only used when deleting a directory. + /// + /// if the File System Object was deleted, otherwise. + /// + /// + /// + /// + /// + /// + /// + public static bool TryDeleteFSO(string fsoUri, bool isRecursive = true) + => TryDeleteFile(fsoUri) || TryDeleteDirectory(fsoUri, isRecursive); + + /// + /// Safely deletes a file. + /// + /// The absolute path to the file. + /// if the file was deleted, otherwise. + /// + /// + public static bool TryDeleteFile(string fileUri) + { + ArgumentException.ThrowIfNullOrEmpty(fileUri, nameof(fileUri)); + + if (!File.Exists(fileUri)) + return false; + + try + { + File.Delete(fileUri); + return true; + } + catch + { + return false; + } + } + + /// + /// Safely deletes a directory. + /// + /// The absolute path to the directory. + /// + /// to remove directories, subdirectories, and files in the path, otherwise. + /// + /// if the directory was deleted, otherwise. + /// + /// + public static bool TryDeleteDirectory(string directoryUri, bool isRecursive = true) + { + ArgumentException.ThrowIfNullOrEmpty(directoryUri, nameof(directoryUri)); + + if (!Directory.Exists(directoryUri)) + return false; + + try + { + Directory.Delete(directoryUri, isRecursive); + return true; + } + catch + { + return false; + } + } + + /// + /// Safely moves a file or directory. + /// + /// The path to the source file or directory. + /// The path to the destination file or directory. + /// if the file system object got successfully moved, otherwise. + /// + /// + public static bool TryMoveFSO(string source, string destination) + => TryMoveFile(source, destination) || TryMoveDirectory(source, destination); + + /// + /// Safely moves a file. + /// + /// The path to the source file. + /// The path to the destination file. + /// if the file got successfully moved, otherwise. + /// + /// + public static bool TryMoveFile(string source, string destination) + { + ArgumentException.ThrowIfNullOrEmpty(source, nameof(source)); + ArgumentException.ThrowIfNullOrEmpty(destination, nameof(destination)); + + if (!File.Exists(source)) + return false; + + try + { + var directoryUri = Directory.GetParent(destination) + ?? Directory.CreateDirectory(Directory.GetDirectoryRoot(destination)); + + if (!directoryUri.Exists) + directoryUri.Create(); + + File.Move(source, destination); + + return true; + } + catch + { + return false; + } + } + + /// + /// Safely moves a directory. + /// + /// The path to the source directory. + /// The path to the destination directory. + /// if the directory got successfully moved, otherwise. + /// + /// + public static bool TryMoveDirectory(string source, string destination) + { + ArgumentException.ThrowIfNullOrEmpty(source, nameof(source)); + ArgumentException.ThrowIfNullOrEmpty(destination, nameof(destination)); + + if (!Directory.Exists(source)) + return false; + + try + { + var directoryUri = Directory.GetParent(destination) + ?? Directory.CreateDirectory(Directory.GetDirectoryRoot(destination)); + + if (!directoryUri.Exists) + directoryUri.Create(); + + Directory.Move(source, destination); + return true; + } + catch (IOException) + { + try + { + return CopyMoveDirectory(source, destination, true); + } + catch + { + TryDeleteDirectory(destination, true); + return false; + } + } + catch + { + return false; + } + } + + /// + /// Moves a directory from to . + /// + /// The path to the source directory. + /// The path to the destination directory. + /// Determines whether the files and directories in should be deleted or not. + /// Use this method to circumvent this issue: https://github.com/dotnet/runtime/issues/31149 + /// if the directory got moved, otherwise. + /// Occurs when one of the files to be moved is still in use. + /// + /// + private static bool CopyMoveDirectory(string source, string destination, bool deleteSourceFSOs) + { + if (!Directory.Exists(source) || !Uri.IsWellFormedUriString(destination, UriKind.RelativeOrAbsolute)) + return false; + + var sourceDir = Directory.CreateDirectory(source); + var destinationDir = (Directory.Exists(destination)) + ? Directory.CreateDirectory(Path.Join(destination, Path.GetFileName(source))) + : Directory.CreateDirectory(destination); + + foreach (var fileUri in Directory.EnumerateFiles(source)) + File.Copy(fileUri, Path.Join(destinationDir.FullName, Path.GetFileName(fileUri))); + + foreach (var directoryUri in Directory.EnumerateDirectories(source)) + CopyMoveDirectory(directoryUri, Path.Join(destinationDir.FullName, Path.GetFileName(directoryUri)), false); + + if (deleteSourceFSOs) + { + // If 'source' is not the root of the drive, delete it. + if (sourceDir.FullName != Path.GetPathRoot(source)) + sourceDir.Delete(true); + else + { + // Else, delete all file system objects directly. + foreach (var fileUri in Directory.EnumerateFiles(source)) + File.Delete(fileUri); + + foreach (var directoryUri in Directory.EnumerateDirectories(source)) + Directory.Delete(directoryUri, true); + } + } + + return true; + } +} \ No newline at end of file diff --git a/Kotz.Extensions/README.md b/Kotz.Extensions/README.md index 4c6067c..3908307 100644 --- a/Kotz.Extensions/README.md +++ b/Kotz.Extensions/README.md @@ -78,4 +78,19 @@ Defines the following extension methods: - ToTitleCase: Converts the current string to the *Title Case* format. - **StringBuilder Extensions** - ReplaceAll: Replaces all instances of a substring with another substring, even if the new substring is a substring of the old substring. - - ToStringAndClear: Returns the `string` value of the current builder and clears the builder. \ No newline at end of file + - ToStringAndClear: Returns the `string` value of the current builder and clears the builder. + +Defines the following types: + +- **KotzUtilities**: static class with a wide range of helper methods. + - AddPathToPATHEnvar: Adds a directory path to the PATH environment variable. + - HasWritePermissionAt: Checks if this application can write to the specified directory. + - ProgramExists: Checks if a program exists at the specified absolute path or the PATH environment variable. + - StartProcess: Starts the specified program in the background. + - TryCreate: Safely creates an object with the specified factory method. + - TryDeleteFSO: Safely deletes a file or directory. + - TryDeleteFile: Safely deletes a file. + - TryDeleteDirectory: Safely deletes a directory. + - TryMoveFSO: Safely moves a file or directory. + - TryMoveFile: Safely moves a file. + - TryMoveDirectory: Safely moves a directory. \ No newline at end of file diff --git a/Kotz.Tests/Events/EventAsyncTests.cs b/Kotz.Tests/Events/EventAsyncTests.cs index 265b5fb..949c266 100644 --- a/Kotz.Tests/Events/EventAsyncTests.cs +++ b/Kotz.Tests/Events/EventAsyncTests.cs @@ -2,9 +2,9 @@ namespace Kotz.Tests.Events; -public sealed class EventAyncTests +public sealed class EventAsyncTests { - private readonly AsyncEvent _asyncEvent = new(); + private readonly AsyncEvent _asyncEvent = new(); internal int Count { get; private set; } diff --git a/Kotz.Tests/Extensions/Utilities/HasWritePermissionAtTests.cs b/Kotz.Tests/Extensions/Utilities/HasWritePermissionAtTests.cs new file mode 100644 index 0000000..54b15db --- /dev/null +++ b/Kotz.Tests/Extensions/Utilities/HasWritePermissionAtTests.cs @@ -0,0 +1,25 @@ +namespace Kotz.Tests.Extensions.Utilities; + +public sealed class HasWritePermissionAtTests +{ + [Fact] + internal void HasWritePermissionAtTrueTest() + => Assert.True(KotzUtilities.HasWritePermissionAt(AppContext.BaseDirectory)); + + [Fact] + internal void HasWritePermissionAtFalseTest() + { + var directoryUri = OperatingSystem.IsWindows() + ? Environment.GetFolderPath(Environment.SpecialFolder.System) + : "/"; + + Assert.False(KotzUtilities.HasWritePermissionAt(directoryUri)); + } + + [Fact] + internal void HasWritePermissionAtFailTest() + { + var fakeUri = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Guid.NewGuid().ToString()); + Assert.Throws(() => KotzUtilities.HasWritePermissionAt(fakeUri)); + } +} \ No newline at end of file diff --git a/Kotz.Tests/Extensions/Utilities/ProgramExistsTests.cs b/Kotz.Tests/Extensions/Utilities/ProgramExistsTests.cs new file mode 100644 index 0000000..48f47ef --- /dev/null +++ b/Kotz.Tests/Extensions/Utilities/ProgramExistsTests.cs @@ -0,0 +1,16 @@ +namespace Kotz.Tests.Extensions.Utilities; + +public sealed class ProgramExistsTests +{ + [Theory] + [InlineData(true, "echo")] + [InlineData(false, "abcde")] + internal void ProgramExistsSuccessTest(bool expected, string command) + => Assert.Equal(expected, KotzUtilities.ProgramExists(command)); + + [Theory] + [InlineData("")] + [InlineData(null)] + internal void ProgramExistsFailTest(string? command) + => Assert.ThrowsAny(() => KotzUtilities.ProgramExists(command!)); +} \ No newline at end of file diff --git a/Kotz.Tests/Extensions/Utilities/StartProgramTests.cs b/Kotz.Tests/Extensions/Utilities/StartProgramTests.cs new file mode 100644 index 0000000..94f73e5 --- /dev/null +++ b/Kotz.Tests/Extensions/Utilities/StartProgramTests.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; + +namespace Kotz.Tests.Extensions.Utilities; + +public sealed class StartProgramTests +{ + [Fact] + internal void StartProgramSuccessTest() + { + using var process1 = KotzUtilities.StartProcess("echo", "Hello from xUnit!"); + using var process2 = KotzUtilities.StartProcess("echo", ["Hello from xUnit!"]); + + Assert.NotNull(process1); + Assert.NotNull(process2); + } + + [Fact] + internal void StartProgramFailTest() + { + Assert.Throws(() => KotzUtilities.StartProcess("idonotexist")); + Assert.Throws(() => KotzUtilities.StartProcess("idonotexist", ["args"])); + Assert.Throws(() => KotzUtilities.StartProcess("", (string?)null!)); + Assert.Throws(() => KotzUtilities.StartProcess("", Enumerable.Empty())); + Assert.Throws(() => KotzUtilities.StartProcess(null!, "")); + Assert.Throws(() => KotzUtilities.StartProcess(null!, Enumerable.Empty())); + Assert.Throws(() => KotzUtilities.StartProcess("idonotexist", (string?)null!)); + Assert.Throws(() => KotzUtilities.StartProcess("idonotexist", (IEnumerable?)null!)); + } +} \ No newline at end of file diff --git a/Kotz.Tests/Extensions/Utilities/TryCreateTests.cs b/Kotz.Tests/Extensions/Utilities/TryCreateTests.cs new file mode 100644 index 0000000..32804a9 --- /dev/null +++ b/Kotz.Tests/Extensions/Utilities/TryCreateTests.cs @@ -0,0 +1,22 @@ +using System.Text; + +namespace Kotz.Tests.Extensions.Utilities; + +public sealed class TryCreateTests +{ + [Fact] + internal void TryCreateSuccessTest() + { + Assert.True(KotzUtilities.TryCreate(() => new StringBuilder(), out var actualResult, out var exception)); + Assert.IsType(actualResult); + Assert.Null(exception); + } + + [Fact] + internal void TryCreateFailTest() + { + Assert.False(KotzUtilities.TryCreate(() => throw new InvalidOperationException(), out var actualResult, out var exception)); + Assert.IsType(exception); + Assert.Null(actualResult); + } +} \ No newline at end of file diff --git a/Kotz.Tests/Extensions/Utilities/TryDeleteDirectoryTests.cs b/Kotz.Tests/Extensions/Utilities/TryDeleteDirectoryTests.cs new file mode 100644 index 0000000..4a36d4d --- /dev/null +++ b/Kotz.Tests/Extensions/Utilities/TryDeleteDirectoryTests.cs @@ -0,0 +1,40 @@ +namespace Kotz.Tests.Extensions.Utilities; + +public sealed class TryDeleteDirectoryTests +{ + [Theory] + [InlineData(true, true)] + [InlineData(false, false)] + internal void TryDeleteDirectorySuccessTest(bool createDirectory, bool expected) + { + var directoryPath = CreateDirectoryPath(createDirectory); + + Assert.Equal(createDirectory, Directory.Exists(directoryPath)); + Assert.Equal(expected, KotzUtilities.TryDeleteDirectory(directoryPath)); + + if (createDirectory) + Assert.Equal(!createDirectory, Directory.Exists(directoryPath)); + } + + [Fact] + internal void TryDeleteDirectoryFailTest() + { + Assert.Throws(() => KotzUtilities.TryDeleteDirectory(string.Empty)); + Assert.Throws(() => KotzUtilities.TryDeleteDirectory(null!)); + } + + /// + /// Creates a random directory path. + /// + /// if the directory should be created, otherwise. + /// The path to the directory. + internal static string CreateDirectoryPath(bool createDirectory) + { + var directoryPath = Path.Join(Path.GetTempPath(), Path.GetRandomFileName()); + + if (createDirectory) + Directory.CreateDirectory(directoryPath).Create(); + + return directoryPath; + } +} \ No newline at end of file diff --git a/Kotz.Tests/Extensions/Utilities/TryDeleteFileTests.cs b/Kotz.Tests/Extensions/Utilities/TryDeleteFileTests.cs new file mode 100644 index 0000000..6057542 --- /dev/null +++ b/Kotz.Tests/Extensions/Utilities/TryDeleteFileTests.cs @@ -0,0 +1,40 @@ +namespace Kotz.Tests.Extensions.Utilities; + +public sealed class TryDeleteFileTests +{ + [Theory] + [InlineData(true, true)] + [InlineData(false, false)] + internal void TryDeleteFileSuccessTest(bool createFile, bool expected) + { + var filePath = CreateFilePath(createFile); + + Assert.Equal(createFile, File.Exists(filePath)); + Assert.Equal(expected, KotzUtilities.TryDeleteFile(filePath)); + + if (createFile) + Assert.Equal(!createFile, File.Exists(filePath)); + } + + [Fact] + internal void TryDeleteFileFailTest() + { + Assert.Throws(() => KotzUtilities.TryDeleteFile(string.Empty)); + Assert.Throws(() => KotzUtilities.TryDeleteFile(null!)); + } + + /// + /// Creates a random file path. + /// + /// if the file should be created, otherwise. + /// The path to the file. + internal static string CreateFilePath(bool createFile) + { + var filePath = Path.Join(Path.GetTempPath(), Path.GetRandomFileName() + ".tmp"); + + if (createFile) + File.Create(filePath).Dispose(); + + return filePath; + } +} \ No newline at end of file diff --git a/Kotz.Tests/Extensions/Utilities/TryMoveDirectoryTests.cs b/Kotz.Tests/Extensions/Utilities/TryMoveDirectoryTests.cs new file mode 100644 index 0000000..16d567f --- /dev/null +++ b/Kotz.Tests/Extensions/Utilities/TryMoveDirectoryTests.cs @@ -0,0 +1,86 @@ +namespace Kotz.Tests.Extensions.Utilities; + +public sealed class TryMoveDirectoryTests +{ + [Theory] + [InlineData(true, true)] + [InlineData(false, false)] + internal void TryMoveDirectoryRenameSuccessTests(bool createdirectory, bool expected) + { + var oldPath = TryDeleteDirectoryTests.CreateDirectoryPath(createdirectory); + var newPath = TryDeleteDirectoryTests.CreateDirectoryPath(false); + + Assert.Equal(createdirectory, Directory.Exists(oldPath)); + Assert.Equal(expected, KotzUtilities.TryMoveDirectory(oldPath, newPath)); + + if (!createdirectory) + return; + + Assert.Equal(!createdirectory, Directory.Exists(oldPath)); + Assert.False(KotzUtilities.TryDeleteDirectory(oldPath)); + Assert.True(KotzUtilities.TryDeleteDirectory(newPath)); + Assert.False(Directory.Exists(newPath)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(false, false)] + internal void TryMoveDirectorySuccessTests(bool createDirectory, bool expected) + { + var oldPath = TryDeleteDirectoryTests.CreateDirectoryPath(createDirectory); + var newPath = Path.Join(TryDeleteDirectoryTests.CreateDirectoryPath(false), Path.GetFileName(oldPath)); + + Assert.Equal(createDirectory, Directory.Exists(oldPath)); + Assert.Equal(expected, KotzUtilities.TryMoveDirectory(oldPath, newPath)); + + if (!createDirectory) + return; + + Assert.Equal(!createDirectory, Directory.Exists(oldPath)); + Assert.False(KotzUtilities.TryDeleteDirectory(oldPath)); + Assert.True(KotzUtilities.TryDeleteDirectory(newPath)); + Assert.False(File.Exists(newPath)); + } + + [Fact] + internal void TryMoveDirectoryVolumeTest() + { + // Define the directory tree + var oldRootDirPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.GetRandomFileName()); // Root directory + var oldDir1Path = Path.Join(oldRootDirPath, Path.GetRandomFileName()); // In: oldRootDirPath > oldDir1Path + var oldFile1Path = Path.Join(oldRootDirPath, Path.GetRandomFileName() + ".tmp"); // Inside: oldRootDirPath + var oldDir2Path = Path.Join(oldRootDirPath, Path.GetRandomFileName()); // In: oldRootDirPath > oldDir2Path + var oldFile2Path = Path.Join(oldDir2Path, Path.GetRandomFileName() + ".tmp"); // Inside: oldRootDirPath > oldDir2Path + + // Create the directory tree + Directory.CreateDirectory(oldDir1Path); + Directory.CreateDirectory(oldDir2Path); + File.Create(oldFile1Path).Dispose(); + File.Create(oldFile2Path).Dispose(); + + // Move to a different volume (ie. "/tmp") + var newRootDirPath = Path.Join(Path.GetTempPath(), Path.GetFileName(oldRootDirPath)); + Assert.True(KotzUtilities.TryMoveDirectory(oldRootDirPath, newRootDirPath)); + + // Check if move was successful + Assert.False(Directory.Exists(oldRootDirPath)); + Assert.True(Directory.Exists(newRootDirPath)); + Assert.True(Directory.Exists(Path.Join(newRootDirPath, Path.GetFileName(oldDir1Path)))); + Assert.True(Directory.Exists(Path.Join(newRootDirPath, Path.GetFileName(oldDir2Path)))); + Assert.True(File.Exists(Path.Join(newRootDirPath, Path.GetFileName(oldFile1Path)))); + Assert.True(File.Exists(Path.Join(newRootDirPath, Path.GetFileName(oldDir2Path), Path.GetFileName(oldFile2Path)))); + + // Cleanup + Directory.Delete(newRootDirPath, true); + Assert.False(Directory.Exists(newRootDirPath)); + } + + [Fact] + internal void TryMoveDirectoryFailTests() + { + Assert.Throws(() => KotzUtilities.TryMoveDirectory(string.Empty, "not empty")); + Assert.Throws(() => KotzUtilities.TryMoveDirectory("not empty", string.Empty)); + Assert.Throws(() => KotzUtilities.TryMoveDirectory(null!, "not empty")); + Assert.Throws(() => KotzUtilities.TryMoveDirectory("not empty", null!)); + } +} \ No newline at end of file diff --git a/Kotz.Tests/Extensions/Utilities/TryMoveFileTests.cs b/Kotz.Tests/Extensions/Utilities/TryMoveFileTests.cs new file mode 100644 index 0000000..87ce36e --- /dev/null +++ b/Kotz.Tests/Extensions/Utilities/TryMoveFileTests.cs @@ -0,0 +1,53 @@ +namespace Kotz.Tests.Extensions.Utilities; + +public sealed class TryMoveFileTests +{ + [Theory] + [InlineData(true, true)] + [InlineData(false, false)] + internal void TryMoveFileRenameSuccessTests(bool createFile, bool expected) + { + var oldPath = TryDeleteFileTests.CreateFilePath(createFile); + var newPath = TryDeleteFileTests.CreateFilePath(false); + + Assert.Equal(createFile, File.Exists(oldPath)); + Assert.Equal(expected, KotzUtilities.TryMoveFile(oldPath, newPath)); + + if (!createFile) + return; + + Assert.Equal(!createFile, File.Exists(oldPath)); + Assert.False(KotzUtilities.TryDeleteFile(oldPath)); + Assert.True(KotzUtilities.TryDeleteFile(newPath)); + Assert.False(File.Exists(newPath)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(false, false)] + internal void TryMoveFileSuccessTests(bool createFile, bool expected) + { + var oldPath = TryDeleteFileTests.CreateFilePath(createFile); + var newPath = Path.Join(TryDeleteDirectoryTests.CreateDirectoryPath(false), Path.GetFileName(oldPath)); + + Assert.Equal(createFile, File.Exists(oldPath)); + Assert.Equal(expected, KotzUtilities.TryMoveFile(oldPath, newPath)); + + if (!createFile) + return; + + Assert.Equal(!createFile, File.Exists(oldPath)); + Assert.False(KotzUtilities.TryDeleteFile(oldPath)); + Assert.True(KotzUtilities.TryDeleteFile(newPath)); + Assert.False(File.Exists(newPath)); + } + + [Fact] + internal void TryMoveFileFailTests() + { + Assert.Throws(() => KotzUtilities.TryMoveFile(string.Empty, "not empty")); + Assert.Throws(() => KotzUtilities.TryMoveFile("not empty", string.Empty)); + Assert.Throws(() => KotzUtilities.TryMoveFile(null!, "not empty")); + Assert.Throws(() => KotzUtilities.TryMoveFile("not empty", null!)); + } +} \ No newline at end of file