From d86df14d7912e84692dfdf719d808ac498fd5e57 Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Tue, 18 Feb 2025 16:45:47 +0100 Subject: [PATCH 1/5] refactor: introduce custom exception classes for improved error handling and user feedback --- CodeLineCounter.Tests/CodeAnalyzerTests.cs | 119 ++--- .../CodeDuplicationCheckerTests.cs | 143 ++--- CodeLineCounter.Tests/CoreUtilsTests.cs | 494 ++++++------------ CodeLineCounter.Tests/CsvHandlerTests.cs | 145 ++--- .../CyclomaticComplexityCalculatorTests.cs | 73 +-- CodeLineCounter.Tests/DataExporterTests.cs | 349 +++++-------- .../DependencyGraphGeneratorTests.cs | 204 +++----- CodeLineCounter.Tests/FileUtilsTests.cs | 120 ++--- CodeLineCounter.Tests/HashUtilsTests.cs | 88 +--- CodeLineCounter.Tests/JsonHandlerTests.cs | 67 +-- .../SolutionAnalyzerTests.cs | 274 ++++------ CodeLineCounter.Tests/TestBase.cs | 112 ++++ .../Exceptions/HelpRequestedException.cs | 13 + .../InvalidExportFormatException.cs | 13 + .../Exceptions/InvalidNumberException.cs | 12 + .../Exceptions/InvalidSelectionException.cs | 12 + CodeLineCounter/Models/Settings.cs | 10 +- CodeLineCounter/Program.cs | 57 +- .../Services/DependencyGraphGenerator.cs | 92 ++++ CodeLineCounter/Services/SolutionAnalyzer.cs | 10 +- CodeLineCounter/Utils/CoreUtils.cs | 30 +- 21 files changed, 1040 insertions(+), 1397 deletions(-) create mode 100644 CodeLineCounter.Tests/TestBase.cs create mode 100644 CodeLineCounter/Exceptions/HelpRequestedException.cs create mode 100644 CodeLineCounter/Exceptions/InvalidExportFormatException.cs create mode 100644 CodeLineCounter/Exceptions/InvalidNumberException.cs create mode 100644 CodeLineCounter/Exceptions/InvalidSelectionException.cs diff --git a/CodeLineCounter.Tests/CodeAnalyzerTests.cs b/CodeLineCounter.Tests/CodeAnalyzerTests.cs index 239393e..e500a3c 100644 --- a/CodeLineCounter.Tests/CodeAnalyzerTests.cs +++ b/CodeLineCounter.Tests/CodeAnalyzerTests.cs @@ -4,123 +4,92 @@ namespace CodeLineCounter.Tests { - public class CodeAnalyzerTests + public class CodeAnalyzerTests : TestBase { - private readonly TextWriter _originalConsoleOut; - - public CodeAnalyzerTests() - { - _originalConsoleOut = Console.Out; - } - [Fact] public void TestAnalyzeSolution() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - - string basePath = FileUtils.GetBasePath(); - var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..", "CodeLineCounter.sln")); - - // Act - var (metrics, projectTotals, totalLines, totalFiles, duplicationMap, dependencies) = CodeMetricsAnalyzer.AnalyzeSolution(solutionPath); - - // Assert - Assert.NotNull(metrics); - Assert.NotEmpty(metrics); - Assert.NotEmpty(projectTotals); - Assert.NotEqual(0, totalLines); - Assert.NotEqual(0, totalFiles); - Assert.NotNull(duplicationMap); - Assert.NotNull(dependencies); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + + string basePath = FileUtils.GetBasePath(); + var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..", "CodeLineCounter.sln")); + + // Act + var (metrics, projectTotals, totalLines, totalFiles, duplicationMap, dependencies) = CodeMetricsAnalyzer.AnalyzeSolution(solutionPath); + + // Assert + Assert.NotNull(metrics); + Assert.NotEmpty(metrics); + Assert.NotEmpty(projectTotals); + Assert.NotEqual(0, totalLines); + Assert.NotEqual(0, totalFiles); + Assert.NotNull(duplicationMap); + Assert.NotNull(dependencies); + } [Fact] public void AnalyzeSourceCode_Should_Set_CurrentNamespace() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var projectNamespaceMetrics = new Dictionary(); - var lines = new string[] - { + // Arrange + var projectNamespaceMetrics = new Dictionary(); + var lines = new string[] + { "namespace MyNamespace", "{", " // Code goes here", "}" - }; - - // Act - CodeMetricsAnalyzer.AnalyzeSourceCode(projectNamespaceMetrics, lines, out string? currentNamespace, out _, out _); + }; - // Assert - Assert.Equal("MyNamespace", currentNamespace); + // Act + CodeMetricsAnalyzer.AnalyzeSourceCode(projectNamespaceMetrics, lines, out string? currentNamespace, out _, out _); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.Equal("MyNamespace", currentNamespace); } [Fact] public void AnalyzeSourceCode_Should_Set_FileLineCount() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var projectNamespaceMetrics = new Dictionary(); - var lines = new string[] - { + // Arrange + var projectNamespaceMetrics = new Dictionary(); + var lines = new string[] + { "namespace MyNamespace", "{", " // Code goes here", "}" - }; + }; - // Act - CodeMetricsAnalyzer.AnalyzeSourceCode(projectNamespaceMetrics, lines, out _, out int fileLineCount, out _); + // Act + CodeMetricsAnalyzer.AnalyzeSourceCode(projectNamespaceMetrics, lines, out _, out int fileLineCount, out _); - // Assert - 3 lines only because comment lines are ignored - Assert.Equal(3, fileLineCount); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert - 3 lines only because comment lines are ignored + Assert.Equal(3, fileLineCount); } [Fact] public void AnalyzeSourceCode_Should_Set_FileCyclomaticComplexity() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var projectNamespaceMetrics = new Dictionary(); - var lines = new string[] - { + // Arrange + var projectNamespaceMetrics = new Dictionary(); + var lines = new string[] + { "namespace MyNamespace", "{", " // Code goes here", "}" - }; + }; - // Act - CodeMetricsAnalyzer.AnalyzeSourceCode(projectNamespaceMetrics, lines, out _, out _, out int fileCyclomaticComplexity); + // Act + CodeMetricsAnalyzer.AnalyzeSourceCode(projectNamespaceMetrics, lines, out _, out _, out int fileCyclomaticComplexity); - // Assert - Assert.Equal(1, fileCyclomaticComplexity); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.Equal(1, fileCyclomaticComplexity); } diff --git a/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs index c602d81..16b6f5e 100644 --- a/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs +++ b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs @@ -2,32 +2,25 @@ namespace CodeLineCounter.Tests { - public class CodeDuplicationCheckerTests : IDisposable + public class CodeDuplicationCheckerTests : TestBase { private readonly string _testDirectory; - private bool _disposed; - - private readonly TextWriter _originalConsoleOut; public CodeDuplicationCheckerTests() { _testDirectory = Path.Combine(Path.GetTempPath(), "CodeDuplicationCheckerTests"); Directory.CreateDirectory(_testDirectory); - _originalConsoleOut = Console.Out; } [Fact] public void DetectCodeDuplicationInFiles_ShouldDetectDuplicates() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var file1 = Path.Combine(_testDirectory, "TestFile1.cs"); - var file2 = Path.Combine(_testDirectory, "TestFile2.cs"); + // Arrange + var file1 = Path.Combine(_testDirectory, "TestFile1.cs"); + var file2 = Path.Combine(_testDirectory, "TestFile2.cs"); - var code1 = @" + var code1 = @" public class TestClass { public void TestMethod() @@ -39,7 +32,7 @@ public void TestMethod() } }"; - var code2 = @" + var code2 = @" public class AnotherTestClass { public void AnotherTestMethod() @@ -51,40 +44,35 @@ public void AnotherTestMethod() } }"; - File.WriteAllText(file1, code1); - File.WriteAllText(file2, code2); + File.WriteAllText(file1, code1); + File.WriteAllText(file2, code2); - var files = new List { file1, file2 }; - var checker = new CodeDuplicationChecker(); + var files = new List { file1, file2 }; + var checker = new CodeDuplicationChecker(); - // Act - checker.DetectCodeDuplicationInFiles(files); - var result = checker.GetCodeDuplicationMap(); + // Act + checker.DetectCodeDuplicationInFiles(files); + var result = checker.GetCodeDuplicationMap(); - // Assert - Assert.NotEmpty(result); - var duplicateEntry = result.First(); - Assert.Equal(2, duplicateEntry.Value.Count); // Both files should be detected as duplicates + // Assert + Assert.NotEmpty(result); + var duplicateEntry = result.First(); + Assert.Equal(2, duplicateEntry.Value.Count); // Both files should be detected as duplicates + + // Clean up + File.Delete(file1); + File.Delete(file2); - // Clean up - File.Delete(file1); - File.Delete(file2); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] public void DetectCodeDuplicationInSourceCode_ShouldDetectDuplicates() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var checker = new CodeDuplicationChecker(); + // Arrange + var checker = new CodeDuplicationChecker(); - var sourceCode1 = @" + var sourceCode1 = @" public class TestClass { public void TestMethod() @@ -96,7 +84,7 @@ public void TestMethod() } }"; - var sourceCode2 = @" + var sourceCode2 = @" public class AnotherTestClass { public void AnotherTestMethod() @@ -108,35 +96,30 @@ public void AnotherTestMethod() } }"; - var file1 = Path.Combine(_testDirectory, "TestFile3.cs"); - var file2 = Path.Combine(_testDirectory, "TestFile4.cs"); + var file1 = Path.Combine(_testDirectory, "TestFile3.cs"); + var file2 = Path.Combine(_testDirectory, "TestFile4.cs"); - // Act - checker.DetectCodeDuplicationInSourceCode(file1, sourceCode1); - checker.DetectCodeDuplicationInSourceCode(file2, sourceCode2); - var result = checker.GetCodeDuplicationMap(); + // Act + checker.DetectCodeDuplicationInSourceCode(file1, sourceCode1); + checker.DetectCodeDuplicationInSourceCode(file2, sourceCode2); + var result = checker.GetCodeDuplicationMap(); + + // Assert + Assert.NotEmpty(result); + var duplicateEntry = result.First(); + Assert.Equal(2, duplicateEntry.Value.Count); // Both methods should be detected as duplicates - // Assert - Assert.NotEmpty(result); - var duplicateEntry = result.First(); - Assert.Equal(2, duplicateEntry.Value.Count); // Both methods should be detected as duplicates - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] public void DetectCodeDuplicationInSourceCode_ShouldNotDetectDuplicatesForDifferentCode() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var checker = new CodeDuplicationChecker(); + // Arrange + var checker = new CodeDuplicationChecker(); - var sourceCode1 = @" + var sourceCode1 = @" public class TestClass { public void TestMethod() @@ -148,7 +131,7 @@ public void TestMethod() } }"; - var sourceCode2 = @" + var sourceCode2 = @" public class AnotherTestClass { public void AnotherTestMethod() @@ -157,47 +140,31 @@ public void AnotherTestMethod() } }"; - var file1 = Path.Combine(_testDirectory, "TestFile5.cs"); - var file2 = Path.Combine(_testDirectory, "TestFile6.cs"); + var file1 = Path.Combine(_testDirectory, "TestFile5.cs"); + var file2 = Path.Combine(_testDirectory, "TestFile6.cs"); - // Act - checker.DetectCodeDuplicationInSourceCode(file1, sourceCode1); - checker.DetectCodeDuplicationInSourceCode(file2, sourceCode2); - var result = checker.GetCodeDuplicationMap(); + // Act + checker.DetectCodeDuplicationInSourceCode(file1, sourceCode1); + checker.DetectCodeDuplicationInSourceCode(file2, sourceCode2); + var result = checker.GetCodeDuplicationMap(); + + // Assert + Assert.Empty(result); // No duplicates should be detected - // Assert - Assert.Empty(result); // No duplicates should be detected - } - // Reset console output - Console.SetOut(_originalConsoleOut); } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!_disposed) + if (disposing && Directory.Exists(_testDirectory)) { - if (disposing && Directory.Exists(_testDirectory)) - { - // Dispose managed resources - Directory.Delete(_testDirectory, true); - } - - // Dispose unmanaged resources (if any) - - _disposed = true; + // Dispose managed resources + Directory.Delete(_testDirectory, true); } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Dispose unmanaged resources (if any) + base.Dispose(disposing); - ~CodeDuplicationCheckerTests() - { - Dispose(false); } } } diff --git a/CodeLineCounter.Tests/CoreUtilsTests.cs b/CodeLineCounter.Tests/CoreUtilsTests.cs index aa97bc6..1cb9b90 100644 --- a/CodeLineCounter.Tests/CoreUtilsTests.cs +++ b/CodeLineCounter.Tests/CoreUtilsTests.cs @@ -1,40 +1,32 @@ using CodeLineCounter.Utils; using CodeLineCounter.Models; +using CodeLineCounter.Exceptions; namespace CodeLineCounter.Tests { - public class CoreUtilsTests : IDisposable + public class CoreUtilsTests : TestBase { private readonly string _testDirectory; - private bool _disposed; - - private readonly TextWriter _originalConsoleOut; public CoreUtilsTests() { _testDirectory = Path.Combine(Path.GetTempPath(), "CoreUtilsTests"); Directory.CreateDirectory(_testDirectory); - _originalConsoleOut = Console.Out; } [Fact] public void ParseArguments_Should_Return_Correct_Values() { // Arrange string[] args = ["-verbose", "-d", "testDirectory", "-output", _testDirectory]; - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Act - Settings settings = CoreUtils.ParseArguments(args); + // Act + Settings settings = CoreUtils.ParseArguments(args); + + // Assert + Assert.True(settings.Verbose); + Assert.Equal("testDirectory", settings.DirectoryPath); - // Assert - Assert.True(settings.Verbose); - Assert.Equal("testDirectory", settings.DirectoryPath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] @@ -42,18 +34,13 @@ public void ParseArguments_help_Should_Return_Correct_Values() { // Arrange string[] args = ["-help"]; - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Act - var settings = CoreUtils.ParseArguments(args); + // Act + var settings = CoreUtils.ParseArguments(args); + + // Assert + Assert.True(settings.Help); - // Assert - Assert.True(settings.Help); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] @@ -61,19 +48,14 @@ public void ParseArguments_Should_Return_Default_Values_When_No_Arguments_Passed { // Arrange string[] args = []; - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Act - var settings = CoreUtils.ParseArguments(args); + // Act + var settings = CoreUtils.ParseArguments(args); + + // Assert + Assert.False(settings.Verbose); + Assert.Null(settings.DirectoryPath); - // Assert - Assert.False(settings.Verbose); - Assert.Null(settings.DirectoryPath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] @@ -81,19 +63,14 @@ public void ParseArguments_Should_Ignore_Invalid_Arguments() { // Arrange string[] args = ["-invalid", "-d", "testDirectory", "-f", "json"]; - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Act - var settings = CoreUtils.ParseArguments(args); + // Act + var settings = CoreUtils.ParseArguments(args); + + // Assert + Assert.False(settings.Verbose); + Assert.Equal("testDirectory", settings.DirectoryPath); - // Assert - Assert.False(settings.Verbose); - Assert.Equal("testDirectory", settings.DirectoryPath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } // ParseArguments correctly processes valid command line arguments with all options @@ -102,21 +79,17 @@ public void ParseArguments_processes_valid_arguments_with_all_options() { // Arrange string[] args = new[] { "-verbose", "-d", "C:/test", "-format", "JSON", "-help" }; - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Act - var result = CoreUtils.ParseArguments(args); - // Assert - Assert.True(result.Verbose); - Assert.Equal("C:/test", result.DirectoryPath); - Assert.True(result.Help); - Assert.Equal(CoreUtils.ExportFormat.JSON, result.Format); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act + var result = CoreUtils.ParseArguments(args); + + // Assert + Assert.True(result.Verbose); + Assert.Equal("C:/test", result.DirectoryPath); + Assert.True(result.Help); + Assert.Equal(CoreUtils.ExportFormat.JSON, result.Format); + } // ParseArguments handles empty or null argument array @@ -125,22 +98,15 @@ public void ParseArguments_handles_empty_argument_array() { // Arrange string[] emptyArgs = Array.Empty(); - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - - // Act - var result = CoreUtils.ParseArguments(emptyArgs); - // Assert - Assert.False(result.Verbose); - Assert.Null(result.DirectoryPath); - Assert.False(result.Help); - Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act + var result = CoreUtils.ParseArguments(emptyArgs); + // Assert + Assert.False(result.Verbose); + Assert.Null(result.DirectoryPath); + Assert.False(result.Help); + Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); } @@ -151,19 +117,9 @@ public void ParseArguments_handles_invalid_format_option() // Arrange string[] args = new[] { "-format", "INVALID" }; Settings result; - string sortieConsole; + // Act & Assert + Assert.Throws(() => result = CoreUtils.ParseArguments(args)); - // Act - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - result = CoreUtils.ParseArguments(args); - sortieConsole = sw.ToString(); - Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); - Assert.Contains("Invalid format", sortieConsole); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] @@ -172,22 +128,11 @@ public void GetUserChoice_Should_Return_Valid_Choice() // Arrange int solutionCount = 5; string input = "3"; - using (var inputStream = new StringReader(input)) - { - using (var consoleOutput = new StringWriter()) - { - Console.SetOut(consoleOutput); - Console.SetIn(inputStream); - - // Act - int result = CoreUtils.GetUserChoice(solutionCount); - // Assert - Assert.Equal(3, result); - } - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act + int result = CoreUtils.CheckInputUserChoice(input, solutionCount); + // Assert + Assert.Equal(3, result); } [Fact] @@ -196,22 +141,11 @@ public void GetUserChoice_With_Invalid_Input_Should_Return_Valid_Choice() // Arrange int solutionCount = 5; string input = "6"; - using (var inputStream = new StringReader(input)) - { - using (var consoleOutput = new StringWriter()) - { - Console.SetOut(consoleOutput); - Console.SetIn(inputStream); + int result = -1; + // Act & Assert + Assert.Throws(() => result = CoreUtils.CheckInputUserChoice(input, solutionCount)); + Assert.Equal(-1, result); - // Act - int result = CoreUtils.GetUserChoice(solutionCount); - - // Assert - Assert.Equal(-1, result); - } - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] @@ -220,24 +154,11 @@ public void GetUserChoice_Should_Return_Invalid_Choice() // Arrange int solutionCount = 5; string input = "invalid"; - using (var inputStream = new StringReader(input)) - { - using (var consoleOutput = new StringWriter()) - { - Console.SetOut(consoleOutput); - Console.SetIn(inputStream); - - // Act - int result = CoreUtils.GetUserChoice(solutionCount); - - // Assert - Assert.Equal(-1, result); - } - - } - // Reset console output - Console.SetOut(_originalConsoleOut); - + int result = -1; + // Act + Assert.Throws(() => result = CoreUtils.CheckInputUserChoice(input, solutionCount)); + // Assert + Assert.Equal(-1, result); } // GetUserChoice returns valid selection when input is within range @@ -245,24 +166,12 @@ public void GetUserChoice_Should_Return_Invalid_Choice() public void GetUserChoice_returns_valid_selection_for_valid_input() { // Arrange - var input = "2"; - using (var consoleInput = new StringReader(input)) - { - using (var consoleOutput = new StringWriter()) - { - Console.SetOut(consoleOutput); - Console.SetIn(consoleInput); - - // Act - int result = CoreUtils.GetUserChoice(3); - - // Assert - Assert.Equal(2, result); - } + string input = "2"; + // Act + int result = CoreUtils.CheckInputUserChoice(input, 3); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.Equal(2, result); } @@ -273,22 +182,18 @@ public void GetUserChoice_returns_valid_selection_for_valid_input() public void GetUserChoice_handles_invalid_input(string input) { // Arrange - using (var consoleInput = new StringReader(input)) + var exceptions = new List() { - using (var consoleOutput = new StringWriter()) - { - Console.SetOut(consoleOutput); - Console.SetIn(consoleInput); + typeof(ArgumentNullException), + typeof(InvalidNumberException), + typeof(InvalidOperationException), + }; - // Act - int result = CoreUtils.GetUserChoice(5); + var ex = Record.Exception(() => CoreUtils.CheckInputUserChoice(input, 5)); + + Assert.NotNull(ex); + Assert.Contains(ex.GetType(), exceptions); - // Assert - Assert.Equal(-1, result); - } - } - // Reset console output - Console.SetOut(_originalConsoleOut); } @@ -305,78 +210,57 @@ public void DisplaySolutions_Should_Write_Solutions_To_Console() "Solution3.sln" ]; - // Redirect console output to a StringWriter - using (StringWriter sw = new()) + // Act + initialization(); + RedirectConsoleInputOutput(); + CoreUtils.DisplaySolutions(solutionFiles); + + + // Assert + string expectedOutput = $"Available solutions:{envNewLine}"; + for (int i = 0; i < solutionFiles.Count; i++) { - Console.SetOut(sw); - - // Act - CoreUtils.DisplaySolutions(solutionFiles); - - // Assert - string expectedOutput = $"Available solutions:{envNewLine}"; - for (int i = 0; i < solutionFiles.Count; i++) - { - expectedOutput += $"{i + 1}. {solutionFiles[i]}{envNewLine}"; - } - Assert.Equal(expectedOutput, sw.ToString()); + expectedOutput += $"{i + 1}. {solutionFiles[i]}{envNewLine}"; } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Equal(expectedOutput, GetConsoleOutput()); + ResetConsoleInputOutput(); } [Fact] public void GetFilenamesList_Should_Return_List_Of_Filenames() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - List solutionFiles = - [ - "Solution1.sln", + // Arrange + List solutionFiles = + [ + "Solution1.sln", "Solution2.sln", "Solution3.sln" - ]; + ]; - // Act - List result = CoreUtils.GetFilenamesList(solutionFiles); + // Act + List result = CoreUtils.GetFilenamesList(solutionFiles); - // Assert - List expectedFilenames = - [ - "Solution1.sln", + // Assert + List expectedFilenames = + [ + "Solution1.sln", "Solution2.sln", "Solution3.sln" - ]; - Assert.Equal(expectedFilenames, result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + ]; + Assert.Equal(expectedFilenames, result); } [Fact] - public void CheckSettings_WhenHelpIsTrue_ReturnsFalse() + public void CheckSettings_WhenHelpIsTrue_ReturnsHelpRequestedException() { // Arrange Settings settings = new Settings(true, null, ".", true, CoreUtils.ExportFormat.JSON); - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - - // Act - var result = settings.IsValid(); - - // Assert - Assert.True(result); - Assert.Contains("Usage:", sw.ToString()); - } - - // Reset console output - Console.SetOut(_originalConsoleOut); + bool result = false; + // Act & Assert + Assert.Throws(() => result = settings.IsValid()); } @@ -385,61 +269,40 @@ public void CheckSettings_WhenDirectoryPathIsNull_ReturnsFalse() { // Arrange Settings settings = new Settings(verbose: false, directoryPath: null, help: false, format: CoreUtils.ExportFormat.CSV); - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - - // Act - var result = settings.IsValid(); - - // Assert - Assert.False(result); - Assert.Contains("Please provide the directory path", sw.ToString()); - } + bool result = false; + // Act + Assert.Throws(() => result = settings.IsValid()); - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.False(result); } [Fact] public void CheckSettings_WhenSettingsAreValid_ReturnsTrue() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - Settings settings = new Settings(false, "some_directory", false, CoreUtils.ExportFormat.CSV); - - // Act - var result = settings.IsValid(); + // Arrange + Settings settings = new Settings(false, "some_directory", false, CoreUtils.ExportFormat.CSV); - // Assert - Assert.True(result); - } + // Act + var result = settings.IsValid(); - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.True(result); } [Fact] public void CheckSettings_WhenSettingsOutputIsInValid_ReturnsFalse() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - Settings settings = new Settings(false, "some_directory", "invalid_output_path", false, CoreUtils.ExportFormat.CSV); - - // Act - var result = settings.IsValid(); + // Arrange + Settings settings = new Settings(false, "some_directory", "invalid_output_path", false, CoreUtils.ExportFormat.CSV); + bool result = false; - // Assert - Assert.True(result); - } + // Act + result = settings.IsValid(); - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.True(result); } @@ -448,18 +311,13 @@ public void CheckSettings_WhenSettingsAreInvalid_ReturnsFalse() { // Arrange Settings settings = new Settings(false, null, false, CoreUtils.ExportFormat.CSV); - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Act - var result = settings.IsValid(); + bool result = false; + // Act + Assert.Throws(() => result = settings.IsValid()); - // Assert - Assert.False(result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.False(result); } @@ -471,19 +329,14 @@ public void CheckSettings_WhenSettingsAreInvalid_ReturnsFalse() [InlineData("metrics_789.csv", CoreUtils.ExportFormat.CSV)] public void get_export_file_name_with_extension_handles_alphanumeric(string fileName, CoreUtils.ExportFormat format) { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Act - var fullFileName = Path.Combine(_testDirectory, fileName); - var result = CoreUtils.GetExportFileNameWithExtension(fullFileName, format); - - // Assert - Assert.Contains(Path.GetFileNameWithoutExtension(fullFileName), result); - Assert.True(File.Exists(result) || !File.Exists(result)); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act + var fullFileName = Path.Combine(_testDirectory, fileName); + var result = CoreUtils.GetExportFileNameWithExtension(fullFileName, format); + + // Assert + Assert.Contains(Path.GetFileNameWithoutExtension(fullFileName), result); + Assert.True(File.Exists(result) || !File.Exists(result)); + } @@ -491,33 +344,28 @@ public void get_export_file_name_with_extension_handles_alphanumeric(string file [Fact] public void get_filenames_list_returns_filenames_for_existing_files() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var testFiles = new List + + // Arrange + var testFiles = new List { Path.Combine(_testDirectory, "file1.txt"), Path.Combine(_testDirectory, "file2.txt") }; - File.WriteAllText(testFiles[0], "test content"); - File.WriteAllText(testFiles[1], "test content"); + File.WriteAllText(testFiles[0], "test content"); + File.WriteAllText(testFiles[1], "test content"); - // Act - var result = CoreUtils.GetFilenamesList(testFiles); + // Act + var result = CoreUtils.GetFilenamesList(testFiles); - // Assert - Assert.Equal(2, result.Count); - Assert.Equal("file1.txt", result[0]); - Assert.Equal("file2.txt", result[1]); + // Assert + Assert.Equal(2, result.Count); + Assert.Equal("file1.txt", result[0]); + Assert.Equal("file2.txt", result[1]); - // Cleanup - File.Delete(testFiles[0]); - File.Delete(testFiles[1]); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Cleanup + File.Delete(testFiles[0]); + File.Delete(testFiles[1]); } @@ -525,54 +373,34 @@ public void get_filenames_list_returns_filenames_for_existing_files() [Fact] public void create_directory_succeeds_with_valid_path() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var settings = new Settings(); - settings.DirectoryPath = _testDirectory; - settings.OutputPath = Path.Combine(_testDirectory, "TestOutput"); - var result = settings.IsValid(); + var settings = new Settings(); + settings.DirectoryPath = _testDirectory; + settings.OutputPath = Path.Combine(_testDirectory, "TestOutput"); - Assert.True(result); - Assert.True(Directory.Exists(settings.OutputPath)); - Directory.Delete(settings.OutputPath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + var result = settings.IsValid(); + + Assert.True(result); + Assert.True(Directory.Exists(settings.OutputPath)); + Directory.Delete(settings.OutputPath); } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!_disposed) - { - - if (disposing) - { - Task.Delay(100).Wait(); - if (Directory.Exists(_testDirectory)) - { - // Dispose managed resources - Directory.Delete(_testDirectory, true); - } - } - // Dispose unmanaged resources (if any) - _disposed = true; + if (Directory.Exists(_testDirectory) && disposing) + { + // Dispose managed resources + Directory.Delete(_testDirectory, true); } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - ~CoreUtilsTests() - { - Dispose(false); + // Dispose unmanaged resources (if any) + base.Dispose(disposing); + } + } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/CsvHandlerTests.cs b/CodeLineCounter.Tests/CsvHandlerTests.cs index 8e25eb6..cdacceb 100644 --- a/CodeLineCounter.Tests/CsvHandlerTests.cs +++ b/CodeLineCounter.Tests/CsvHandlerTests.cs @@ -2,12 +2,10 @@ namespace CodeLineCounter.Tests { - public class CsvHandlerTests : IDisposable + public class CsvHandlerTests : TestBase { private readonly string _testDirectory; - private bool _disposed; - private readonly TextWriter _originalConsoleOut; private class TestRecord { @@ -19,150 +17,107 @@ public CsvHandlerTests() { _testDirectory = Path.Combine(Path.GetTempPath(), "CsvHandlerTests"); Directory.CreateDirectory(_testDirectory); - _originalConsoleOut = Console.Out; } [Fact] public void Serialize_ValidData_WritesToFile() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var data = new List + // Arrange + var data = new List { new() { Id = 1, Name = "Alice" }, new() { Id = 2, Name = "Bob" } }; - string filePath = Path.Combine(_testDirectory, "test_1.csv"); - - // Act - CsvHandler.Serialize(data, filePath); + string filePath = Path.Combine(_testDirectory, "test_1.csv"); - // Assert - var lines = File.ReadAllLines(filePath); - Assert.Equal(3, lines.Length); // Header + 2 records - Assert.Contains("Alice", lines[1]); - Assert.Contains("Bob", lines[2]); + // Act + CsvHandler.Serialize(data, filePath); - // Cleanup - File.Delete(filePath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + var lines = File.ReadAllLines(filePath); + Assert.Equal(3, lines.Length); // Header + 2 records + Assert.Contains("Alice", lines[1]); + Assert.Contains("Bob", lines[2]); + // Cleanup + File.Delete(filePath); } [Fact] public void Deserialize_ValidFile_ReturnsData() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - string filePath = Path.Combine(_testDirectory, "test_2.csv"); - var data = new List + // Arrange + string filePath = Path.Combine(_testDirectory, "test_2.csv"); + var data = new List { "Id,Name", "1,Alice", "2,Bob" }; - File.WriteAllLines(filePath, data); + File.WriteAllLines(filePath, data); - // Act - var result = CsvHandler.Deserialize(filePath).ToList(); + // Act + var result = CsvHandler.Deserialize(filePath).ToList(); - // Assert - Assert.Equal(2, result.Count); - Assert.Equal("Alice", result[0].Name); - Assert.Equal("Bob", result[1].Name); + // Assert + Assert.Equal(2, result.Count); + Assert.Equal("Alice", result[0].Name); + Assert.Equal("Bob", result[1].Name); - // Cleanup - File.Delete(filePath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Cleanup + File.Delete(filePath); } [Fact] public void Serialize_EmptyData_WritesEmptyFile() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var data = new List(); - string filePath = Path.Combine(_testDirectory, "test_3.csv"); + // Arrange + var data = new List(); + string filePath = Path.Combine(_testDirectory, "test_3.csv"); - // Act - CsvHandler.Serialize(data, filePath); + // Act + CsvHandler.Serialize(data, filePath); - // Assert - var lines = File.ReadAllLines(filePath); - Assert.Single(lines); // Only header + // Assert + var lines = File.ReadAllLines(filePath); + Assert.Single(lines); // Only header - // Cleanup - File.Delete(filePath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Cleanup + File.Delete(filePath); } [Fact] public void Deserialize_EmptyFile_ReturnsEmptyList() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); + // Arrange + string filePath = Path.Combine(_testDirectory, "test_4.csv"); + File.WriteAllText(filePath, "Id,Name"); - // Arrange - string filePath = Path.Combine(_testDirectory, "test_4.csv"); - File.WriteAllText(filePath, "Id,Name"); + // Act + var result = CsvHandler.Deserialize(filePath).ToList(); - // Act - var result = CsvHandler.Deserialize(filePath).ToList(); - - // Assert - Assert.Empty(result); - - // Cleanup - File.Delete(filePath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.Empty(result); + // Cleanup + File.Delete(filePath); } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!_disposed) + if (disposing && Directory.Exists(_testDirectory)) { - if (disposing && Directory.Exists(_testDirectory)) - { - // Dispose managed resources - Directory.Delete(_testDirectory, true); - } - - // Dispose unmanaged resources (if any) - - _disposed = true; + // Dispose managed resources + Directory.Delete(_testDirectory, true); } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~CsvHandlerTests() - { - Dispose(false); + // Dispose unmanaged resources (if any) + base.Dispose(disposing); } } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs b/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs index 3d3608b..f251644 100644 --- a/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs +++ b/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs @@ -4,22 +4,13 @@ namespace CodeLineCounter.Tests { - public class CyclomaticComplexityCalculatorTests + public class CyclomaticComplexityCalculatorTests : TestBase { - private readonly TextWriter _originalConsoleOut; - - public CyclomaticComplexityCalculatorTests() - { - _originalConsoleOut = Console.Out; - } [Fact] public void TestCalculateComplexity() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var code = @" + // Arrange + var code = @" public class TestClass { public void TestMethod() @@ -29,27 +20,21 @@ public void TestMethod() } } "; - var tree = CSharpSyntaxTree.ParseText(code); + var tree = CSharpSyntaxTree.ParseText(code); - // Act - var complexity = CyclomaticComplexityCalculator.Calculate(tree.GetRoot()); + // Act + var complexity = CyclomaticComplexityCalculator.Calculate(tree.GetRoot()); - // Assert - Assert.Equal(3, complexity); // 1 (default) + 1 (if) + 1 (for) - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.Equal(3, complexity); // 1 (default) + 1 (if) + 1 (for) } [Fact] public void Calculate_Should_Return_Correct_Complexity() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var syntaxTree = CSharpSyntaxTree.ParseText(@" + // Arrange + var syntaxTree = CSharpSyntaxTree.ParseText(@" public class MyClass { public void MyMethod() @@ -65,28 +50,22 @@ public void MyMethod() } } "); - var root = syntaxTree.GetRoot(); - var methodDeclaration = root.DescendantNodes().OfType().First(); + var root = syntaxTree.GetRoot(); + var methodDeclaration = root.DescendantNodes().OfType().First(); - // Act - var complexity = CyclomaticComplexityCalculator.Calculate(methodDeclaration); + // Act + var complexity = CyclomaticComplexityCalculator.Calculate(methodDeclaration); - // Assert - Assert.Equal(3, complexity); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.Equal(3, complexity); } [Fact] public void Calculate_Should_Return_Correct_Complexity_6() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var syntaxTree = CSharpSyntaxTree.ParseText(@" + // Arrange + var syntaxTree = CSharpSyntaxTree.ParseText(@" class Program { static void Main(string[] args) @@ -111,18 +90,14 @@ static void Main(string[] args) } } "); - var root = syntaxTree.GetRoot(); - var methodDeclaration = root.DescendantNodes().OfType().First(); - - // Act - var complexity = CyclomaticComplexityCalculator.Calculate(methodDeclaration); + var root = syntaxTree.GetRoot(); + var methodDeclaration = root.DescendantNodes().OfType().First(); - // Assert - Assert.Equal(6, complexity); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act + var complexity = CyclomaticComplexityCalculator.Calculate(methodDeclaration); + // Assert + Assert.Equal(6, complexity); } } } diff --git a/CodeLineCounter.Tests/DataExporterTests.cs b/CodeLineCounter.Tests/DataExporterTests.cs index a311305..de49664 100644 --- a/CodeLineCounter.Tests/DataExporterTests.cs +++ b/CodeLineCounter.Tests/DataExporterTests.cs @@ -3,17 +3,20 @@ namespace CodeLineCounter.Tests { - public class DataExporterTests : IDisposable + public class DataExporterTests : TestBase { private readonly string _testDirectory; - private bool _disposed; - private readonly TextWriter _originalConsoleOut; + + private class TestClass + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + } public DataExporterTests() { _testDirectory = Path.Combine(Path.GetTempPath(), "DataExporterTests"); Directory.CreateDirectory(_testDirectory); - _originalConsoleOut = Console.Out; } [Theory] @@ -21,21 +24,15 @@ public DataExporterTests() [InlineData(CoreUtils.ExportFormat.JSON, ".json")] public void Export_SingleItem_CreatesFileWithCorrectExtension(CoreUtils.ExportFormat format, string expectedExtension) { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var testItem = new TestClass { Id = 1, Name = "Test" }; - var filePath = "test"; + // Arrange + var testItem = new TestClass { Id = 1, Name = "Test" }; + var filePath = "test"; - // Act - DataExporter.Export(filePath, _testDirectory, testItem, format); + // Act + DataExporter.Export(filePath, _testDirectory, testItem, format); - // Assert - Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + expectedExtension))); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + expectedExtension))); } @@ -44,100 +41,80 @@ public void Export_SingleItem_CreatesFileWithCorrectExtension(CoreUtils.ExportFo [InlineData(CoreUtils.ExportFormat.JSON, ".json")] public void ExportCollection_WithMultipleItems_CreatesFile(CoreUtils.ExportFormat format, string expectedExtension) { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var items = new List + // Arrange + var items = new List { new() { Id = 1, Name = "Test1" }, new() { Id = 2, Name = "Test2" } }; - var filePath = Path.Combine(_testDirectory, "collection"); + var filePath = Path.Combine(_testDirectory, "collection"); - // Act - DataExporter.ExportCollection(filePath, _testDirectory, items, format); + // Act + DataExporter.ExportCollection(filePath, _testDirectory, items, format); - // Assert - Assert.True(File.Exists(filePath + expectedExtension)); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.True(File.Exists(filePath + expectedExtension)); } [Fact] public void ExportMetrics_CreatesFileWithCorrectData() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var metrics = new List + + // Arrange + var metrics = new List { new() { ProjectName = "Project1", LineCount = 100 }, new() { ProjectName = "Project2", LineCount = 200 } }; - var projectTotals = new Dictionary + var projectTotals = new Dictionary { { "Project1", 100 }, { "Project2", 200 } }; - var duplications = new List(); - var filePath = "metrics"; - AnalysisResult result = new AnalysisResult - { - Metrics = metrics, - ProjectTotals = projectTotals, - DuplicationMap = duplications, - DependencyList = new List(), - TotalLines = 300, - SolutionFileName = "TestSolution" - }; + var duplications = new List(); + var filePath = "metrics"; + AnalysisResult result = new AnalysisResult + { + Metrics = metrics, + ProjectTotals = projectTotals, + DuplicationMap = duplications, + DependencyList = new List(), + TotalLines = 300, + SolutionFileName = "TestSolution" + }; - // Act - DataExporter.ExportMetrics(filePath, _testDirectory, result, ".", CoreUtils.ExportFormat.CSV); + // Act + DataExporter.ExportMetrics(filePath, _testDirectory, result, ".", CoreUtils.ExportFormat.CSV); - // Assert - Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + ".csv"))); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + ".csv"))); } [Fact] public void ExportDuplications_CreatesFileWithCorrectData() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var duplications = new List + // Arrange + var duplications = new List { new() { CodeHash = "hash1", FilePath = "file1.cs", MethodName = "method1", StartLine = 10, NbLines = 20 }, new() { CodeHash = "hash2", FilePath = "file2.cs", MethodName = "method2", StartLine = 8, NbLines = 10 } }; - var filePath = "duplications"; + var filePath = "duplications"; - // Act - DataExporter.ExportDuplications(filePath, _testDirectory, duplications, CoreUtils.ExportFormat.CSV); + // Act + DataExporter.ExportDuplications(filePath, _testDirectory, duplications, CoreUtils.ExportFormat.CSV); - // Assert - Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + ".csv"))); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + ".csv"))); } [Fact] public void get_duplication_counts_returns_correct_counts_for_duplicate_paths() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var duplications = new List + var duplications = new List { new DuplicationCode { FilePath = "file1.cs" }, new DuplicationCode { FilePath = "file1.cs" }, @@ -145,115 +122,82 @@ public void get_duplication_counts_returns_correct_counts_for_duplicate_paths() new DuplicationCode { FilePath = "file1.cs" } }; - var result = DataExporter.GetDuplicationCounts(duplications); + var result = DataExporter.GetDuplicationCounts(duplications); - Assert.Equal(3u, result[Path.GetFullPath("file1.cs")]); - Assert.Equal(1u, result[Path.GetFullPath("file2.cs")]); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Equal(3u, result[Path.GetFullPath("file1.cs")]); + Assert.Equal(1u, result[Path.GetFullPath("file2.cs")]); } [Fact] public void get_duplication_counts_returns_empty_dictionary_for_empty_list() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var duplications = new List(); + var duplications = new List(); - var result = DataExporter.GetDuplicationCounts(duplications); - - Assert.Empty(result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + var result = DataExporter.GetDuplicationCounts(duplications); + Assert.Empty(result); } [Fact] public void get_duplication_counts_handles_absolute_paths() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var absolutePath = Path.GetFullPath(@"C:\test\file.cs"); - var duplications = new List + var absolutePath = Path.GetFullPath(@"C:\test\file.cs"); + var duplications = new List { new DuplicationCode { FilePath = absolutePath }, new DuplicationCode { FilePath = absolutePath } }; - var result = DataExporter.GetDuplicationCounts(duplications); + var result = DataExporter.GetDuplicationCounts(duplications); + + Assert.Equal(2u, result[absolutePath]); - Assert.Equal(2u, result[absolutePath]); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] public void get_file_duplications_count_returns_correct_count_when_path_exists() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var duplicationCounts = new Dictionary + var duplicationCounts = new Dictionary { { Path.Combine(Path.GetFullPath("."), "test.cs"), 5 } }; - var metric = new NamespaceMetrics { FilePath = "test.cs" }; + var metric = new NamespaceMetrics { FilePath = "test.cs" }; - var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, null); + var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, null); - Assert.Equal(5, result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Equal(5, result); } [Fact] public void get_file_duplications_count_returns_zero_when_path_not_found() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var duplicationCounts = new Dictionary(); + var duplicationCounts = new Dictionary(); - var metric = new NamespaceMetrics { FilePath = "nonexistent.cs" }; + var metric = new NamespaceMetrics { FilePath = "nonexistent.cs" }; - var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, null); + var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, null); - Assert.Equal(0, result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Equal(0, result); } [Fact] public void get_file_duplications_count_returns_zero_when_filepath_null() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var duplicationCounts = new Dictionary + var duplicationCounts = new Dictionary { { Path.Combine(Path.GetFullPath("."), "test.cs"), 5 } }; - var metric = new NamespaceMetrics { FilePath = null }; + var metric = new NamespaceMetrics { FilePath = null }; - var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, null); + var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, null); - Assert.Equal(0, result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Equal(0, result); } @@ -261,23 +205,17 @@ public void get_file_duplications_count_returns_zero_when_filepath_null() [Fact] public void get_file_duplications_count_uses_current_dir_for_empty_solution_path() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var expectedPath = Path.Combine(Path.GetFullPath("."), "test.cs"); - var duplicationCounts = new Dictionary + var expectedPath = Path.Combine(Path.GetFullPath("."), "test.cs"); + var duplicationCounts = new Dictionary { { expectedPath, 3 } }; - var metric = new NamespaceMetrics { FilePath = "test.cs" }; + var metric = new NamespaceMetrics { FilePath = "test.cs" }; - var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, string.Empty); + var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, string.Empty); - Assert.Equal(3, result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Equal(3, result); } @@ -285,23 +223,17 @@ public void get_file_duplications_count_uses_current_dir_for_empty_solution_path [Fact] public void get_file_duplications_count_uses_current_dir_for_null_solution_path() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var expectedPath = Path.Combine(Path.GetFullPath("."), "test.cs"); - var duplicationCounts = new Dictionary + var expectedPath = Path.Combine(Path.GetFullPath("."), "test.cs"); + var duplicationCounts = new Dictionary { { expectedPath, 4 } }; - var metric = new NamespaceMetrics { FilePath = "test.cs" }; + var metric = new NamespaceMetrics { FilePath = "test.cs" }; - var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, null); + var result = DataExporter.GetFileDuplicationsCount(duplicationCounts, metric, null); - Assert.Equal(4, result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Equal(4, result); } @@ -309,109 +241,74 @@ public void get_file_duplications_count_uses_current_dir_for_null_solution_path( [Fact] public async Task export_dependencies_creates_files_in_correct_formats() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var dependencies = new List + // Arrange + var dependencies = new List { new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "file1.cs", StartLine = 10 }, }; - var testFilePath = "test_export.dot"; - var format = CoreUtils.ExportFormat.JSON; + var testFilePath = "test_export.dot"; + var format = CoreUtils.ExportFormat.JSON; - // Act - await DataExporter.ExportDependencies(testFilePath, _testDirectory, dependencies, format); + // Act + await DataExporter.ExportDependencies(testFilePath, _testDirectory, dependencies, format); - // Assert - string expectedJsonPath = Path.Combine(_testDirectory, CoreUtils.GetExportFileNameWithExtension(testFilePath, format)); - string expectedDotPath = Path.ChangeExtension(expectedJsonPath, ".dot"); + // Assert + string expectedJsonPath = Path.Combine(_testDirectory, CoreUtils.GetExportFileNameWithExtension(testFilePath, format)); + string expectedDotPath = Path.ChangeExtension(expectedJsonPath, ".dot"); - Assert.True(File.Exists(expectedJsonPath)); - Assert.True(File.Exists(expectedDotPath)); + Assert.True(File.Exists(expectedJsonPath)); + Assert.True(File.Exists(expectedDotPath)); - try - { - if (File.Exists(expectedJsonPath)) - { - File.Delete(expectedJsonPath); - } - - if (File.Exists(expectedDotPath)) - { - File.Delete(expectedDotPath); - } - } - catch (IOException ex) + try + { + if (File.Exists(expectedJsonPath)) { - throw new IOException($"Error deleting files: {ex.Message}", ex); + File.Delete(expectedJsonPath); } - catch (UnauthorizedAccessException ex) + + if (File.Exists(expectedDotPath)) { - throw new UnauthorizedAccessException($"Access denied while deleting files: {ex.Message}", ex); + File.Delete(expectedDotPath); } } - // Reset console output - Console.SetOut(_originalConsoleOut); - + catch (IOException ex) + { + throw new IOException($"Error deleting files: {ex.Message}", ex); + } + catch (UnauthorizedAccessException ex) + { + throw new UnauthorizedAccessException($"Access denied while deleting files: {ex.Message}", ex); + } } // Throws ArgumentException when file path is null [Fact] public void export_collection_throws_when_filepath_null() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - string? filePath = null; - var testData = new List { new TestClass { Id = 1, Name = "Test" } }; - var format = CoreUtils.ExportFormat.CSV; - - // Act & Assert - var exception = Assert.Throws(() => - DataExporter.ExportCollection(filePath, _testDirectory, testData, format)); - Assert.Contains("File path cannot be null or empty", exception.Message); - } - // Reset console output - Console.SetOut(_originalConsoleOut); - + // Arrange + string? filePath = null; + var testData = new List { new TestClass { Id = 1, Name = "Test" } }; + var format = CoreUtils.ExportFormat.CSV; + + // Act & Assert + var exception = Assert.Throws(() => + DataExporter.ExportCollection(filePath, _testDirectory, testData, format)); + Assert.Contains("File path cannot be null or empty", exception.Message); } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!_disposed) + if (disposing && Directory.Exists(_testDirectory)) { - if (disposing && Directory.Exists(_testDirectory)) - { - // Dispose managed resources - Directory.Delete(_testDirectory, true); - } - - // Dispose unmanaged resources (if any) - - _disposed = true; + // Dispose managed resources + Directory.Delete(_testDirectory, true); } - } - - private class TestClass - { - public int Id { get; set; } - public string Name { get; set; } = string.Empty; - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Dispose unmanaged resources (if any) + base.Dispose(disposing); - ~DataExporterTests() - { - Dispose(false); } - - } + } \ No newline at end of file diff --git a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs index c98e465..50fea03 100644 --- a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs +++ b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs @@ -6,139 +6,113 @@ namespace CodeLineCounter.Tests { - public class DependencyGraphGeneratorTests : IDisposable + public class DependencyGraphGeneratorTests : TestBase { private readonly string _testDirectory; - private bool _disposed; - private readonly TextWriter _originalConsoleOut; public DependencyGraphGeneratorTests() { _testDirectory = Path.Combine(Path.GetTempPath(), "dependencygraphgeneratortests"); Directory.CreateDirectory(_testDirectory); - _originalConsoleOut = Console.Out; } [Fact] public async Task generate_graph_with_valid_dependencies_creates_dot_file() { var testDirectory = _testDirectory; string fileName = "testgraph.dot"; - using (var sw = new StringWriter()) - { - // Arrange - var dependencies = new List + // Arrange + var dependencies = new List { new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB" , TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "path/to/file", StartLine = 1}, new DependencyRelation { SourceClass = "ClassB", SourceNamespace = "NamespaceB", SourceAssembly = "AssemblyB", TargetClass = "ClassC", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "path/to/file", StartLine = 1} }; - string outputPath = Path.Combine(testDirectory, fileName); - try - { - Console.SetOut(sw); - - // Act - DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); - Directory.CreateDirectory(testDirectory); - await DependencyGraphGenerator.CompileGraphAndWriteToFile(fileName, testDirectory, graph); - - // Assert - Assert.True(File.Exists(outputPath)); - string content = await File.ReadAllTextAsync(outputPath); - Assert.Contains("ClassA", content); - Assert.Contains("ClassB", content); - Assert.Contains("ClassC", content); - } - finally + string outputPath = Path.Combine(testDirectory, fileName); + try + { + + // Act + DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); + Directory.CreateDirectory(testDirectory); + await DependencyGraphGenerator.CompileGraphAndWriteToFile(fileName, testDirectory, graph); + + // Assert + Assert.True(File.Exists(outputPath)); + string content = await File.ReadAllTextAsync(outputPath); + Assert.Contains("ClassA", content); + Assert.Contains("ClassB", content); + Assert.Contains("ClassC", content); + } + finally + { + await Task.Delay(100); + if (File.Exists(outputPath)) { - await Task.Delay(100); - if (File.Exists(outputPath)) - { - File.Delete(outputPath); - } + File.Delete(outputPath); } } - // Reset console output - Console.SetOut(_originalConsoleOut); - } // Empty dependencies list [Fact] public async Task generate_graph_with_empty_dependencies_creates_empty_graph() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var dependencies = new List(); - string filename = "empty_graph.dot"; - string outputPath = Path.Combine(_testDirectory, filename); + // Arrange + var dependencies = new List(); + string filename = "empty_graph.dot"; + string outputPath = Path.Combine(_testDirectory, filename); - // Act - DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); - await DependencyGraphGenerator.CompileGraphAndWriteToFile(filename, _testDirectory, graph); + // Act + DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); + await DependencyGraphGenerator.CompileGraphAndWriteToFile(filename, _testDirectory, graph); - // Assert - Assert.True(File.Exists(outputPath)); - string content = await File.ReadAllTextAsync(outputPath); - Assert.Contains("digraph", content); - Assert.DoesNotContain("->", content); + // Assert + Assert.True(File.Exists(outputPath)); + string content = await File.ReadAllTextAsync(outputPath); + Assert.Contains("digraph", content); + Assert.DoesNotContain("->", content); - await Task.Delay(100); + await Task.Delay(100); - if (File.Exists(outputPath)) - { - File.Delete(outputPath); - } + if (File.Exists(outputPath)) + { + File.Delete(outputPath); } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] public void create_node_sets_correct_fillcolor_and_style_incoming_greater() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var vertexInfo = new Dictionary + + var vertexInfo = new Dictionary { { "TestVertex", (3, 2) } }; - DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex"); + DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex"); - Assert.Equal(DotColor.MediumSeaGreen.ToHexString(), node.FillColor.Value); - Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Equal(DotColor.MediumSeaGreen.ToHexString(), node.FillColor.Value); + Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value); } [Fact] public void create_node_sets_correct_fillcolor_and_style_incoming_lower() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var vertexInfo = new Dictionary + var vertexInfo = new Dictionary { { "TestVertex", (3, 4) } }; - DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex"); + DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex"); + + Assert.Equal(DotColor.Salmon.ToHexString(), node.FillColor.Value); + Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value); - Assert.Equal(DotColor.Salmon.ToHexString(), node.FillColor.Value); - Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } @@ -146,20 +120,14 @@ public void create_node_sets_correct_fillcolor_and_style_incoming_lower() [Fact] public void enclose_string_in_quotes_returns_empty_quoted_string_for_nonempty_input() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var input = "test string"; + // Arrange + var input = "test string"; - // Act - var result = DependencyGraphGenerator.EncloseNotEmptyOrNullStringInQuotes(input); + // Act + var result = DependencyGraphGenerator.EncloseNotEmptyOrNullStringInQuotes(input); - // Assert - Assert.Equal("\"test string\"", result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.Equal("\"test string\"", result); } @@ -167,65 +135,39 @@ public void enclose_string_in_quotes_returns_empty_quoted_string_for_nonempty_in [Fact] public void enclose_string_in_quotes_returns_quoted_null_for_null_input() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - string? input = null; + // Arrange + string? input = null; - // Act - var result = DependencyGraphGenerator.EncloseNotEmptyOrNullStringInQuotes(input); - - // Assert - Assert.Equal(string.Empty, result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act + var result = DependencyGraphGenerator.EncloseNotEmptyOrNullStringInQuotes(input); + // Assert + Assert.Equal(string.Empty, result); } // Graph initialization with empty values [Fact] public void initialize_graph_sets_default_label_and_identifier() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - var graph = DependencyGraphGenerator.InitializeGraph(); - - Assert.Equal("DependencyGraph", graph.Label.Value); - Assert.Equal("DependencyGraph", graph.Identifier.Value); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + var graph = DependencyGraphGenerator.InitializeGraph(); + Assert.Equal("DependencyGraph", graph.Label.Value); + Assert.Equal("DependencyGraph", graph.Identifier.Value); } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!_disposed) - { - if (disposing && Directory.Exists(_testDirectory)) - { - // Dispose managed resources - Directory.Delete(_testDirectory, true); - } - // Dispose unmanaged resources (if any) - - _disposed = true; + if (disposing && Directory.Exists(_testDirectory)) + { + // Dispose managed resources + Directory.Delete(_testDirectory, true); } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Dispose unmanaged resources (if any) + base.Dispose(disposing); - ~DependencyGraphGeneratorTests() - { - Dispose(false); } + } } diff --git a/CodeLineCounter.Tests/FileUtilsTests.cs b/CodeLineCounter.Tests/FileUtilsTests.cs index 8732133..c21e091 100644 --- a/CodeLineCounter.Tests/FileUtilsTests.cs +++ b/CodeLineCounter.Tests/FileUtilsTests.cs @@ -2,130 +2,86 @@ namespace CodeLineCounter.Tests { - public class FileUtilsTests : IDisposable + public class FileUtilsTests : TestBase { private readonly string _testDirectory; - private bool _disposed; - private readonly TextWriter _originalConsoleOut; public FileUtilsTests() { _testDirectory = Path.Combine(Path.GetTempPath(), "FileUtilsTests"); Directory.CreateDirectory(_testDirectory); - _originalConsoleOut = Console.Out; } [Fact] public void GetSolutionFiles_Should_Return_List_Of_Solution_Files() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var basePath = FileUtils.GetBasePath(); - var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); - var rootPath = solutionPath; - - // Act - var result = FileUtils.GetSolutionFiles(rootPath); - - // Assert - Assert.NotNull(result); - Assert.NotEmpty(result); - Assert.All(result, file => Assert.EndsWith(".sln", file)); - } + // Arrange + var basePath = FileUtils.GetBasePath(); + var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); + var rootPath = solutionPath; + + // Act + var result = FileUtils.GetSolutionFiles(rootPath); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result); + Assert.All(result, file => Assert.EndsWith(".sln", file)); // Reset console output - Console.SetOut(_originalConsoleOut); - } [Fact] public void GetBasePath_Should_Return_NonEmptyString() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Act - string basePath = FileUtils.GetBasePath(); + // Act + string basePath = FileUtils.GetBasePath(); - // Assert - Assert.False(string.IsNullOrEmpty(basePath), "Base path should not be null or empty."); - Assert.True(Directory.Exists(basePath), "Base path should be a valid directory."); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.False(string.IsNullOrEmpty(basePath), "Base path should not be null or empty."); + Assert.True(Directory.Exists(basePath), "Base path should be a valid directory."); } [Fact] public void get_solution_files_throws_exception_for_nonexistent_directory() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var nonExistentPath = Path.Combine(_testDirectory, Guid.NewGuid().ToString()); - - // Act & Assert - Assert.Throws(() => FileUtils.GetSolutionFiles(nonExistentPath)); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Arrange + var nonExistentPath = Path.Combine(_testDirectory, Guid.NewGuid().ToString()); + // Act & Assert + Assert.Throws(() => FileUtils.GetSolutionFiles(nonExistentPath)); } // Throws UnauthorizedAccessException when solution file does not exist [Fact] public void get_project_files_throws_when_file_not_exists() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - // Arrange - var nonExistentPath = Path.Combine(_testDirectory, "nonexistent.sln"); - - // Act & Assert - var exception = Assert.Throws(() => - FileUtils.GetProjectFiles(nonExistentPath)); + // Arrange + var nonExistentPath = Path.Combine(_testDirectory, "nonexistent.sln"); - Assert.Contains(nonExistentPath, exception.Message); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act & Assert + var exception = Assert.Throws(() => + FileUtils.GetProjectFiles(nonExistentPath)); + Assert.Contains(nonExistentPath, exception.Message); } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!_disposed) + + // Ensure the file is closed before attempting to delete it + if (disposing) { - // Ensure the file is closed before attempting to delete it - if (disposing) + if (Directory.Exists(_testDirectory)) { - - Task.Delay(100).Wait(); - - if (Directory.Exists(_testDirectory)) - { - // Dispose managed resources - Directory.Delete(_testDirectory, true); - } + // Dispose managed resources + Directory.Delete(_testDirectory, true); } - - // Dispose unmanaged resources (if any) - - _disposed = true; } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Dispose unmanaged resources (if any) + base.Dispose(disposing); - ~FileUtilsTests() - { - Dispose(false); } + } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/HashUtilsTests.cs b/CodeLineCounter.Tests/HashUtilsTests.cs index fb8b2a5..e15d2ab 100644 --- a/CodeLineCounter.Tests/HashUtilsTests.cs +++ b/CodeLineCounter.Tests/HashUtilsTests.cs @@ -4,97 +4,63 @@ namespace CodeLineCounter.Tests { public class HashUtilsTests { - private readonly TextWriter _originalConsoleOut; - - public HashUtilsTests() - { - _originalConsoleOut = Console.Out; - } [Fact] public void ComputeHash_EmptyString_ReturnsEmptyString() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - - // Arrange - string input = ""; + // Arrange + string input = ""; - // Act - string result = HashUtils.ComputeHash(input); - - // Assert - Assert.Equal("", result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act + string result = HashUtils.ComputeHash(input); + // Assert + Assert.Equal("", result); } [Fact] public void ComputeHash_NullString_ReturnsEmptyString() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); + // Arrange + string? input = null; - // Arrange - string? input = null; + // Act + string result = HashUtils.ComputeHash(input); - // Act - string result = HashUtils.ComputeHash(input); + // Assert + Assert.Equal("", result); - // Assert - Assert.Equal("", result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } [Fact] public void ComputeHash_ValidString_ReturnsHash() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - - // Arrange - string input = "Hello, World!"; + // Arrange + string input = "Hello, World!"; - // Act - string result = HashUtils.ComputeHash(input); + // Act + string result = HashUtils.ComputeHash(input); - // Assert - Assert.NotEmpty(result); - Assert.IsType(result); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.NotEmpty(result); + Assert.IsType(result); } [Fact] public void ComputeHash_DuplicateStrings_ReturnSameHash() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - - // Arrange - string input1 = "Hello, World!"; - string input2 = "Hello, World!"; - // Act - string result1 = HashUtils.ComputeHash(input1); - string result2 = HashUtils.ComputeHash(input2); + // Arrange + string input1 = "Hello, World!"; + string input2 = "Hello, World!"; - // Assert - Assert.Equal(result1, result2); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Act + string result1 = HashUtils.ComputeHash(input1); + string result2 = HashUtils.ComputeHash(input2); + // Assert + Assert.Equal(result1, result2); } } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/JsonHandlerTests.cs b/CodeLineCounter.Tests/JsonHandlerTests.cs index 604c114..612e93b 100644 --- a/CodeLineCounter.Tests/JsonHandlerTests.cs +++ b/CodeLineCounter.Tests/JsonHandlerTests.cs @@ -3,18 +3,15 @@ namespace CodeLineCounter.Tests { - public class JsonHandlerTests : IDisposable + public class JsonHandlerTests : TestBase { private readonly string _testDirectory; - private bool _disposed; - private readonly TextWriter _originalConsoleOut; public JsonHandlerTests() { _testDirectory = Path.Combine(Path.GetTempPath(), "JsonHandlerTests"); Directory.CreateDirectory(_testDirectory); - _originalConsoleOut = Console.Out; } public class TestClass @@ -32,62 +29,38 @@ public TestClass(int id, string name) [Fact] public void deserialize_valid_json_file_returns_expected_objects() { - using (StringWriter consoleOutput = new()) - { - Console.SetOut(consoleOutput); - - // Arrange - var testFilePath = Path.Combine(_testDirectory, "test.json"); - var expectedData = new[] { new TestClass(id: 1, name: "Test") }; - File.WriteAllText(testFilePath, JsonSerializer.Serialize(expectedData)); + // Arrange + var testFilePath = Path.Combine(_testDirectory, "test.json"); + var expectedData = new[] { new TestClass(id: 1, name: "Test") }; + File.WriteAllText(testFilePath, JsonSerializer.Serialize(expectedData)); - // Act - var result = JsonHandler.Deserialize(testFilePath); + // Act + var result = JsonHandler.Deserialize(testFilePath); - // Assert - Assert.Equal(expectedData.Length, result.Count()); - Assert.Equal(expectedData[0].Id, result.First().Id); - Assert.Equal(expectedData[0].Name, result.First().Name); - - File.Delete(testFilePath); - - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.Equal(expectedData.Length, result.Count()); + Assert.Equal(expectedData[0].Id, result.First().Id); + Assert.Equal(expectedData[0].Name, result.First().Name); + File.Delete(testFilePath); } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!_disposed) + if (disposing) { - if (disposing) + if (Directory.Exists(_testDirectory)) { - Task.Delay(100).Wait(); - - if (Directory.Exists(_testDirectory)) - { - // Dispose managed resources - Directory.Delete(_testDirectory, true); - } + // Dispose managed resources + Directory.Delete(_testDirectory, true); } - - // Dispose unmanaged resources (if any) - - _disposed = true; } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Dispose unmanaged resources (if any) + base.Dispose(disposing); - ~JsonHandlerTests() - { - Dispose(false); } + } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index eb0ce4c..7d327e3 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -2,23 +2,22 @@ using CodeLineCounter.Models; using CodeLineCounter.Utils; -namespace CodeLineCounter.Tests.Services +namespace CodeLineCounter.Tests { - public class SolutionAnalyzerTest : IDisposable + public class SolutionAnalyzerTest : TestBase { private readonly string _testDirectory; private readonly string _testSolutionPath; private readonly string _outputPath; - private bool _disposed; - private readonly TextWriter _originalConsoleOut; public SolutionAnalyzerTest() { _testDirectory = Path.Combine(Path.GetTempPath(), "SolutionAnalyzerTest"); _testSolutionPath = Path.Combine(_testDirectory, "TestSolution.sln"); _outputPath = _testDirectory; - _originalConsoleOut = Console.Out; + initialization(); + RedirectConsoleInputOutput(); Directory.CreateDirectory(_testDirectory); // Create minimal test solution if it doesn't exist if (!File.Exists(_testSolutionPath)) @@ -43,63 +42,48 @@ public void analyze_and_export_solution_succeeds_with_valid_inputs() var outputPath = _outputPath; - // Redirect console output - using (StringWriter stringWriter = new StringWriter()) - { - Console.SetOut(stringWriter); + try + { - try + // Act + var exception = Record.Exception(() => { + SolutionAnalyzer.AnalyzeAndExportSolution(_testSolutionPath, verbose, format, outputPath); + }); - // Act - var exception = Record.Exception(() => - { - SolutionAnalyzer.AnalyzeAndExportSolution(_testSolutionPath, verbose, format, outputPath); - }); + // Assert + Assert.Null(exception); + Assert.True(File.Exists(Path.Combine(outputPath, "TestSolution.CodeMetrics.csv"))); - // Assert - Assert.Null(exception); - Assert.True(File.Exists(Path.Combine(outputPath, "TestSolution.CodeMetrics.csv"))); + } + finally + { + // Cleanup - } - finally - { - // Cleanup - if (File.Exists(solutionPath)) - { - File.Delete(solutionPath); - } + if (File.Exists(solutionPath)) + { + File.Delete(solutionPath); } } - // Reset console output - Console.SetOut(_originalConsoleOut); - } [Fact] public void analyze_and_export_solution_throws_on_invalid_path() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - - var invalidPath = ""; - var verbose = false; - var format = CoreUtils.ExportFormat.JSON; + // Arrange - // Act & Assert - var exception = Assert.Throws(() => - SolutionAnalyzer.AnalyzeAndExportSolution(invalidPath, verbose, format)); + var invalidPath = ""; + var verbose = false; + var format = CoreUtils.ExportFormat.JSON; - Assert.Contains("Access to the path '' is denied.", exception.Message); + // Act & Assert + var exception = Assert.Throws(() => + SolutionAnalyzer.AnalyzeAndExportSolution(invalidPath, verbose, format)); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + Assert.Contains("Access to the path '' is denied.", exception.Message); } @@ -110,71 +94,58 @@ public void PerformAnalysis_ShouldReturnCorrectAnalysisResult() var basePath = FileUtils.GetBasePath(); var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln"); - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - Console.WriteLine($"Constructed solution path: {solutionPath}"); - Assert.True(File.Exists(solutionPath), $"The solution file '{solutionPath}' does not exist."); - Console.WriteLine($"Constructed solution path: {solutionPath}"); - Assert.True(File.Exists(solutionPath), $"The solution file '{solutionPath}' does not exist."); - - // Act - var result = SolutionAnalyzer.PerformAnalysis(solutionPath); + Assert.True(File.Exists(solutionPath), $"The solution file '{solutionPath}' does not exist."); + Assert.True(File.Exists(solutionPath), $"The solution file '{solutionPath}' does not exist."); - // Assert - Assert.NotNull(result); - Assert.Equal("CodeLineCounter.sln", result.SolutionFileName); + // Act + var result = SolutionAnalyzer.PerformAnalysis(solutionPath); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + // Assert + Assert.NotNull(result); + Assert.Equal("CodeLineCounter.sln", result.SolutionFileName); } [Fact] public void OutputAnalysisResults_ShouldPrintCorrectOutput() { - using (var sw = new StringWriter()) + + // Arrange + var result = new AnalysisResult { - Console.SetOut(sw); - // Arrange - var result = new AnalysisResult - { - Metrics = new List(), - ProjectTotals = new Dictionary(), - TotalLines = 1000, - TotalFiles = 10, - DuplicationMap = new List(), - DependencyList = new List(), - ProcessingTime = TimeSpan.FromSeconds(10), - SolutionFileName = "CodeLineCounter.sln", - DuplicatedLines = 100 - }; - var verbose = true; + Metrics = new List(), + ProjectTotals = new Dictionary(), + TotalLines = 1000, + TotalFiles = 10, + DuplicationMap = new List(), + DependencyList = new List(), + ProcessingTime = TimeSpan.FromSeconds(10), + SolutionFileName = "CodeLineCounter.sln", + DuplicatedLines = 100 + }; + var verbose = false; - // Act - SolutionAnalyzer.OutputAnalysisResults(result, verbose); + // Act - // Assert - var output = sw.ToString(); - Assert.Contains("Processing completed, number of source files processed: 10", output); - Assert.Contains("Total lines of code: 1000", output); - Assert.Contains("Solution CodeLineCounter.sln has 100 duplicated lines of code.", output); - Assert.Contains("Percentage of duplicated code: 10.00 %", output); - Assert.Contains("Time taken: 0:10.000", output); - } - // Reset console output - Console.SetOut(_originalConsoleOut); + SolutionAnalyzer.OutputAnalysisResults(result, verbose); + // Get console output + var output = GetConsoleOutput(); + + + // Assert + + Assert.Contains("Processing completed, number of source files processed: 10", output); + Assert.Contains("Total lines of code: 1000", output); + Assert.Contains("Solution CodeLineCounter.sln has 100 duplicated lines of code.", output); + Assert.Contains("Percentage of duplicated code: 10.00 %", output); + Assert.Contains("Time taken: 0:10.000", output); } [Fact] public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals() { - using (var sw = new StringWriter()) - { - Console.SetOut(sw); - // Arrange - var metrics = new List + // Arrange + var metrics = new List { new NamespaceMetrics { @@ -198,26 +169,24 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals() } }; - var projectTotals = new Dictionary + var projectTotals = new Dictionary { { "Project1", 100 }, { "Project2", 200 } }; - // Act - SolutionAnalyzer.OutputDetailedMetrics(metrics, projectTotals); + // Act + SolutionAnalyzer.OutputDetailedMetrics(metrics, projectTotals); - // Assert - var expectedOutput = - $"Project Project1 (/path/to/project1) - Namespace Namespace1 in file File1.cs (/path/to/project1/File1.cs) has 100 lines of code and a cyclomatic complexity of 10.{Environment.NewLine}" + - $"Project Project2 (/path/to/project2) - Namespace Namespace2 in file File2.cs (/path/to/project2/File2.cs) has 200 lines of code and a cyclomatic complexity of 20.{Environment.NewLine}" + - $"Project Project1 has 100 total lines of code.{Environment.NewLine}" + - $"Project Project2 has 200 total lines of code.{Environment.NewLine}"; + // Assert + var expectedOutput = + $"Project Project1 (/path/to/project1) - Namespace Namespace1 in file File1.cs (/path/to/project1/File1.cs) has 100 lines of code and a cyclomatic complexity of 10.{Environment.NewLine}" + + $"Project Project2 (/path/to/project2) - Namespace Namespace2 in file File2.cs (/path/to/project2/File2.cs) has 200 lines of code and a cyclomatic complexity of 20.{Environment.NewLine}" + + $"Project Project1 has 100 total lines of code.{Environment.NewLine}" + + $"Project Project2 has 200 total lines of code.{Environment.NewLine}"; + + Assert.Equal(expectedOutput, GetConsoleOutput()); - Assert.Equal(expectedOutput, sw.ToString()); - } - // Reset console output - Console.SetOut(_originalConsoleOut); } // Export metrics, duplications and dependencies data in parallel for valid input @@ -225,78 +194,59 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals() public void export_results_with_valid_input_exports_all_files() { // Act - using (var sw = new StringWriter()) + // Arrange + var result = new AnalysisResult { - // Arrange - var result = new AnalysisResult - { - SolutionFileName = "CodelineCounter.sln", - Metrics = new List(), - ProjectTotals = new Dictionary(), - TotalLines = 1000, - DuplicationMap = new List(), - DependencyList = new List() - }; + SolutionFileName = "CodelineCounter.sln", + Metrics = new List(), + ProjectTotals = new Dictionary(), + TotalLines = 1000, + DuplicationMap = new List(), + DependencyList = new List() + }; - var basePath = FileUtils.GetBasePath(); - var baseSolutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); + var basePath = FileUtils.GetBasePath(); + var baseSolutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); - var solutionPath = Path.Combine(baseSolutionPath, "CodelineCounter.sln"); - var format = CoreUtils.ExportFormat.JSON; - Console.SetOut(sw); + var solutionPath = Path.Combine(baseSolutionPath, "CodelineCounter.sln"); + var format = CoreUtils.ExportFormat.JSON; - try - { - SolutionAnalyzer.ExportResults(result, solutionPath, format, baseSolutionPath); - // Assert - Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter.CodeMetrics.json"))); - Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter.CodeDuplications.json"))); - Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter.Dependencies.dot"))); - } - finally - { - File.Delete(Path.Combine(baseSolutionPath, "CodelineCounter.CodeMetrics.json")); - File.Delete(Path.Combine(baseSolutionPath, "CodelineCounter.CodeDuplications.json")); - File.Delete(Path.Combine(baseSolutionPath, "CodelineCounter.Dependencies.dot")); - } + try + { + SolutionAnalyzer.ExportResults(result, solutionPath, format, baseSolutionPath); + // Assert + Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter.CodeMetrics.json"))); + Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter.CodeDuplications.json"))); + Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter.Dependencies.dot"))); + } + finally + { + // Cleanup + File.Delete(Path.Combine(baseSolutionPath, "CodelineCounter.CodeMetrics.json")); + File.Delete(Path.Combine(baseSolutionPath, "CodelineCounter.CodeDuplications.json")); + File.Delete(Path.Combine(baseSolutionPath, "CodelineCounter.Dependencies.dot")); } - // Reset console output - Console.SetOut(_originalConsoleOut); } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!_disposed) + + if (disposing) { - if (disposing) + if (Directory.Exists(_testDirectory)) { - Task.Delay(100).Wait(); - - if (Directory.Exists(_testDirectory)) - { - // Dispose managed resources - File.Delete(_testSolutionPath); - Directory.Delete(_testDirectory, true); - } + // Dispose managed resources + File.Delete(_testSolutionPath); + Directory.Delete(_testDirectory, true); } - - // Dispose unmanaged resources (if any) - - _disposed = true; } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Dispose unmanaged resources (if any) + base.Dispose(disposing); + - ~SolutionAnalyzerTest() - { - Dispose(false); } } diff --git a/CodeLineCounter.Tests/TestBase.cs b/CodeLineCounter.Tests/TestBase.cs new file mode 100644 index 0000000..018f3b6 --- /dev/null +++ b/CodeLineCounter.Tests/TestBase.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Threading; +using Xunit; + +namespace CodeLineCounter.Tests +{ + public abstract class TestBase : IDisposable + { + private static readonly object _consoleLock = new object(); + private readonly ThreadLocal _stringWriter = new ThreadLocal(() => new StringWriter()); + private readonly ThreadLocal _stringReader = new ThreadLocal(() => new StringReader(string.Empty)); + private readonly ThreadLocal _originalConsoleOut = new ThreadLocal(); + private readonly ThreadLocal _originalConsoleIn = new ThreadLocal(); + + private bool _disposed; + + protected TestBase() + { + + } + + protected void initialization() + { + lock (_consoleLock) + { + _originalConsoleOut.Value = Console.Out; + _originalConsoleIn.Value = Console.In; + } + + } + + protected void RedirectConsoleInputOutput() + { + lock (_consoleLock) + { + if (_stringWriter.Value != null) + { + Console.SetOut(_stringWriter.Value); + } + if (_stringReader.Value != null) + { + Console.SetIn(_stringReader.Value); + } + } + } + + protected string GetConsoleOutput() + { + lock (_consoleLock) + { + if (_stringWriter.Value != null) + { + return _stringWriter.Value.ToString(); + } + return string.Empty; + } + } + + protected void SetConsoleInput(string input) + { + lock (_consoleLock) + { + _stringReader.Value = new StringReader(input); + } + } + + protected void ResetConsoleInputOutput() + { + lock (_consoleLock) + { + if (_originalConsoleOut != null && _originalConsoleOut.Value != null) + { + Console.SetOut(_originalConsoleOut.Value); + } + if (_originalConsoleIn != null && _originalConsoleIn.Value != null) + { + Console.SetIn(_originalConsoleIn.Value); + } + if (_stringWriter.Value != null) + { + _stringWriter.Value.GetStringBuilder().Clear(); // Clear the StringWriter's buffer + } + } + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + ResetConsoleInputOutput(); + _stringWriter.Value?.Dispose(); + _stringReader.Value?.Dispose(); + } + _disposed = true; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~TestBase() + { + Dispose(false); + } + } +} \ No newline at end of file diff --git a/CodeLineCounter/Exceptions/HelpRequestedException.cs b/CodeLineCounter/Exceptions/HelpRequestedException.cs new file mode 100644 index 0000000..12a214d --- /dev/null +++ b/CodeLineCounter/Exceptions/HelpRequestedException.cs @@ -0,0 +1,13 @@ +using System; +using CodeLineCounter.Models; + +namespace CodeLineCounter.Exceptions +{ + public class HelpRequestedException : Exception + + { + public HelpRequestedException() : base($"Usage: CodeLineCounter.exe [-verbose] [-d ] [-output ] [-help, -h] (-format )'") + { + } + } +} \ No newline at end of file diff --git a/CodeLineCounter/Exceptions/InvalidExportFormatException.cs b/CodeLineCounter/Exceptions/InvalidExportFormatException.cs new file mode 100644 index 0000000..2561ba6 --- /dev/null +++ b/CodeLineCounter/Exceptions/InvalidExportFormatException.cs @@ -0,0 +1,13 @@ +using System; +using CodeLineCounter.Models; + +namespace CodeLineCounter.Exceptions +{ + public class InvalidExportFormatException : Exception + + { + public InvalidExportFormatException(string formatString, Settings settings) : base($"Invalid format: {formatString}. Valid formats are: {string.Join(", ", Enum.GetNames())}. Using default format {settings.Format}'") + { + } + } +} \ No newline at end of file diff --git a/CodeLineCounter/Exceptions/InvalidNumberException.cs b/CodeLineCounter/Exceptions/InvalidNumberException.cs new file mode 100644 index 0000000..a7af547 --- /dev/null +++ b/CodeLineCounter/Exceptions/InvalidNumberException.cs @@ -0,0 +1,12 @@ +using System; + +namespace CodeLineCounter.Exceptions +{ + public class InvalidNumberException : Exception + + { + public InvalidNumberException() : base($"'Invalid input. Please enter a numeric value.'") + { + } + } +} \ No newline at end of file diff --git a/CodeLineCounter/Exceptions/InvalidSelectionException.cs b/CodeLineCounter/Exceptions/InvalidSelectionException.cs new file mode 100644 index 0000000..970e69d --- /dev/null +++ b/CodeLineCounter/Exceptions/InvalidSelectionException.cs @@ -0,0 +1,12 @@ +using System; + +namespace CodeLineCounter.Exceptions +{ + public class InvalidSelectionException : Exception + + { + public InvalidSelectionException(int solutionCount) : base($"'Selection must be between 1 and {solutionCount}.'") + { + } + } +} \ No newline at end of file diff --git a/CodeLineCounter/Models/Settings.cs b/CodeLineCounter/Models/Settings.cs index c182d02..9168bb4 100644 --- a/CodeLineCounter/Models/Settings.cs +++ b/CodeLineCounter/Models/Settings.cs @@ -1,3 +1,4 @@ +using CodeLineCounter.Exceptions; using CodeLineCounter.Utils; namespace CodeLineCounter.Models @@ -42,14 +43,12 @@ public bool IsValid() { if (Help) { - Console.WriteLine("Usage: CodeLineCounter.exe [-verbose] [-d ] [-output ] [-help, -h] (-format )"); - return true; + throw new HelpRequestedException(); } if (DirectoryPath == null) { - Console.WriteLine("Please provide the directory path containing the solutions to analyze using the -d switch."); - return false; + throw new ArgumentNullException(DirectoryPath, "Directory path cannot be null"); } if (OutputPath != null && !Directory.Exists(OutputPath)) @@ -60,8 +59,7 @@ public bool IsValid() } catch (Exception) { - Console.WriteLine($"Cannot create or access output directory: {OutputPath}"); - return false; + throw new UnauthorizedAccessException($"Cannot create or access output directory: {OutputPath}"); } } diff --git a/CodeLineCounter/Program.cs b/CodeLineCounter/Program.cs index cb31026..6ce92ac 100644 --- a/CodeLineCounter/Program.cs +++ b/CodeLineCounter/Program.cs @@ -7,34 +7,45 @@ static class Program { static void Main(string[] args) { - var settings = CoreUtils.ParseArguments(args); - if (!settings.IsValid() || settings.DirectoryPath == null) - return; - - List solutionFiles = FileUtils.GetSolutionFiles(settings.DirectoryPath); - if (solutionFiles.Count == 0) + try { - Console.WriteLine("No solution (.sln) found in the specified directory."); - return; - } + var settings = CoreUtils.ParseArguments(args); + if (!settings.IsValid() || settings.DirectoryPath == null) + return; - // If only one solution found in directory, select it automatically - // if more than one waiting for user selection - if (solutionFiles.Count == 1) - { - SolutionAnalyzer.AnalyzeAndExportSolution(solutionFiles[0], settings.Verbose, settings.Format, settings.OutputPath); - return; - } + List solutionFiles = FileUtils.GetSolutionFiles(settings.DirectoryPath); + if (solutionFiles.Count == 0) + { + Console.WriteLine("No solution (.sln) found in the specified directory."); + return; + } + + // If only one solution found in directory, select it automatically + // if more than one waiting for user selection + if (solutionFiles.Count == 1) + { + SolutionAnalyzer.AnalyzeAndExportSolution(solutionFiles[0], settings.Verbose, settings.Format, settings.OutputPath); + return; + } - var solutionFilenameList = CoreUtils.GetFilenamesList(solutionFiles); - CoreUtils.DisplaySolutions(solutionFilenameList); + var solutionFilenameList = CoreUtils.GetFilenamesList(solutionFiles); + CoreUtils.DisplaySolutions(solutionFilenameList); + Console.Write($"Choose a solution to analyze (1-{solutionFiles.Count}): "); + string? input = Console.ReadLine(); - var choice = CoreUtils.GetUserChoice(solutionFiles.Count); - if (choice == -1) - return; - var solutionPath = Path.GetFullPath(solutionFiles[choice - 1]); - SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, settings.Verbose, settings.Format, settings.OutputPath); + var choice = CoreUtils.CheckInputUserChoice(input, solutionFiles.Count); + if (choice == -1) + return; + + var solutionPath = Path.GetFullPath(solutionFiles[choice - 1]); + SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, settings.Verbose, settings.Format, settings.OutputPath); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } } } \ No newline at end of file diff --git a/CodeLineCounter/Services/DependencyGraphGenerator.cs b/CodeLineCounter/Services/DependencyGraphGenerator.cs index ccdde57..0c00543 100644 --- a/CodeLineCounter/Services/DependencyGraphGenerator.cs +++ b/CodeLineCounter/Services/DependencyGraphGenerator.cs @@ -9,8 +9,98 @@ namespace CodeLineCounter.Services { + public class GraphvizUnflattenOptions + { + public int ChainLimit { get; set; } = 0; + public int MaxMinlen { get; set; } = 0; + public bool DoFans { get; set; } = false; + } + public static class DependencyGraphGenerator { + public static void UnflattenGraph(DotGraph graph, GraphvizUnflattenOptions options) + { + var nodeDegrees = new Dictionary(); + + DotNode? chainNode = null; + int chainSize = 0; + + // Extraire les arêtes du graphe + var edges = graph.Elements.OfType().ToList(); + var nodes = graph.Elements.OfType().ToList(); + var subgraphes = graph.Elements.OfType().ToList(); + foreach (var subgraphe in subgraphes) + { + edges.AddRange(subgraphe.Elements.OfType()); + nodes.AddRange(subgraphe.Elements.OfType()); + } + + // Calculer le degré de chaque nœud + foreach (var node in nodes) + { + int degree = GetNodeDegree(edges, node.Identifier.Value); + nodeDegrees[node.Identifier.Value] = degree; + } + + // Traiter les nœuds du graphe + foreach (var node in nodes) + { + DotIdentifier nodeId = node.Identifier; + int degree = nodeDegrees[nodeId.Value]; + + if (degree == 0) + { + if (options.ChainLimit < 1) + continue; + + if (chainNode != null) + { + var edge = new DotEdge { From = chainNode.Identifier, To = nodeId, Style = DotEdgeStyle.Invis }; + graph.Elements.Add(edge); + chainSize++; + + if (chainSize < options.ChainLimit) + chainNode = node; + else + { + chainNode = null; + chainSize = 0; + } + } + else + { + chainNode = node; + } + } + else if (degree > 1) + { + if (options.MaxMinlen < 1) + continue; + + int cnt = 0; + foreach (var edge in edges) + { + if (edge.To == nodeId && IsLeaf(edges, edge.From.Value)) + { + DotAttribute minLen = edge.GetAttribute("minlen"); + minLen.Value = (cnt % options.MaxMinlen + 1).ToString(); + edge.SetAttribute("minLen", minLen); + cnt++; + } + } + } + } + } + + private static int GetNodeDegree(List edges, string nodeId) + { + return edges.Count(e => e.From.ToString() == nodeId || e.To.ToString() == nodeId); + } + + private static bool IsLeaf(List edges, string nodeId) + { + return !edges.Any(e => e.From.ToString() == nodeId); + } private static readonly RecyclableMemoryStreamManager MemoryStreamManager = new RecyclableMemoryStreamManager(); public static DotGraph GenerateGraphOnly(List dependencies, string? filterNamespace = null, string? filterAssembly = null) { @@ -32,6 +122,8 @@ public static DotGraph GenerateGraphOnly(List dependencies, // Add edges AddEdgesBetweenDependencies(filteredDependencies, graph); + UnflattenGraph(graph, new GraphvizUnflattenOptions { ChainLimit = 10, MaxMinlen = 10, DoFans = false }); + return graph; } diff --git a/CodeLineCounter/Services/SolutionAnalyzer.cs b/CodeLineCounter/Services/SolutionAnalyzer.cs index 25a2f13..c3c17f2 100644 --- a/CodeLineCounter/Services/SolutionAnalyzer.cs +++ b/CodeLineCounter/Services/SolutionAnalyzer.cs @@ -17,17 +17,17 @@ public static void AnalyzeAndExportSolution(string solutionPath, bool verbose, C } catch (UnauthorizedAccessException ex) { - Console.Error.WriteLine($"Access denied: {ex.Message}"); + Console.WriteLine($"Access denied: {ex.Message}"); throw; } catch (FileNotFoundException ex) { - Console.Error.WriteLine($"File not found: {ex.Message}"); + Console.WriteLine($"File not found: {ex.Message}"); throw; } catch (Exception ex) { - Console.Error.WriteLine($"Error analyzing solution: {ex.Message}"); + Console.WriteLine($"Error analyzing solution: {ex.Message}"); throw; } } @@ -101,12 +101,12 @@ public static void ExportResults(AnalysisResult result, string solutionPath, Cor } catch (AggregateException ae) { - Console.Error.WriteLine($"Error during parallel export operations: {ae.InnerException?.Message}"); + Console.WriteLine($"Error during parallel export operations: {ae.InnerException?.Message}"); throw; } catch (IOException ioe) { - Console.Error.WriteLine($"IO error during file operations: {ioe.Message}"); + Console.WriteLine($"IO error during file operations: {ioe.Message}"); throw; } } diff --git a/CodeLineCounter/Utils/CoreUtils.cs b/CodeLineCounter/Utils/CoreUtils.cs index 1dbb0d4..c98733f 100644 --- a/CodeLineCounter/Utils/CoreUtils.cs +++ b/CodeLineCounter/Utils/CoreUtils.cs @@ -1,3 +1,4 @@ +using CodeLineCounter.Exceptions; using CodeLineCounter.Models; namespace CodeLineCounter.Utils @@ -64,7 +65,7 @@ private static void HandleFormat(string[] args, Settings settings, ref int argIn } else { - Console.WriteLine($"Invalid format: {formatString}. Valid formats are: {string.Join(", ", Enum.GetNames())}. Using default format {settings.Format}"); + throw new InvalidExportFormatException(formatString, settings); } } } @@ -78,35 +79,36 @@ private static void HandleOutput(string[] args, Settings settings, ref int argIn } } + /// - /// Gets a valid user selection from the available solutions. + /// Checks user input and returns a valid choice. /// - /// The total number of available solutions - /// Selected solution number (1-based index) or -1 if invalid - public static int GetUserChoice(int solutionCount) + /// The user input from the console. + /// The number of solutions to choose from. + /// The user's choice. + /// Thrown if is null or empty. + /// Thrown if is less than 1. + public static int CheckInputUserChoice(string? inputFromConsole, int solutionCount) { ArgumentOutOfRangeException.ThrowIfLessThan(solutionCount, 1); - Console.Write($"Choose a solution to analyze (1-{solutionCount}): "); + int choice = -1; - string? input = Console.ReadLine(); + string? input = inputFromConsole; if (string.IsNullOrWhiteSpace(input)) { - Console.WriteLine("No input provided. Please enter a valid number."); - return -1; + throw new ArgumentNullException("No input provided. Please enter a valid number."); } - if (!int.TryParse(input, out int choice)) + if (!int.TryParse(input, out choice)) { - Console.WriteLine("Invalid input. Please enter a numeric value."); - return -1; + throw new InvalidNumberException(); } if (choice < 1 || choice > solutionCount) { - Console.WriteLine($"Selection must be between 1 and {solutionCount}."); - return -1; + throw new InvalidSelectionException(solutionCount); } return choice; From a7ce1dd3ed3fafd9fae5a16e00c0106c3ad3b2b2 Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Tue, 18 Feb 2025 18:08:37 +0100 Subject: [PATCH 2/5] refactor: simplify node and edge extraction, enhance degree calculation, and improve error messaging in DependencyGraphGenerator and CoreUtils --- .../Services/DependencyGraphGenerator.cs | 113 ++++++++++-------- CodeLineCounter/Utils/CoreUtils.cs | 2 +- 2 files changed, 64 insertions(+), 51 deletions(-) diff --git a/CodeLineCounter/Services/DependencyGraphGenerator.cs b/CodeLineCounter/Services/DependencyGraphGenerator.cs index 0c00543..afa75a5 100644 --- a/CodeLineCounter/Services/DependencyGraphGenerator.cs +++ b/CodeLineCounter/Services/DependencyGraphGenerator.cs @@ -26,21 +26,12 @@ public static void UnflattenGraph(DotGraph graph, GraphvizUnflattenOptions optio int chainSize = 0; // Extraire les arêtes du graphe - var edges = graph.Elements.OfType().ToList(); - var nodes = graph.Elements.OfType().ToList(); - var subgraphes = graph.Elements.OfType().ToList(); - foreach (var subgraphe in subgraphes) - { - edges.AddRange(subgraphe.Elements.OfType()); - nodes.AddRange(subgraphe.Elements.OfType()); - } + List edges; + List nodes; + ExtractNodesAndEdges(graph, out edges, out nodes); // Calculer le degré de chaque nœud - foreach (var node in nodes) - { - int degree = GetNodeDegree(edges, node.Identifier.Value); - nodeDegrees[node.Identifier.Value] = degree; - } + CalculateDegrees(nodeDegrees, edges, nodes); // Traiter les nœuds du graphe foreach (var node in nodes) @@ -48,50 +39,72 @@ public static void UnflattenGraph(DotGraph graph, GraphvizUnflattenOptions optio DotIdentifier nodeId = node.Identifier; int degree = nodeDegrees[nodeId.Value]; - if (degree == 0) + if (degree == 0 && options.ChainLimit >= 1) + { + AddEdgeAndStyleToUnflatten(graph, options, ref chainNode, ref chainSize, node, nodeId); + } + else if (degree > 1 && options.MaxMinlen >= 1) + { + SetMinLenAttributeWhenUnflatting(options, edges, nodeId); + } + } + } + + private static void AddEdgeAndStyleToUnflatten(DotGraph graph, GraphvizUnflattenOptions options, ref DotNode? chainNode, ref int chainSize, DotNode node, DotIdentifier nodeId) + { + if (chainNode != null) + { + var edge = new DotEdge { From = chainNode.Identifier, To = nodeId, Style = DotEdgeStyle.Invis }; + graph.Elements.Add(edge); + chainSize++; + + if (chainSize < options.ChainLimit) + chainNode = node; + else { - if (options.ChainLimit < 1) - continue; - - if (chainNode != null) - { - var edge = new DotEdge { From = chainNode.Identifier, To = nodeId, Style = DotEdgeStyle.Invis }; - graph.Elements.Add(edge); - chainSize++; - - if (chainSize < options.ChainLimit) - chainNode = node; - else - { - chainNode = null; - chainSize = 0; - } - } - else - { - chainNode = node; - } + chainNode = null; + chainSize = 0; } - else if (degree > 1) + } + else + { + chainNode = node; + } + } + + private static void SetMinLenAttributeWhenUnflatting(GraphvizUnflattenOptions options, List edges, DotIdentifier nodeId) + { + int cnt = 0; + foreach (var edge in edges) + { + if (edge.To == nodeId && IsLeaf(edges, edge.From.Value)) { - if (options.MaxMinlen < 1) - continue; - - int cnt = 0; - foreach (var edge in edges) - { - if (edge.To == nodeId && IsLeaf(edges, edge.From.Value)) - { - DotAttribute minLen = edge.GetAttribute("minlen"); - minLen.Value = (cnt % options.MaxMinlen + 1).ToString(); - edge.SetAttribute("minLen", minLen); - cnt++; - } - } + DotAttribute minLen = edge.GetAttribute("minlen"); + minLen.Value = (cnt % options.MaxMinlen + 1).ToString(); + edge.SetAttribute("minLen", minLen); + cnt++; } } } + private static void CalculateDegrees(Dictionary nodeDegrees, List edges, List nodes) + { + foreach (var nodeId in nodes.Select(node => node.Identifier.Value)) + { + int degree = GetNodeDegree(edges, nodeId); + nodeDegrees[nodeId] = degree; + } + } + + private static void ExtractNodesAndEdges(DotGraph graph, out List edges, out List nodes) + { + edges = graph.Elements.OfType().ToList(); + nodes = graph.Elements.OfType().ToList(); + var subgraphes = graph.Elements.OfType().ToList(); + edges.AddRange(subgraphes.SelectMany(s => s.Elements.OfType())); + nodes.AddRange(subgraphes.SelectMany(s => s.Elements.OfType())); + } + private static int GetNodeDegree(List edges, string nodeId) { return edges.Count(e => e.From.ToString() == nodeId || e.To.ToString() == nodeId); diff --git a/CodeLineCounter/Utils/CoreUtils.cs b/CodeLineCounter/Utils/CoreUtils.cs index c98733f..d360837 100644 --- a/CodeLineCounter/Utils/CoreUtils.cs +++ b/CodeLineCounter/Utils/CoreUtils.cs @@ -98,7 +98,7 @@ public static int CheckInputUserChoice(string? inputFromConsole, int solutionCou if (string.IsNullOrWhiteSpace(input)) { - throw new ArgumentNullException("No input provided. Please enter a valid number."); + throw new ArgumentNullException("inputFromConsole", "No input provided. Please enter a valid number."); } if (!int.TryParse(input, out choice)) From 237ccf0149b1415ffe7737afce0e65ef83a03d1c Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Tue, 18 Feb 2025 18:31:13 +0100 Subject: [PATCH 3/5] refactor: remove unused UnflattenGraph method and related logic in DependencyGraphGenerator --- .../DependencyGraphGeneratorTests.cs | 1 + .../Services/DependencyGraphGenerator.cs | 97 ------------------- 2 files changed, 1 insertion(+), 97 deletions(-) diff --git a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs index 50fea03..b99eb3c 100644 --- a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs +++ b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs @@ -1,5 +1,6 @@ using CodeLineCounter.Models; using CodeLineCounter.Services; +using DotNetGraph.Attributes; using DotNetGraph.Core; using DotNetGraph.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/CodeLineCounter/Services/DependencyGraphGenerator.cs b/CodeLineCounter/Services/DependencyGraphGenerator.cs index afa75a5..8ef32e7 100644 --- a/CodeLineCounter/Services/DependencyGraphGenerator.cs +++ b/CodeLineCounter/Services/DependencyGraphGenerator.cs @@ -18,102 +18,7 @@ public class GraphvizUnflattenOptions public static class DependencyGraphGenerator { - public static void UnflattenGraph(DotGraph graph, GraphvizUnflattenOptions options) - { - var nodeDegrees = new Dictionary(); - - DotNode? chainNode = null; - int chainSize = 0; - - // Extraire les arêtes du graphe - List edges; - List nodes; - ExtractNodesAndEdges(graph, out edges, out nodes); - - // Calculer le degré de chaque nœud - CalculateDegrees(nodeDegrees, edges, nodes); - - // Traiter les nœuds du graphe - foreach (var node in nodes) - { - DotIdentifier nodeId = node.Identifier; - int degree = nodeDegrees[nodeId.Value]; - - if (degree == 0 && options.ChainLimit >= 1) - { - AddEdgeAndStyleToUnflatten(graph, options, ref chainNode, ref chainSize, node, nodeId); - } - else if (degree > 1 && options.MaxMinlen >= 1) - { - SetMinLenAttributeWhenUnflatting(options, edges, nodeId); - } - } - } - - private static void AddEdgeAndStyleToUnflatten(DotGraph graph, GraphvizUnflattenOptions options, ref DotNode? chainNode, ref int chainSize, DotNode node, DotIdentifier nodeId) - { - if (chainNode != null) - { - var edge = new DotEdge { From = chainNode.Identifier, To = nodeId, Style = DotEdgeStyle.Invis }; - graph.Elements.Add(edge); - chainSize++; - - if (chainSize < options.ChainLimit) - chainNode = node; - else - { - chainNode = null; - chainSize = 0; - } - } - else - { - chainNode = node; - } - } - - private static void SetMinLenAttributeWhenUnflatting(GraphvizUnflattenOptions options, List edges, DotIdentifier nodeId) - { - int cnt = 0; - foreach (var edge in edges) - { - if (edge.To == nodeId && IsLeaf(edges, edge.From.Value)) - { - DotAttribute minLen = edge.GetAttribute("minlen"); - minLen.Value = (cnt % options.MaxMinlen + 1).ToString(); - edge.SetAttribute("minLen", minLen); - cnt++; - } - } - } - private static void CalculateDegrees(Dictionary nodeDegrees, List edges, List nodes) - { - foreach (var nodeId in nodes.Select(node => node.Identifier.Value)) - { - int degree = GetNodeDegree(edges, nodeId); - nodeDegrees[nodeId] = degree; - } - } - - private static void ExtractNodesAndEdges(DotGraph graph, out List edges, out List nodes) - { - edges = graph.Elements.OfType().ToList(); - nodes = graph.Elements.OfType().ToList(); - var subgraphes = graph.Elements.OfType().ToList(); - edges.AddRange(subgraphes.SelectMany(s => s.Elements.OfType())); - nodes.AddRange(subgraphes.SelectMany(s => s.Elements.OfType())); - } - - private static int GetNodeDegree(List edges, string nodeId) - { - return edges.Count(e => e.From.ToString() == nodeId || e.To.ToString() == nodeId); - } - - private static bool IsLeaf(List edges, string nodeId) - { - return !edges.Any(e => e.From.ToString() == nodeId); - } private static readonly RecyclableMemoryStreamManager MemoryStreamManager = new RecyclableMemoryStreamManager(); public static DotGraph GenerateGraphOnly(List dependencies, string? filterNamespace = null, string? filterAssembly = null) { @@ -135,8 +40,6 @@ public static DotGraph GenerateGraphOnly(List dependencies, // Add edges AddEdgesBetweenDependencies(filteredDependencies, graph); - UnflattenGraph(graph, new GraphvizUnflattenOptions { ChainLimit = 10, MaxMinlen = 10, DoFans = false }); - return graph; } From cb348bf731f84a504a0c11638a503eaa5939c729 Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Tue, 18 Feb 2025 18:34:11 +0100 Subject: [PATCH 4/5] refactor: remove unused GraphvizUnflattenOptions class in DependencyGraphGenerator --- CodeLineCounter/Services/DependencyGraphGenerator.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CodeLineCounter/Services/DependencyGraphGenerator.cs b/CodeLineCounter/Services/DependencyGraphGenerator.cs index 8ef32e7..df2685c 100644 --- a/CodeLineCounter/Services/DependencyGraphGenerator.cs +++ b/CodeLineCounter/Services/DependencyGraphGenerator.cs @@ -9,12 +9,6 @@ namespace CodeLineCounter.Services { - public class GraphvizUnflattenOptions - { - public int ChainLimit { get; set; } = 0; - public int MaxMinlen { get; set; } = 0; - public bool DoFans { get; set; } = false; - } public static class DependencyGraphGenerator { From 543c303aa3844c9a078ca665067313d08395e43e Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Tue, 18 Feb 2025 18:38:02 +0100 Subject: [PATCH 5/5] refactor: use nameof operator for argument name in exception throwing --- CodeLineCounter/Utils/CoreUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeLineCounter/Utils/CoreUtils.cs b/CodeLineCounter/Utils/CoreUtils.cs index d360837..d633e73 100644 --- a/CodeLineCounter/Utils/CoreUtils.cs +++ b/CodeLineCounter/Utils/CoreUtils.cs @@ -98,7 +98,7 @@ public static int CheckInputUserChoice(string? inputFromConsole, int solutionCou if (string.IsNullOrWhiteSpace(input)) { - throw new ArgumentNullException("inputFromConsole", "No input provided. Please enter a valid number."); + throw new ArgumentNullException(nameof(inputFromConsole), "No input provided. Please enter a valid number."); } if (!int.TryParse(input, out choice))