diff --git a/.gitignore b/.gitignore index 8f9a7e0..fff9a86 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ /codeql* /*.dot /*snyk* +/exports # Mac OS .DS_Store diff --git a/.vscode/launch.json b/.vscode/launch.json index 60f073a..d7a60a5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,6 +20,16 @@ "cwd": "${workspaceFolder}", "stopAtEntry": false, "console": "internalConsole" + }, + { + "name": "C#: CodeLineCounter - withOutputPath", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/CodeLineCounter/bin/Debug/net9.0/CodeLineCounter.dll", + "args": ["-d", "${workspaceFolder}", "-output", "${workspaceFolder}\\exports" ], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" } ] } \ No newline at end of file diff --git a/CodeLineCounter.Tests/CodeAnalyzerTests.cs b/CodeLineCounter.Tests/CodeAnalyzerTests.cs index 32b3485..4eb13c9 100644 --- a/CodeLineCounter.Tests/CodeAnalyzerTests.cs +++ b/CodeLineCounter.Tests/CodeAnalyzerTests.cs @@ -9,6 +9,9 @@ public class CodeAnalyzerTests [Fact] public void TestAnalyzeSolution() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + string basePath = FileUtils.GetBasePath(); var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..", "CodeLineCounter.sln")); @@ -28,61 +31,80 @@ public void TestAnalyzeSolution() [Fact] public void AnalyzeSourceCode_Should_Set_CurrentNamespace() { - // Arrange - var projectNamespaceMetrics = new Dictionary(); - var lines = new string[] + using (StringWriter consoleOutput = new()) { + Console.SetOut(consoleOutput); + + // Arrange + var projectNamespaceMetrics = new Dictionary(); + var lines = new string[] + { "namespace MyNamespace", "{", " // Code goes here", "}" - }; + }; - // Act - CodeMetricsAnalyzer.AnalyzeSourceCode(projectNamespaceMetrics, lines, out string? currentNamespace, out _, out _); + // Act + CodeMetricsAnalyzer.AnalyzeSourceCode(projectNamespaceMetrics, lines, out string? currentNamespace, out _, out _); + + // Assert + Assert.Equal("MyNamespace", currentNamespace); + + } - // Assert - Assert.Equal("MyNamespace", currentNamespace); } [Fact] public void AnalyzeSourceCode_Should_Set_FileLineCount() { - // Arrange - var projectNamespaceMetrics = new Dictionary(); - var lines = new string[] + using (StringWriter consoleOutput = new()) { + Console.SetOut(consoleOutput); + + // 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); + } - // Assert - 3 lines only because comment lines are ignored - Assert.Equal(3, fileLineCount); } [Fact] public void AnalyzeSourceCode_Should_Set_FileCyclomaticComplexity() { - // Arrange - var projectNamespaceMetrics = new Dictionary(); - var lines = new string[] + using (StringWriter consoleOutput = new()) { + Console.SetOut(consoleOutput); + + // 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); + } - // Assert - Assert.Equal(1, fileCyclomaticComplexity); } [Fact] diff --git a/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs index d1f978e..c45dfbd 100644 --- a/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs +++ b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs @@ -2,14 +2,26 @@ namespace CodeLineCounter.Tests { - public class CodeDuplicationCheckerTests + public class CodeDuplicationCheckerTests : IDisposable { + private readonly string _testDirectory; + private bool _disposed; + + public CodeDuplicationCheckerTests() + { + _testDirectory = Path.Combine(Path.GetTempPath(), "CodeDuplicationCheckerTests"); + Directory.CreateDirectory(_testDirectory); + } + [Fact] public void DetectCodeDuplicationInFiles_ShouldDetectDuplicates() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange - var file1 = "TestFile1.cs"; - var file2 = "TestFile2.cs"; + var file1 = Path.Combine(_testDirectory, "TestFile1.cs"); + var file2 = Path.Combine(_testDirectory, "TestFile2.cs"); var code1 = @" public class TestClass @@ -58,10 +70,14 @@ public void AnotherTestMethod() [Fact] public void DetectCodeDuplicationInSourceCode_ShouldDetectDuplicates() { - // Arrange - var checker = new CodeDuplicationChecker(); + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); - var sourceCode1 = @" + // Arrange + var checker = new CodeDuplicationChecker(); + + var sourceCode1 = @" public class TestClass { public void TestMethod() @@ -73,7 +89,7 @@ public void TestMethod() } }"; - var sourceCode2 = @" + var sourceCode2 = @" public class AnotherTestClass { public void AnotherTestMethod() @@ -85,27 +101,33 @@ public void AnotherTestMethod() } }"; - var file1 = "TestFile3.cs"; - var file2 = "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 } [Fact] public void DetectCodeDuplicationInSourceCode_ShouldNotDetectDuplicatesForDifferentCode() { - // Arrange - var checker = new CodeDuplicationChecker(); + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); + + // Arrange + var checker = new CodeDuplicationChecker(); - var sourceCode1 = @" + var sourceCode1 = @" public class TestClass { public void TestMethod() @@ -117,7 +139,7 @@ public void TestMethod() } }"; - var sourceCode2 = @" + var sourceCode2 = @" public class AnotherTestClass { public void AnotherTestMethod() @@ -126,16 +148,45 @@ public void AnotherTestMethod() } }"; - var file1 = "TestFile5.cs"; - var file2 = "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 + } + + } + + protected virtual 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; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~CodeDuplicationCheckerTests() + { + Dispose(false); } } } diff --git a/CodeLineCounter.Tests/CoreUtilsTests.cs b/CodeLineCounter.Tests/CoreUtilsTests.cs index c5864eb..7c49806 100644 --- a/CodeLineCounter.Tests/CoreUtilsTests.cs +++ b/CodeLineCounter.Tests/CoreUtilsTests.cs @@ -1,21 +1,33 @@ using CodeLineCounter.Utils; +using CodeLineCounter.Models; namespace CodeLineCounter.Tests { - public class CoreUtilsTests + public class CoreUtilsTests : IDisposable { + + private readonly string _testDirectory; + private bool _disposed; + + public CoreUtilsTests() + { + _testDirectory = Path.Combine(Path.GetTempPath(), "CoreUtilsTests"); + Directory.CreateDirectory(_testDirectory); + } [Fact] public void ParseArguments_Should_Return_Correct_Values() { // Arrange - string[] args = ["-verbose", "-d", "testDirectory"]; + string[] args = ["-verbose", "-d", "testDirectory", "-output", _testDirectory]; + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); // Act - var (Verbose, DirectoryPath, _, _) = CoreUtils.ParseArguments(args); + Settings settings = CoreUtils.ParseArguments(args); // Assert - Assert.True(Verbose); - Assert.Equal("testDirectory", DirectoryPath); + Assert.True(settings.Verbose); + Assert.Equal("testDirectory", settings.DirectoryPath); } [Fact] @@ -23,12 +35,14 @@ public void ParseArguments_help_Should_Return_Correct_Values() { // Arrange string[] args = ["-help"]; + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); // Act - var (_, _, Help, _) = CoreUtils.ParseArguments(args); + var settings = CoreUtils.ParseArguments(args); // Assert - Assert.True(Help); + Assert.True(settings.Help); } [Fact] @@ -36,13 +50,15 @@ public void ParseArguments_Should_Return_Default_Values_When_No_Arguments_Passed { // Arrange string[] args = []; + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); // Act - var (Verbose, DirectoryPath, _, _) = CoreUtils.ParseArguments(args); + var settings = CoreUtils.ParseArguments(args); // Assert - Assert.False(Verbose); - Assert.Null(DirectoryPath); + Assert.False(settings.Verbose); + Assert.Null(settings.DirectoryPath); } [Fact] @@ -50,13 +66,15 @@ public void ParseArguments_Should_Ignore_Invalid_Arguments() { // Arrange string[] args = ["-invalid", "-d", "testDirectory", "-f", "json"]; + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); // Act - var (Verbose, DirectoryPath, _, _) = CoreUtils.ParseArguments(args); + var settings = CoreUtils.ParseArguments(args); // Assert - Assert.False(Verbose); - Assert.Equal("testDirectory", DirectoryPath); + Assert.False(settings.Verbose); + Assert.Equal("testDirectory", settings.DirectoryPath); } // ParseArguments correctly processes valid command line arguments with all options @@ -65,6 +83,8 @@ 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); @@ -73,7 +93,7 @@ public void ParseArguments_processes_valid_arguments_with_all_options() Assert.True(result.Verbose); Assert.Equal("C:/test", result.DirectoryPath); Assert.True(result.Help); - Assert.Equal(CoreUtils.ExportFormat.JSON, result.format); + Assert.Equal(CoreUtils.ExportFormat.JSON, result.Format); } // ParseArguments handles empty or null argument array @@ -82,6 +102,8 @@ 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); @@ -90,7 +112,7 @@ public void ParseArguments_handles_empty_argument_array() Assert.False(result.Verbose); Assert.Null(result.DirectoryPath); Assert.False(result.Help); - Assert.Equal(CoreUtils.ExportFormat.CSV, result.format); + Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); } // ParseArguments processes invalid format option gracefully @@ -99,15 +121,19 @@ public void ParseArguments_handles_invalid_format_option() { // Arrange string[] args = new[] { "-format", "INVALID" }; - var consoleOutput = new StringWriter(); - Console.SetOut(consoleOutput); + Settings result; + string sortieConsole; // Act - var result = CoreUtils.ParseArguments(args); + 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); + } - // Assert - Assert.Equal(CoreUtils.ExportFormat.CSV, result.format); - Assert.Contains("Invalid format", consoleOutput.ToString()); } [Fact] @@ -116,8 +142,8 @@ public void GetUserChoice_Should_Return_Valid_Choice() // Arrange int solutionCount = 5; string input = "3"; - var inputStream = new StringReader(input); - var consoleOutput = new StringWriter(); + using var inputStream = new StringReader(input); + using var consoleOutput = new StringWriter(); Console.SetOut(consoleOutput); Console.SetIn(inputStream); @@ -128,22 +154,50 @@ public void GetUserChoice_Should_Return_Valid_Choice() Assert.Equal(3, result); } + [Fact] + 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); + + // Act + int result = CoreUtils.GetUserChoice(solutionCount); + + // Assert + Assert.Equal(-1, result); + } + } + } + [Fact] public void GetUserChoice_Should_Return_Invalid_Choice() { // Arrange int solutionCount = 5; string input = "invalid"; - var inputStream = new StringReader(input); - var consoleOutput = new StringWriter(); - Console.SetOut(consoleOutput); - Console.SetIn(inputStream); + using (var inputStream = new StringReader(input)) + { + using (var consoleOutput = new StringWriter()) + { + Console.SetOut(consoleOutput); + Console.SetIn(inputStream); - // Act - int result = CoreUtils.GetUserChoice(solutionCount); + // Act + int result = CoreUtils.GetUserChoice(solutionCount); + + // Assert + Assert.Equal(-1, result); + } + + } - // Assert - Assert.Equal(-1, result); } // GetUserChoice returns valid selection when input is within range @@ -152,16 +206,22 @@ public void GetUserChoice_returns_valid_selection_for_valid_input() { // Arrange var input = "2"; - var consoleInput = new StringReader(input); - var consoleOutput = new StringWriter(); - Console.SetOut(consoleOutput); - Console.SetIn(consoleInput); + using (var consoleInput = new StringReader(input)) + { + using (var consoleOutput = new StringWriter()) + { + Console.SetOut(consoleOutput); + Console.SetIn(consoleInput); - // Act - int result = CoreUtils.GetUserChoice(3); + // Act + int result = CoreUtils.GetUserChoice(3); + + // Assert + Assert.Equal(2, result); + } + + } - // Assert - Assert.Equal(2, result); } [Theory] @@ -171,16 +231,22 @@ public void GetUserChoice_returns_valid_selection_for_valid_input() public void GetUserChoice_handles_invalid_input(string input) { // Arrange - var consoleInput = new StringReader(input); - var consoleOutput = new StringWriter(); - Console.SetOut(consoleOutput); - Console.SetIn(consoleInput); + using (var consoleInput = new StringReader(input)) + { + using (var consoleOutput = new StringWriter()) + { + Console.SetOut(consoleOutput); + Console.SetIn(consoleInput); + + // Act + int result = CoreUtils.GetUserChoice(5); + + // Assert + Assert.Equal(-1, result); + } + } - // Act - int result = CoreUtils.GetUserChoice(5); - // Assert - Assert.Equal(-1, result); } [Fact] @@ -197,43 +263,51 @@ public void DisplaySolutions_Should_Write_Solutions_To_Console() ]; // Redirect console output to a StringWriter - using StringWriter sw = new(); - Console.SetOut(sw); - - // Act - CoreUtils.DisplaySolutions(solutionFiles); - - // Assert - string expectedOutput = $"Available solutions:{envNewLine}"; - for (int i = 0; i < solutionFiles.Count; i++) + using (StringWriter sw = new()) { - expectedOutput += $"{i + 1}. {solutionFiles[i]}{envNewLine}"; + 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()); } - Assert.Equal(expectedOutput, sw.ToString()); + } [Fact] public void GetFilenamesList_Should_Return_List_Of_Filenames() { - // Arrange - List solutionFiles = - [ - "Solution1.sln", + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // 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); + ]; + Assert.Equal(expectedFilenames, result); + } + } @@ -241,60 +315,91 @@ public void GetFilenamesList_Should_Return_List_Of_Filenames() public void CheckSettings_WhenHelpIsTrue_ReturnsFalse() { // Arrange - (bool Verbose, string? DirectoryPath, bool Help, CoreUtils.ExportFormat format) settings = (true, null, true, CoreUtils.ExportFormat.JSON); - using var sw = new StringWriter(); - Console.SetOut(sw); + Settings settings = new Settings(true, null, ".", true, CoreUtils.ExportFormat.JSON); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); - // Act - var result = CoreUtils.CheckSettings(settings); + // Act + var result = settings.IsValid(); + + // Assert + Assert.True(result); + Assert.Contains("Usage:", sw.ToString()); + } - // Assert - Assert.False(result); - Assert.Contains("Usage:", sw.ToString()); } [Fact] public void CheckSettings_WhenDirectoryPathIsNull_ReturnsFalse() { // Arrange - (bool Verbose, string? DirectoryPath, bool Help, CoreUtils.ExportFormat format) settings = (Verbose: false, DirectoryPath: null, Help: false, format: CoreUtils.ExportFormat.CSV); - using var sw = new StringWriter(); - Console.SetOut(sw); + 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 = CoreUtils.CheckSettings(settings); + // Act + var result = settings.IsValid(); + + // Assert + Assert.False(result); + Assert.Contains("Please provide the directory path", sw.ToString()); + } - // Assert - Assert.False(result); - Assert.Contains("Please provide the directory path", sw.ToString()); } [Fact] public void CheckSettings_WhenSettingsAreValid_ReturnsTrue() { - // Arrange - (bool Verbose, string DirectoryPath, bool Help, CoreUtils.ExportFormat format) settings = (false, "some_directory", false, CoreUtils.ExportFormat.CSV); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + Settings settings = new Settings(false, "some_directory", false, CoreUtils.ExportFormat.CSV); - // Act - var result = CoreUtils.CheckSettings(settings); + // Act + var result = settings.IsValid(); + + // 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(); + + // Assert + Assert.True(result); + } - // Assert - Assert.True(result); } [Fact] public void CheckSettings_WhenSettingsAreInvalid_ReturnsFalse() { // Arrange - (bool Verbose, string? DirectoryPath, bool Help, CoreUtils.ExportFormat format) settings = (false, null, false, CoreUtils.ExportFormat.CSV); - using var sw = new StringWriter(); - Console.SetOut(sw); + Settings settings = new Settings(false, null, false, CoreUtils.ExportFormat.CSV); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); - // Act - var result = CoreUtils.CheckSettings(settings); + // Act + var result = settings.IsValid(); + + // Assert + Assert.False(result); + } - // Assert - Assert.False(result); } [Theory] @@ -305,12 +410,97 @@ 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) { - // Act - var result = CoreUtils.GetExportFileNameWithExtension(fileName, 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)); + } - // Assert - Assert.Contains(Path.GetFileNameWithoutExtension(fileName), result); - Assert.True(File.Exists(result) || !File.Exists(result)); + } + + // Returns list of filenames for existing files in input list + [Fact] + public void get_filenames_list_returns_filenames_for_existing_files() + { + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // 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"); + + // Act + var result = CoreUtils.GetFilenamesList(testFiles); + + // 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]); + } + + } + + // Directory creation succeeds when OutputPath is valid and does not exist + [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(); + + Assert.True(result); + Assert.True(Directory.Exists(settings.OutputPath)); + Directory.Delete(settings.OutputPath); + } + + } + + protected virtual 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; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~CoreUtilsTests() + { + Dispose(false); } } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/CsvHandlerTests.cs b/CodeLineCounter.Tests/CsvHandlerTests.cs index 2a01b51..03b9125 100644 --- a/CodeLineCounter.Tests/CsvHandlerTests.cs +++ b/CodeLineCounter.Tests/CsvHandlerTests.cs @@ -2,96 +2,157 @@ namespace CodeLineCounter.Tests { - public class CsvHandlerTests + public class CsvHandlerTests : IDisposable { + + private readonly string _testDirectory; + private bool _disposed; + private class TestRecord { public int Id { get; set; } public string? Name { get; set; } } + public CsvHandlerTests() + { + _testDirectory = Path.Combine(Path.GetTempPath(), "CsvHandlerTests"); + Directory.CreateDirectory(_testDirectory); + } + [Fact] public void Serialize_ValidData_WritesToFile() { - // Arrange - var data = new List + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); + + // Arrange + var data = new List { new() { Id = 1, Name = "Alice" }, new() { Id = 2, Name = "Bob" } }; - string filePath = "test_1.csv"; + string filePath = Path.Combine(_testDirectory, "test_1.csv"); + + // Act + CsvHandler.Serialize(data, filePath); - // Act - CsvHandler.Serialize(data, filePath); + // Assert + var lines = File.ReadAllLines(filePath); + Assert.Equal(3, lines.Length); // Header + 2 records + Assert.Contains("Alice", lines[1]); + Assert.Contains("Bob", lines[2]); - // 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); + } - // Cleanup - File.Delete(filePath); } [Fact] public void Deserialize_ValidFile_ReturnsData() { - // Arrange - string filePath = "test_2.csv"; - var data = new List + using (StringWriter consoleOutput = new()) { - "Id,Name", - "1,Alice", - "2,Bob" - }; - File.WriteAllLines(filePath, data); - - // Act - var result = CsvHandler.Deserialize(filePath).ToList(); + Console.SetOut(consoleOutput); + + // Arrange + string filePath = Path.Combine(_testDirectory, "test_2.csv"); + var data = new List + { + "Id,Name", + "1,Alice", + "2,Bob" + }; + File.WriteAllLines(filePath, data); + + // 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); + + // Cleanup + File.Delete(filePath); + } - // Assert - Assert.Equal(2, result.Count); - Assert.Equal("Alice", result[0].Name); - Assert.Equal("Bob", result[1].Name); - - // Cleanup - File.Delete(filePath); } [Fact] public void Serialize_EmptyData_WritesEmptyFile() { - // Arrange - var data = new List(); - string filePath = "test_3.csv"; + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); - // Act - CsvHandler.Serialize(data, filePath); + // Arrange + var data = new List(); + string filePath = Path.Combine(_testDirectory, "test_3.csv"); - // Assert - var lines = File.ReadAllLines(filePath); - Assert.Single(lines); // Only header + // Act + CsvHandler.Serialize(data, filePath); + + // Assert + var lines = File.ReadAllLines(filePath); + Assert.Single(lines); // Only header + + // Cleanup + File.Delete(filePath); + } - // Cleanup - File.Delete(filePath); } [Fact] public void Deserialize_EmptyFile_ReturnsEmptyList() { - // Arrange - string filePath = "test_4.csv"; - File.WriteAllText(filePath, "Id,Name"); + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); - // Act - var result = CsvHandler.Deserialize(filePath).ToList(); + // Arrange + string filePath = Path.Combine(_testDirectory, "test_4.csv"); + File.WriteAllText(filePath, "Id,Name"); - // Assert - Assert.Empty(result); + // Act + var result = CsvHandler.Deserialize(filePath).ToList(); - // Cleanup - File.Delete(filePath); + // Assert + Assert.Empty(result); + + // Cleanup + File.Delete(filePath); + } + + } + + protected virtual 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; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~CsvHandlerTests() + { + Dispose(false); } } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs b/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs index ebfd1c6..be937bc 100644 --- a/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs +++ b/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs @@ -9,8 +9,11 @@ public class CyclomaticComplexityCalculatorTests [Fact] public void TestCalculateComplexity() { - // Arrange - var code = @" + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + var code = @" public class TestClass { public void TestMethod() @@ -19,21 +22,26 @@ public void TestMethod() for (int i = 0; i < 10; i++) {} } } - "; - 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) + } - // Assert - Assert.Equal(3, complexity); // 1 (default) + 1 (if) + 1 (for) } [Fact] public void Calculate_Should_Return_Correct_Complexity() { - // Arrange - var syntaxTree = CSharpSyntaxTree.ParseText(@" + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + var syntaxTree = CSharpSyntaxTree.ParseText(@" public class MyClass { public void MyMethod() @@ -48,54 +56,61 @@ 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); + } - // Assert - Assert.Equal(3, complexity); } [Fact] - public void Calculate_Should_Return_Correct_Complexity_6() + public void Calculate_Should_Return_Correct_Complexity_6() { - // Arrange - var syntaxTree = CSharpSyntaxTree.ParseText(@" - class Program + using (var sw = new StringWriter()) { - static void Main(string[] args) + Console.SetOut(sw); + // Arrange + var syntaxTree = CSharpSyntaxTree.ParseText(@" + class Program { - int x = 5; - - switch (x) + static void Main(string[] args) { - case 1: - Console.WriteLine(1); - break; - case 2: - Console.WriteLine(2); - break; - case 3: - Console.WriteLine(3); - break; - case 4: - Console.WriteLine(4); - break; + int x = 5; + + switch (x) + { + case 1: + Console.WriteLine(1); + break; + case 2: + Console.WriteLine(2); + break; + case 3: + Console.WriteLine(3); + break; + case 4: + Console.WriteLine(4); + break; + } } } - } - "); - 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(6, complexity); + } - // Assert - Assert.Equal(6, complexity); } } } diff --git a/CodeLineCounter.Tests/DataExporterTests.cs b/CodeLineCounter.Tests/DataExporterTests.cs index fa50c5c..0f80e6e 100644 --- a/CodeLineCounter.Tests/DataExporterTests.cs +++ b/CodeLineCounter.Tests/DataExporterTests.cs @@ -1,6 +1,5 @@ using CodeLineCounter.Models; using CodeLineCounter.Utils; -using Moq; namespace CodeLineCounter.Tests { @@ -20,15 +19,20 @@ public DataExporterTests() [InlineData(CoreUtils.ExportFormat.JSON, ".json")] public void Export_SingleItem_CreatesFileWithCorrectExtension(CoreUtils.ExportFormat format, string expectedExtension) { - // Arrange - var testItem = new TestClass { Id = 1, Name = "Test" }; - var filePath = Path.Combine(_testDirectory, "test"); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + var testItem = new TestClass { Id = 1, Name = "Test" }; + var filePath = "test"; + + // Act + DataExporter.Export(filePath, _testDirectory, testItem, format); - // Act - DataExporter.Export(filePath, testItem, format); + // Assert + Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + expectedExtension))); + } - // Assert - Assert.True(File.Exists(filePath + expectedExtension)); } [Theory] @@ -36,239 +40,313 @@ public void Export_SingleItem_CreatesFileWithCorrectExtension(CoreUtils.ExportFo [InlineData(CoreUtils.ExportFormat.JSON, ".json")] public void ExportCollection_WithMultipleItems_CreatesFile(CoreUtils.ExportFormat format, string expectedExtension) { - // Arrange - var items = new List + using (var sw = new StringWriter()) { - new() { Id = 1, Name = "Test1" }, - new() { Id = 2, Name = "Test2" } - }; - var filePath = Path.Combine(_testDirectory, "collection"); + Console.SetOut(sw); + // Arrange + var items = new List + { + new() { Id = 1, Name = "Test1" }, + new() { Id = 2, Name = "Test2" } + }; + var filePath = Path.Combine(_testDirectory, "collection"); + + // Act + DataExporter.ExportCollection(filePath, _testDirectory, items, format); - // Act - DataExporter.ExportCollection(filePath, items, format); + // Assert + Assert.True(File.Exists(filePath + expectedExtension)); + } - // Assert - Assert.True(File.Exists(filePath + expectedExtension)); } [Fact] public void ExportMetrics_CreatesFileWithCorrectData() { - // Arrange - var metrics = new List - { - new() { ProjectName = "Project1", LineCount = 100 }, - new() { ProjectName = "Project2", LineCount = 200 } - }; - var projectTotals = new Dictionary + using (var sw = new StringWriter()) { - { "Project1", 100 }, - { "Project2", 200 } - }; - var duplications = new List(); - var filePath = Path.Combine(_testDirectory, "metrics"); - - // Act - DataExporter.ExportMetrics(filePath, metrics, projectTotals, 300, duplications, null, CoreUtils.ExportFormat.CSV); + Console.SetOut(sw); + // Arrange + var metrics = new List + { + new() { ProjectName = "Project1", LineCount = 100 }, + new() { ProjectName = "Project2", LineCount = 200 } + }; + 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" + }; + + // Act + DataExporter.ExportMetrics(filePath, _testDirectory, result, ".", CoreUtils.ExportFormat.CSV); + + // Assert + Assert.True(File.Exists(Path.Combine(_testDirectory,filePath + ".csv"))); + } - // Assert - Assert.True(File.Exists(filePath + ".csv")); } [Fact] public void ExportDuplications_CreatesFileWithCorrectData() { - // Arrange - var duplications = new List + using (var sw = new StringWriter()) { - 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 = Path.Combine(_testDirectory, "duplications"); + Console.SetOut(sw); + // 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"; + + // Act + DataExporter.ExportDuplications(filePath, _testDirectory, duplications, CoreUtils.ExportFormat.CSV); - // Act - DataExporter.ExportDuplications(filePath, duplications, CoreUtils.ExportFormat.CSV); + // Assert + Assert.True(File.Exists(Path.Combine(_testDirectory,filePath + ".csv"))); + } - // Assert - Assert.True(File.Exists(filePath + ".csv")); } [Fact] public void get_duplication_counts_returns_correct_counts_for_duplicate_paths() { - var duplications = new List + using (var sw = new StringWriter()) { - new DuplicationCode { FilePath = "file1.cs" }, - new DuplicationCode { FilePath = "file1.cs" }, - new DuplicationCode { FilePath = "file2.cs" }, - new DuplicationCode { FilePath = "file1.cs" } - }; + Console.SetOut(sw); + var duplications = new List + { + new DuplicationCode { FilePath = "file1.cs" }, + new DuplicationCode { FilePath = "file1.cs" }, + new DuplicationCode { FilePath = "file2.cs" }, + 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")]); + } - 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() { - var duplications = new List(); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + var duplications = new List(); - var result = DataExporter.GetDuplicationCounts(duplications); + var result = DataExporter.GetDuplicationCounts(duplications); + + Assert.Empty(result); + } - Assert.Empty(result); } [Fact] public void get_duplication_counts_handles_absolute_paths() { - var absolutePath = Path.GetFullPath(@"C:\test\file.cs"); - var duplications = new List + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + 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]); } [Fact] public void get_file_duplications_count_returns_correct_count_when_path_exists() { - var duplicationCounts = new Dictionary - { - { Path.Combine(Path.GetFullPath("."), "test.cs"), 5 } - }; + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + 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); + } - Assert.Equal(5, result); } [Fact] public void get_file_duplications_count_returns_zero_when_path_not_found() { - var duplicationCounts = new Dictionary(); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + 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); + } - Assert.Equal(0, result); } [Fact] public void get_file_duplications_count_returns_zero_when_filepath_null() { - var duplicationCounts = new Dictionary + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + 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); + } - Assert.Equal(0, result); } // Handles empty string solutionPath by using current directory [Fact] public void get_file_duplications_count_uses_current_dir_for_empty_solution_path() { - var expectedPath = Path.Combine(Path.GetFullPath("."), "test.cs"); - var duplicationCounts = new Dictionary + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + 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); + } - Assert.Equal(3, result); } // Handles null solutionPath by using current directory [Fact] public void get_file_duplications_count_uses_current_dir_for_null_solution_path() { - var expectedPath = Path.Combine(Path.GetFullPath("."), "test.cs"); - var duplicationCounts = new Dictionary + using (var sw = new StringWriter()) { - { expectedPath, 4 } - }; + Console.SetOut(sw); + 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); + } - Assert.Equal(4, result); } // Successfully exports dependencies to specified format (CSV/JSON) and creates DOT file [Fact] public async Task export_dependencies_creates_files_in_correct_formats() { - // Arrange - var dependencies = new List + using (var sw = new StringWriter()) { - new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "file1.cs", StartLine = 10 }, - }; + Console.SetOut(sw); + // 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"; - var format = CoreUtils.ExportFormat.JSON; + var testFilePath = "test_export.dot"; + var format = CoreUtils.ExportFormat.JSON; - // Act - await DataExporter.ExportDependencies(testFilePath, dependencies, format); + // Act + await DataExporter.ExportDependencies(testFilePath, _testDirectory, dependencies, format); - // Assert - string expectedJsonPath = 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)) + try { - File.Delete(expectedJsonPath); + if (File.Exists(expectedJsonPath)) + { + File.Delete(expectedJsonPath); + } + + if (File.Exists(expectedDotPath)) + { + File.Delete(expectedDotPath); + } } - - if (File.Exists(expectedDotPath)) + catch (IOException ex) { - File.Delete(expectedDotPath); + throw new IOException($"Error deleting files: {ex.Message}", ex); + } + catch (UnauthorizedAccessException ex) + { + throw new UnauthorizedAccessException($"Access denied while deleting files: {ex.Message}", ex); } } - 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() { - // 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, testData, format)); - Assert.Equal("File path cannot be null or empty (Parameter 'filePath')", exception.Message); + 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); + } + } protected virtual void Dispose(bool disposing) diff --git a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs index a3c5eb1..6631aef 100644 --- a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs +++ b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs @@ -5,124 +5,197 @@ namespace CodeLineCounter.Tests { - public class DependencyGraphGeneratorTests + public class DependencyGraphGeneratorTests : IDisposable { + + private readonly string _testDirectory; + private bool _disposed; + + public DependencyGraphGeneratorTests() + { + _testDirectory = Path.Combine(Path.GetTempPath(), "DependencyGraphGeneratorTests"); + Directory.CreateDirectory(_testDirectory); + } [Fact] public async Task generate_graph_with_valid_dependencies_creates_dot_file() { - // Arrange - var dependencies = new List + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + + // 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(Path.GetTempPath(), "test_graph.dot"); + string fileName = "test_graph.dot"; + + string outputPath = Path.Combine(_testDirectory, fileName); + + // Act - // Act + DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); + Directory.CreateDirectory(_testDirectory); + await DependencyGraphGenerator.CompileGraphAndWriteToFile(fileName, _testDirectory, graph); - DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); - await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, 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); - // Assert - Assert.True(File.Exists(outputPath)); - string content = await File.ReadAllTextAsync(outputPath); - Assert.Contains("ClassA", content); - Assert.Contains("ClassB", content); - Assert.Contains("ClassC", content); + File.Delete(outputPath); + } - File.Delete(outputPath); } // Empty dependencies list [Fact] public async Task generate_graph_with_empty_dependencies_creates_empty_graph() { - // Arrange - var dependencies = new List(); - string outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot"); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + + // 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(outputPath, 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); + + File.Delete(outputPath); + } - File.Delete(outputPath); } [Fact] public void create_node_sets_correct_fillcolor_and_style_incoming_greater() { - var vertexInfo = new Dictionary + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + 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); + } - 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() { - var vertexInfo = new Dictionary + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + 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); } // Returns empty quoted string '""' for non-empty input string [Fact] public void enclose_string_in_quotes_returns_empty_quoted_string_for_nonempty_input() { - // Arrange - var input = "test string"; + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + var input = "test string"; + + // Act + var result = DependencyGraphGenerator.EncloseNotEmptyOrNullStringInQuotes(input); - // Act - var result = DependencyGraphGenerator.EncloseNotEmptyOrNullStringInQuotes(input); + // Assert + Assert.Equal("\"test string\"", result); + } - // Assert - Assert.Equal("\"test string\"", result); } // Returns quoted string with null value for null input [Fact] public void enclose_string_in_quotes_returns_quoted_null_for_null_input() { - // Arrange - string? input = null; + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + string? input = null; + + // Act + var result = DependencyGraphGenerator.EncloseNotEmptyOrNullStringInQuotes(input); - // Act - var result = DependencyGraphGenerator.EncloseNotEmptyOrNullStringInQuotes(input); + // Assert + Assert.Equal(string.Empty, result); + } - // Assert - Assert.Equal(string.Empty, result); } // Graph initialization with empty values [Fact] public void initialize_graph_sets_default_label_and_identifier() { - var graph = DependencyGraphGenerator.InitializeGraph(); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + var graph = DependencyGraphGenerator.InitializeGraph(); + + Assert.Equal("DependencyGraph", graph.Label.Value); + Assert.Equal("DependencyGraph", graph.Identifier.Value); + } - Assert.Equal("DependencyGraph", graph.Label.Value); - Assert.Equal("DependencyGraph", graph.Identifier.Value); } + protected virtual 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; + } + } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + ~DependencyGraphGeneratorTests() + { + Dispose(false); + } } } diff --git a/CodeLineCounter.Tests/FileUtilsTests.cs b/CodeLineCounter.Tests/FileUtilsTests.cs index ea4c5f0..3465949 100644 --- a/CodeLineCounter.Tests/FileUtilsTests.cs +++ b/CodeLineCounter.Tests/FileUtilsTests.cs @@ -2,58 +2,113 @@ namespace CodeLineCounter.Tests { - public class FileUtilsTests + public class FileUtilsTests : IDisposable { + private readonly string _testDirectory; + private bool _disposed; + + public FileUtilsTests() + { + _testDirectory = Path.Combine(Path.GetTempPath(), "DependencyGraphGeneratorTests"); + Directory.CreateDirectory(_testDirectory); + } [Fact] public void GetSolutionFiles_Should_Return_List_Of_Solution_Files() { - // Arrange - var basePath = FileUtils.GetBasePath(); - var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); - var rootPath = solutionPath; + 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); + // Act + var result = FileUtils.GetSolutionFiles(rootPath); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result); + Assert.All(result, file => Assert.EndsWith(".sln", file)); + } - // Assert - Assert.NotNull(result); - Assert.NotEmpty(result); - Assert.All(result, file => Assert.EndsWith(".sln", file)); } [Fact] public void GetBasePath_Should_Return_NonEmptyString() { - // Act - string basePath = FileUtils.GetBasePath(); + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); + // 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."); + } - // 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() { - // Arrange - var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); + // Arrange + var nonExistentPath = Path.Combine(_testDirectory, Guid.NewGuid().ToString()); + + // Act & Assert + Assert.Throws(() => FileUtils.GetSolutionFiles(nonExistentPath)); + } - // 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() { - // Arrange - var nonExistentPath = "nonexistent.sln"; + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); + // Arrange + var nonExistentPath = Path.Combine(_testDirectory, "nonexistent.sln"); + + // Act & Assert + var exception = Assert.Throws(() => + FileUtils.GetProjectFiles(nonExistentPath)); + + Assert.Contains(nonExistentPath, exception.Message); + } + + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing && Directory.Exists(_testDirectory)) + { + // Dispose managed resources + Directory.Delete(_testDirectory, true); + } - // Act & Assert - var exception = Assert.Throws(() => - FileUtils.GetProjectFiles(nonExistentPath)); + // Dispose unmanaged resources (if any) + + _disposed = true; + } + } - Assert.Contains(nonExistentPath, exception.Message); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~FileUtilsTests() + { + Dispose(false); } } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/HashUtilsTests.cs b/CodeLineCounter.Tests/HashUtilsTests.cs index 8d99b04..02bcd8a 100644 --- a/CodeLineCounter.Tests/HashUtilsTests.cs +++ b/CodeLineCounter.Tests/HashUtilsTests.cs @@ -7,56 +7,80 @@ public class HashUtilsTests [Fact] public void ComputeHash_EmptyString_ReturnsEmptyString() { - // Arrange - string input = ""; + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); - // Act - string result = HashUtils.ComputeHash(input); + // Arrange + string input = ""; + + // Act + string result = HashUtils.ComputeHash(input); + + // Assert + Assert.Equal("", result); + } - // Assert - Assert.Equal("", result); } [Fact] public void ComputeHash_NullString_ReturnsEmptyString() { - // Arrange - string? input = null; + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); + + // Arrange + string? input = null; + + // Act + string result = HashUtils.ComputeHash(input); - // Act - string result = HashUtils.ComputeHash(input); + // Assert + Assert.Equal("", result); + } - // Assert - Assert.Equal("", result); } [Fact] public void ComputeHash_ValidString_ReturnsHash() { - // Arrange - string input = "Hello, World!"; + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); - // Act - string result = HashUtils.ComputeHash(input); + // Arrange + string input = "Hello, World!"; + + // Act + string result = HashUtils.ComputeHash(input); + + // Assert + Assert.NotEmpty(result); + Assert.IsType(result); + } - // Assert - Assert.NotEmpty(result); - Assert.IsType(result); } [Fact] public void ComputeHash_DuplicateStrings_ReturnSameHash() { - // Arrange - string input1 = "Hello, World!"; - string input2 = "Hello, World!"; + 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); - // Act - string result1 = HashUtils.ComputeHash(input1); - string result2 = HashUtils.ComputeHash(input2); + // Assert + Assert.Equal(result1, result2); + } - // Assert - Assert.Equal(result1, result2); } } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/JsonHandlerTests.cs b/CodeLineCounter.Tests/JsonHandlerTests.cs index 5d90291..a78f08c 100644 --- a/CodeLineCounter.Tests/JsonHandlerTests.cs +++ b/CodeLineCounter.Tests/JsonHandlerTests.cs @@ -3,8 +3,18 @@ namespace CodeLineCounter.Tests { - public class JsonHandlerTests + public class JsonHandlerTests : IDisposable { + + private readonly string _testDirectory; + private bool _disposed; + + public JsonHandlerTests() + { + _testDirectory = Path.Combine(Path.GetTempPath(), "JsonHandlerTests"); + Directory.CreateDirectory(_testDirectory); + } + public class TestClass { public int Id { get; set; } @@ -20,20 +30,54 @@ public TestClass(int id, string name) [Fact] public void deserialize_valid_json_file_returns_expected_objects() { - // Arrange - var testFilePath = "test.json"; - var expectedData = new[] { new TestClass(id: 1, name: "Test") }; - File.WriteAllText(testFilePath, JsonSerializer.Serialize(expectedData)); + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); - // Act - var result = JsonHandler.Deserialize(testFilePath); + // Arrange + var testFilePath = Path.Combine(_testDirectory, "test.json"); + var expectedData = new[] { new TestClass(id: 1, name: "Test") }; + File.WriteAllText(testFilePath, JsonSerializer.Serialize(expectedData)); - // Assert - Assert.Equal(expectedData.Length, result.Count()); - Assert.Equal(expectedData[0].Id, result.First().Id); - Assert.Equal(expectedData[0].Name, result.First().Name); + // 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); + + } + + } - File.Delete(testFilePath); + protected virtual 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; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~JsonHandlerTests() + { + Dispose(false); } } diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index 5c8f71d..c99261e 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -4,41 +4,93 @@ namespace CodeLineCounter.Tests.Services { - public class SolutionAnalyzerTest + public class SolutionAnalyzerTest : IDisposable { + private readonly string _testDirectory; + private readonly string _testSolutionPath; + private readonly string _outputPath; + private bool _disposed; + + public SolutionAnalyzerTest() + { + _testDirectory = Path.Combine(Path.GetTempPath(), "SolutionAnalyzerTest"); + _testSolutionPath = Path.Combine(_testDirectory, "TestSolution.sln"); + _outputPath = _testDirectory; + Directory.CreateDirectory(_testDirectory); + // Create minimal test solution if it doesn't exist + if (!File.Exists(_testSolutionPath)) + { + File.WriteAllText(_testSolutionPath, @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""TestProject"", ""TestProject.csproj"", ""{F60AAFF8-A3B6-4D3C-9372-06A0F1B6F82B}"" +EndProject"); + } + } + [Fact] public void analyze_and_export_solution_succeeds_with_valid_inputs() { // Arrange - var basePath = FileUtils.GetBasePath(); - var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); - solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln"); - var sw = new StringWriter(); - Console.SetOut(sw); - var verbose = false; - var format = CoreUtils.ExportFormat.JSON; + var solutionPath = _testSolutionPath; + var verbose = true; + var format = CoreUtils.ExportFormat.CSV; + var outputPath = _outputPath; - // Act & Assert - var exception = Record.Exception(() => - SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, verbose, format)); + try + { + // Redirect console output + using (StringWriter stringWriter = new StringWriter()) + { + + Console.SetOut(stringWriter); + + // 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.Null(exception); + } + finally + { + // Cleanup + + if (File.Exists(solutionPath)) + { + File.Delete(solutionPath); + } + } } [Fact] public void analyze_and_export_solution_throws_on_invalid_path() { - // Arrange - var invalidPath = ""; - var verbose = false; - var format = CoreUtils.ExportFormat.JSON; + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + + var invalidPath = ""; + var verbose = false; + var format = CoreUtils.ExportFormat.JSON; - // Act & Assert - var exception = Assert.Throws(() => + // Act & Assert + var exception = Assert.Throws(() => SolutionAnalyzer.AnalyzeAndExportSolution(invalidPath, verbose, format)); - Assert.Contains("Access to the path '' is denied.", exception.Message); + Assert.Contains("Access to the path '' is denied.", exception.Message); + + } + } [Fact] @@ -48,42 +100,45 @@ public void PerformAnalysis_ShouldReturnCorrectAnalysisResult() var basePath = FileUtils.GetBasePath(); var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln"); - 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."); + 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); + // Act + var result = SolutionAnalyzer.PerformAnalysis(solutionPath); + + // Assert + Assert.NotNull(result); + Assert.Equal("CodeLineCounter.sln", result.SolutionFileName); + + } - // Assert - Assert.NotNull(result); - Assert.Equal("CodeLineCounter.sln", result.SolutionFileName); } [Fact] public void OutputAnalysisResults_ShouldPrintCorrectOutput() { - // 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; - using (var sw = new StringWriter()) { 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; // Act SolutionAnalyzer.OutputAnalysisResults(result, verbose); @@ -101,40 +156,39 @@ public void OutputAnalysisResults_ShouldPrintCorrectOutput() [Fact] public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals() { - // Arrange - var metrics = new List - { - new NamespaceMetrics - { - ProjectName = "Project1", - ProjectPath = "/path/to/project1", - NamespaceName = "Namespace1", - FileName = "File1.cs", - FilePath = "/path/to/project1/File1.cs", - LineCount = 100, - CyclomaticComplexity = 10 - }, - new NamespaceMetrics - { - ProjectName = "Project2", - ProjectPath = "/path/to/project2", - NamespaceName = "Namespace2", - FileName = "File2.cs", - FilePath = "/path/to/project2/File2.cs", - LineCount = 200, - CyclomaticComplexity = 20 - } - }; - - var projectTotals = new Dictionary - { - { "Project1", 100 }, - { "Project2", 200 } - }; - using (var sw = new StringWriter()) { Console.SetOut(sw); + // Arrange + var metrics = new List + { + new NamespaceMetrics + { + ProjectName = "Project1", + ProjectPath = "/path/to/project1", + NamespaceName = "Namespace1", + FileName = "File1.cs", + FilePath = "/path/to/project1/File1.cs", + LineCount = 100, + CyclomaticComplexity = 10 + }, + new NamespaceMetrics + { + ProjectName = "Project2", + ProjectPath = "/path/to/project2", + NamespaceName = "Namespace2", + FileName = "File2.cs", + FilePath = "/path/to/project2/File2.cs", + LineCount = 200, + CyclomaticComplexity = 20 + } + }; + + var projectTotals = new Dictionary + { + { "Project1", 100 }, + { "Project2", 200 } + }; // Act SolutionAnalyzer.OutputDetailedMetrics(metrics, projectTotals); @@ -154,34 +208,62 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals() [Fact] public void export_results_with_valid_input_exports_all_files() { - // Arrange - var result = new AnalysisResult + // Act + using (var sw = new StringWriter()) { - SolutionFileName = "TestSolution", - Metrics = new List(), - ProjectTotals = new Dictionary(), - TotalLines = 1000, - DuplicationMap = new List(), - DependencyList = new List() - }; + // Arrange + var result = new AnalysisResult + { + SolutionFileName = "CodelineCounter.sln", + Metrics = new List(), + ProjectTotals = new Dictionary(), + TotalLines = 1000, + DuplicationMap = new List(), + DependencyList = new List() + }; - var basePath = FileUtils.GetBasePath(); - var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); + var basePath = FileUtils.GetBasePath(); + var baseSolutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); - solutionPath = Path.Combine(solutionPath, "TestSolution.sln"); - var format = CoreUtils.ExportFormat.CSV; + var solutionPath = Path.Combine(baseSolutionPath, "CodelineCounter.sln"); + var format = CoreUtils.ExportFormat.CSV; + Console.SetOut(sw); + SolutionAnalyzer.ExportResults(result, solutionPath, format, baseSolutionPath); + // Assert + Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter-CodeMetrics.csv"))); + Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter-CodeDuplications.csv"))); + Assert.True(File.Exists(Path.Combine(baseSolutionPath, "CodelineCounter-Dependencies.dot"))); + } - // Act - using (var sw = new StringWriter()) + + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) { - Console.SetOut(sw); - SolutionAnalyzer.ExportResults(result, solutionPath, format); + if (disposing && Directory.Exists(_testDirectory)) + { + // Dispose managed resources + File.Delete(_testSolutionPath); + Directory.Delete(_testDirectory, true); + } + + // Dispose unmanaged resources (if any) + + _disposed = true; } + } - // Assert - Assert.True(File.Exists("TestSolution-CodeMetrics.csv")); - Assert.True(File.Exists("TestSolution-CodeDuplications.csv")); - Assert.True(File.Exists("TestSolution-CodeDependencies.csv")); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SolutionAnalyzerTest() + { + Dispose(false); } } diff --git a/CodeLineCounter/Models/Settings.cs b/CodeLineCounter/Models/Settings.cs new file mode 100644 index 0000000..c182d02 --- /dev/null +++ b/CodeLineCounter/Models/Settings.cs @@ -0,0 +1,71 @@ +using CodeLineCounter.Utils; + +namespace CodeLineCounter.Models +{ + public class Settings + { + + public bool Verbose { get; set; } + public string? DirectoryPath { get; set; } + public string? OutputPath { get; set; } + public bool Help { get; set; } + public CoreUtils.ExportFormat Format { get; set; } + + public Settings() + { + Format = CoreUtils.ExportFormat.CSV; + Help = false; + Verbose = false; + OutputPath = Directory.GetCurrentDirectory(); + } + + public Settings(bool verbose, string? directoryPath, string? outputPath, bool help, CoreUtils.ExportFormat format) + { + Verbose = verbose; + DirectoryPath = directoryPath; + OutputPath = outputPath; + Help = help; + Format = format; + } + + + public Settings(bool verbose, string? directoryPath, bool help, CoreUtils.ExportFormat format) + { + Verbose = verbose; + DirectoryPath = directoryPath; + OutputPath = OutputPath = Directory.GetCurrentDirectory(); + Help = help; + Format = format; + } + + public bool IsValid() + { + if (Help) + { + Console.WriteLine("Usage: CodeLineCounter.exe [-verbose] [-d ] [-output ] [-help, -h] (-format )"); + return true; + } + + if (DirectoryPath == null) + { + Console.WriteLine("Please provide the directory path containing the solutions to analyze using the -d switch."); + return false; + } + + if (OutputPath != null && !Directory.Exists(OutputPath)) + { + try + { + Directory.CreateDirectory(OutputPath); + } + catch (Exception) + { + Console.WriteLine($"Cannot create or access output directory: {OutputPath}"); + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/CodeLineCounter/Program.cs b/CodeLineCounter/Program.cs index 5924d6e..fc74e10 100644 --- a/CodeLineCounter/Program.cs +++ b/CodeLineCounter/Program.cs @@ -8,7 +8,7 @@ static class Program static void Main(string[] args) { var settings = CoreUtils.ParseArguments(args); - if (!CoreUtils.CheckSettings(settings) || settings.DirectoryPath == null) + if (!settings.IsValid() || settings.DirectoryPath == null) return; // file deepcode ignore PT: Not a web server. This software is a console application. @@ -23,7 +23,7 @@ static void Main(string[] args) // if more than one waiting for user selection if (solutionFiles.Count == 1) { - SolutionAnalyzer.AnalyzeAndExportSolution(solutionFiles[0], settings.Verbose, settings.format); + SolutionAnalyzer.AnalyzeAndExportSolution(solutionFiles[0], settings.Verbose, settings.Format, settings.OutputPath); return; } @@ -35,7 +35,7 @@ static void Main(string[] args) return; var solutionPath = Path.GetFullPath(solutionFiles[choice - 1]); - SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, settings.Verbose, settings.format); + SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, settings.Verbose, settings.Format, settings.OutputPath); } } } \ No newline at end of file diff --git a/CodeLineCounter/Services/DependencyGraphGenerator.cs b/CodeLineCounter/Services/DependencyGraphGenerator.cs index 21a1b8c..f96ebac 100644 --- a/CodeLineCounter/Services/DependencyGraphGenerator.cs +++ b/CodeLineCounter/Services/DependencyGraphGenerator.cs @@ -163,7 +163,7 @@ private static void GroupByNamespace(Dictionary DataExporter.ExportMetrics( - metricsOutputFilePath, - result.Metrics, - result.ProjectTotals, - result.TotalLines, - result.DuplicationMap, + metricsFileName, + outputPath ??".", + result, solutionPath, format), () => DataExporter.ExportDuplications( - duplicationOutputFilePath, + duplicationsFileName, + outputPath ?? ".", result.DuplicationMap, format), async () => await DataExporter.ExportDependencies( - dependenciesOutputFilePath, + graphFileName, + outputPath ?? ".", result.DependencyList, format) ); - Console.WriteLine($"The data has been exported to {metricsOutputFilePath}"); - Console.WriteLine($"The code duplications have been exported to {duplicationOutputFilePath}"); - Console.WriteLine($"The code dependencies have been exported to {dependenciesOutputFilePath} and the graph has been generated. (dot file can be found in the same directory)"); + Console.WriteLine($"The data has been exported to {metricsOutputPath}"); + Console.WriteLine($"The code duplications have been exported to {duplicationsOutputPath}"); + Console.WriteLine($"The code dependencies have been exported to {graphOutputPath} and the graph has been generated. (dot file can be found in the same directory)"); } catch (AggregateException ae) { diff --git a/CodeLineCounter/Utils/CoreUtils.cs b/CodeLineCounter/Utils/CoreUtils.cs index bde916b..539b94c 100644 --- a/CodeLineCounter/Utils/CoreUtils.cs +++ b/CodeLineCounter/Utils/CoreUtils.cs @@ -1,3 +1,4 @@ +using CodeLineCounter.Models; namespace CodeLineCounter.Utils { @@ -8,57 +9,73 @@ public enum ExportFormat CSV, JSON } - public static (bool Verbose, string? DirectoryPath, bool Help, ExportFormat format) ParseArguments(string[] args) + + public static Settings ParseArguments(string[] args) { - bool verbose = false; - bool help = false; - ExportFormat format = ExportFormat.CSV; - string? directoryPath = null; + var settings = new Settings(); int argIndex = 0; + while (argIndex < args.Length) { switch (args[argIndex]) { case "-help": - help = true; - argIndex++; + settings.Help = true; break; case "-verbose": - verbose = true; - argIndex++; + settings.Verbose = true; break; case "-d": - if (argIndex + 1 < args.Length) - { - directoryPath = args[argIndex + 1]; - argIndex += 2; // Increment by 2 to skip the next argument - } - else - { - argIndex++; // Increment by 1 if there's no next argument - } + HandleDirectory(args, settings, ref argIndex); break; case "-format": - if (argIndex + 1 < args.Length) - { - string formatString = args[argIndex + 1]; - argIndex += 2; // Increment by 2 to skip the next argument - if (Enum.TryParse(formatString, true, out ExportFormat result)) - { - format = result; - } - else - { - Console.WriteLine($"Invalid format: {formatString}. Valid formats are: {string.Join(", ", Enum.GetNames())}. Using default format {format}"); - } - } + HandleFormat(args, settings, ref argIndex); + break; + case "-output": + HandleOutput(args, settings, ref argIndex); break; default: - argIndex++; break; } + argIndex++; + } + return settings; + } + + private static void HandleDirectory(string[] args, Settings settings, ref int argIndex) + { + if (argIndex + 1 < args.Length) + { + settings.DirectoryPath = args[argIndex + 1]; + argIndex++; + } + + } + + private static void HandleFormat(string[] args, Settings settings, ref int argIndex) + { + if (argIndex + 1 < args.Length) + { + string formatString = args[argIndex + 1]; + argIndex++; + if (Enum.TryParse(formatString, true, out ExportFormat result)) + { + settings.Format = result; + } + else + { + Console.WriteLine($"Invalid format: {formatString}. Valid formats are: {string.Join(", ", Enum.GetNames())}. Using default format {settings.Format}"); + } + } + } + + private static void HandleOutput(string[] args, Settings settings, ref int argIndex) + { + if (argIndex + 1 < args.Length) + { + settings.OutputPath = args[argIndex + 1]; + argIndex++; } - return (verbose, directoryPath, help, format); } /// @@ -121,49 +138,42 @@ public static void DisplaySolutions(List solutionFiles) } } - public static bool CheckSettings((bool Verbose, string? DirectoryPath, bool Help, ExportFormat format) settings) + public static string GetExportFileNameWithExtension(string filePath, ExportFormat format) { - if (settings.Help) + if (filePath == null) { - Console.WriteLine("Usage: CodeLineCounter.exe [-verbose] [-d ] [-help, -h] (-format )"); - return false; + filePath = "export."; } - - if (settings.DirectoryPath == null) - { - Console.WriteLine("Please provide the directory path containing the solutions to analyze using the -d switch."); - return false; - } - - return true; - } - - public static string GetExportFileNameWithExtension(string filePath, CoreUtils.ExportFormat format) - { + string fileName = Path.GetFileName(filePath); + string newExtension = format switch { - CoreUtils.ExportFormat.CSV => ".csv", - CoreUtils.ExportFormat.JSON => ".json", + ExportFormat.CSV => ".csv", + ExportFormat.JSON => ".json", _ => throw new ArgumentException($"Unsupported format: {format}", nameof(format)) }; string currentExtension = Path.GetExtension(filePath); - // If the file already has the desired extension (case-insensitive) - if (currentExtension.Equals(newExtension, StringComparison.OrdinalIgnoreCase)) + // If file already has the desired extension, keep it, otherwise change it + if (!currentExtension.Equals(newExtension, StringComparison.OrdinalIgnoreCase)) { - return filePath; + if (!string.IsNullOrEmpty(currentExtension)) + { + fileName = Path.ChangeExtension(fileName, newExtension); + } + else + { + fileName = fileName + newExtension; + } } - - // If the file has no extension, add the new one - if (string.IsNullOrEmpty(currentExtension)) + else { - return filePath + newExtension; + fileName = filePath; } - // If the file has a different extension, replace it - return Path.ChangeExtension(filePath, newExtension); + // If an output directory is specified, combine the path + return fileName; } } - } \ No newline at end of file diff --git a/CodeLineCounter/Utils/DataExporter.cs b/CodeLineCounter/Utils/DataExporter.cs index a39e188..5afeff0 100644 --- a/CodeLineCounter/Utils/DataExporter.cs +++ b/CodeLineCounter/Utils/DataExporter.cs @@ -14,57 +14,80 @@ public static class DataExporter { CoreUtils.ExportFormat.JSON, new JsonExportStrategy() } }; - public static void Export(string filePath, T data, CoreUtils.ExportFormat format) where T : class + public static void Export(string baseFilename, string outputPath, T data, CoreUtils.ExportFormat format) where T : class { ArgumentNullException.ThrowIfNull(data); - ExportCollection(filePath, [data], format); + ExportCollection(baseFilename, outputPath, [data], format); } - public static void ExportCollection(string? filePath, IEnumerable data, CoreUtils.ExportFormat format) where T : class + + public static void ExportCollection(string? filename, string outputPath, IEnumerable data, CoreUtils.ExportFormat format) where T : class { - if (string.IsNullOrEmpty(filePath)) - throw new ArgumentException("File path cannot be null or empty", nameof(filePath)); + if (string.IsNullOrEmpty(filename)) + throw new ArgumentException("File path cannot be null or empty", nameof(filename)); ArgumentNullException.ThrowIfNull(data); try { - filePath = CoreUtils.GetExportFileNameWithExtension(filePath, format); - _exportStrategies[format].Export(filePath, data); + filename = CoreUtils.GetExportFileNameWithExtension(filename, format); + _exportStrategies[format].Export(filename, outputPath, data); } catch (IOException ex) { - throw new IOException($"Failed to export data to {filePath}", ex); + throw new IOException($"Failed to export data to {filename}", ex); } } - public static void ExportDuplications(string filePath, List duplications, CoreUtils.ExportFormat format) + public static void ExportDuplications(string baseFileName, string outputPath, List duplications, CoreUtils.ExportFormat format) { - ExportCollection(filePath, duplications, format); + string? directory = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + + ExportCollection(baseFileName,outputPath, duplications, format); } - public static async Task ExportDependencies(string filePath, List dependencies,CoreUtils.ExportFormat format) + public static async Task ExportDependencies(string baseFileName,string outputPath, List dependencies, CoreUtils.ExportFormat format) { - string outputFilePath = CoreUtils.GetExportFileNameWithExtension(filePath, format); - ExportCollection(outputFilePath, dependencies, format); + string? directory = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + string filename = CoreUtils.GetExportFileNameWithExtension(baseFileName, format); + - DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); - await DependencyGraphGenerator.CompileGraphAndWriteToFile(Path.ChangeExtension(outputFilePath, ".dot"), graph); + ExportCollection(filename,outputPath, dependencies, format); + + DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); + + filename = Path.ChangeExtension(filename, ".dot"); + + await DependencyGraphGenerator.CompileGraphAndWriteToFile(filename, outputPath, graph); } - public static void ExportMetrics(string filePath, List metrics, - Dictionary projectTotals, int totalLines, - List duplications, string? solutionPath, CoreUtils.ExportFormat format) + public static void ExportMetrics(string baseFilename, string outputPath, AnalysisResult analyzeMetrics, string solutionPath,CoreUtils.ExportFormat format) { + string? directory = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + string TOTAL = "Total"; + var filePath = CoreUtils.GetExportFileNameWithExtension(baseFilename, format); + try { string? currentProject = null; - filePath = CoreUtils.GetExportFileNameWithExtension(filePath, format); + List namespaceMetrics = []; - var duplicationCounts = GetDuplicationCounts(duplications); + var duplicationCounts = GetDuplicationCounts(analyzeMetrics.DuplicationMap); - foreach (var metric in metrics) + foreach (var metric in analyzeMetrics.Metrics) { if (!string.Equals(currentProject, metric.ProjectName, System.StringComparison.OrdinalIgnoreCase)) { @@ -73,8 +96,8 @@ public static void ExportMetrics(string filePath, List metrics namespaceMetrics.Add(new NamespaceMetrics { ProjectName = currentProject, - ProjectPath = "Total", - LineCount = projectTotals[currentProject] + ProjectPath = TOTAL, + LineCount = analyzeMetrics.ProjectTotals[currentProject] }); } currentProject = metric.ProjectName; @@ -88,23 +111,24 @@ public static void ExportMetrics(string filePath, List metrics namespaceMetrics.Add(new NamespaceMetrics { ProjectName = currentProject, - ProjectPath = "Total", - LineCount = projectTotals[currentProject] + ProjectPath = TOTAL, + LineCount = analyzeMetrics.ProjectTotals[currentProject] }); namespaceMetrics.Add(new NamespaceMetrics { - ProjectName = "Total", + ProjectName = TOTAL, ProjectPath = "", - LineCount = totalLines + LineCount = analyzeMetrics.TotalLines }); } - ExportCollection(filePath, namespaceMetrics, format); + ExportCollection(filePath, outputPath, namespaceMetrics, format); } catch (IOException ex) { - throw new IOException($"Failed to export metrics to {filePath}", ex); + throw new IOException($"Failed to export metrics to {outputPath}/{filePath}", ex); } + } public static Dictionary GetDuplicationCounts(List duplications) @@ -133,22 +157,22 @@ public static int GetFileDuplicationsCount(Dictionary duplicationC public interface IExportStrategy { - void Export(string filePath, IEnumerable data) where T : class; + void Export(string filePath, string outputPath, IEnumerable data) where T : class; } public class CsvExportStrategy : IExportStrategy { - public void Export(string filePath, IEnumerable data) where T : class + public void Export(string filePath, string outputPath, IEnumerable data) where T : class { - CsvHandler.Serialize(data, filePath); + CsvHandler.Serialize(data, Path.Combine(outputPath, filePath)); } } public class JsonExportStrategy : IExportStrategy { - public void Export(string filePath, IEnumerable data) where T : class + public void Export(string filePath,string outputPath, IEnumerable data) where T : class { - JsonHandler.Serialize(data, filePath); + JsonHandler.Serialize(data, Path.Combine(outputPath, filePath)); } } } \ No newline at end of file diff --git a/README.md b/README.md index 06944c8..6952cf6 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,24 @@ dotnet run --project CodeLineCounter/CodeLineCounter.csproj -verbose -d "path/to - Select the solution to analyze by entering the corresponding number. +### Example + +```sh +CodeLineCounter.exe [-verbose] [-d ] [-output ] [-format ] [-help] +``` + +### Available Arguments + +```sh +- `-d ` : Directory path containing the solutions to analyze (required) +- `-output ` : Destination directory for result files (optional, default: current directory) +- `-format ` : Export format for results (optional) + - Possible values: `csv`, `json` + - Default: `csv` +- `-verbose` : Enable detailed logging (optional) +- `-help` : Display help +``` + ## Generated Files The program generates a CSV file named `-CodeMetrics.csv` containing the following metrics: @@ -177,6 +195,7 @@ CodeLineCounter/ │ │ └── Dependencies.cs │ │ └── DuplicationCode.cs │ │ └── NamespaceMetrics.cs +│ │ └── Settings.cs │ ├── Services/ │ │ ├── CodeMetricsAnalyzer.cs │ │ ├── CodeDuplicationChecker.cs