From 19af0fd8956ef2f037c2ece89c3c8125e8aeb07c Mon Sep 17 00:00:00 2001 From: magic56 Date: Thu, 16 Jan 2025 15:36:47 +0100 Subject: [PATCH 1/9] feat: enhance command-line arguments and add output path support in CodeLineCounter --- .gitignore | 1 + .vscode/launch.json | 10 + .../CodeDuplicationCheckerTests.cs | 50 ++++- CodeLineCounter.Tests/CoreUtilsTests.cs | 196 ++++++++++++++---- CodeLineCounter.Tests/CsvHandlerTests.cs | 47 ++++- .../CyclomaticComplexityCalculatorTests.cs | 6 + CodeLineCounter.Tests/DataExporterTests.cs | 39 +++- .../DependencyGraphGeneratorTests.cs | 55 ++++- CodeLineCounter.Tests/FileUtilsTests.cs | 41 +++- .../SolutionAnalyzerTests.cs | 44 +++- CodeLineCounter/Models/Settings.cs | 71 +++++++ CodeLineCounter/Program.cs | 6 +- CodeLineCounter/Services/SolutionAnalyzer.cs | 42 ++-- CodeLineCounter/Utils/CoreUtils.cs | 82 ++++---- CodeLineCounter/Utils/DataExporter.cs | 44 ++-- README.md | 19 ++ 16 files changed, 613 insertions(+), 140 deletions(-) create mode 100644 CodeLineCounter/Models/Settings.cs 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/CodeDuplicationCheckerTests.cs b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs index d1f978e..1be8a14 100644 --- a/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs +++ b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs @@ -2,14 +2,23 @@ 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() { // 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 @@ -85,8 +94,8 @@ 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); @@ -126,8 +135,8 @@ 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); @@ -137,5 +146,32 @@ public void AnotherTestMethod() // 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..3279d8f 100644 --- a/CodeLineCounter.Tests/CoreUtilsTests.cs +++ b/CodeLineCounter.Tests/CoreUtilsTests.cs @@ -1,21 +1,31 @@ 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]; // 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] @@ -25,10 +35,10 @@ public void ParseArguments_help_Should_Return_Correct_Values() string[] args = ["-help"]; // Act - var (_, _, Help, _) = CoreUtils.ParseArguments(args); + var settings = CoreUtils.ParseArguments(args); // Assert - Assert.True(Help); + Assert.True(settings.Help); } [Fact] @@ -38,11 +48,11 @@ public void ParseArguments_Should_Return_Default_Values_When_No_Arguments_Passed string[] args = []; // 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] @@ -52,11 +62,11 @@ public void ParseArguments_Should_Ignore_Invalid_Arguments() string[] args = ["-invalid", "-d", "testDirectory", "-f", "json"]; // 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 @@ -73,7 +83,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 @@ -90,7 +100,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,14 +109,14 @@ public void ParseArguments_handles_invalid_format_option() { // Arrange string[] args = new[] { "-format", "INVALID" }; - var consoleOutput = new StringWriter(); + using StringWriter consoleOutput = new(); Console.SetOut(consoleOutput); // Act var result = CoreUtils.ParseArguments(args); // Assert - Assert.Equal(CoreUtils.ExportFormat.CSV, result.format); + Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); Assert.Contains("Invalid format", consoleOutput.ToString()); } @@ -116,8 +126,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,14 +138,32 @@ 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(); + using var inputStream = new StringReader(input); + using var consoleOutput = new StringWriter(); Console.SetOut(consoleOutput); Console.SetIn(inputStream); @@ -152,8 +180,8 @@ public void GetUserChoice_returns_valid_selection_for_valid_input() { // Arrange var input = "2"; - var consoleInput = new StringReader(input); - var consoleOutput = new StringWriter(); + using var consoleInput = new StringReader(input); + using var consoleOutput = new StringWriter(); Console.SetOut(consoleOutput); Console.SetIn(consoleInput); @@ -171,8 +199,8 @@ 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(); + using var consoleInput = new StringReader(input); + using var consoleOutput = new StringWriter(); Console.SetOut(consoleOutput); Console.SetIn(consoleInput); @@ -186,7 +214,7 @@ public void GetUserChoice_handles_invalid_input(string input) [Fact] public void DisplaySolutions_Should_Write_Solutions_To_Console() { - + var envNewLine = Environment.NewLine; // Arrange List solutionFiles = @@ -215,6 +243,8 @@ public void DisplaySolutions_Should_Write_Solutions_To_Console() [Fact] public void GetFilenamesList_Should_Return_List_Of_Filenames() { + using var sw = new StringWriter(); + Console.SetOut(sw); // Arrange List solutionFiles = [ @@ -241,15 +271,15 @@ 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); + Settings settings = new Settings(true, null, ".", true, CoreUtils.ExportFormat.JSON); using var sw = new StringWriter(); Console.SetOut(sw); // Act - var result = CoreUtils.CheckSettings(settings); + var result = settings.IsValid(); // Assert - Assert.False(result); + Assert.True(result); Assert.Contains("Usage:", sw.ToString()); } @@ -257,12 +287,12 @@ public void CheckSettings_WhenHelpIsTrue_ReturnsFalse() 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); + 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); + var result = settings.IsValid(); // Assert Assert.False(result); @@ -272,11 +302,27 @@ public void CheckSettings_WhenDirectoryPathIsNull_ReturnsFalse() [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(); + + // Assert + Assert.True(result); + } + [Fact] + public void CheckSettings_WhenSettingsOutputIsInValid_ReturnsFalse() + { + using var sw = new StringWriter(); + Console.SetOut(sw); // Arrange - (bool Verbose, string DirectoryPath, bool Help, CoreUtils.ExportFormat format) settings = (false, "some_directory", false, CoreUtils.ExportFormat.CSV); + Settings settings = new Settings(false, "some_directory", "invalid_output_path", false, CoreUtils.ExportFormat.CSV); // Act - var result = CoreUtils.CheckSettings(settings); + var result = settings.IsValid(); // Assert Assert.True(result); @@ -286,12 +332,12 @@ public void CheckSettings_WhenSettingsAreValid_ReturnsTrue() public void CheckSettings_WhenSettingsAreInvalid_ReturnsFalse() { // Arrange - (bool Verbose, string? DirectoryPath, bool Help, CoreUtils.ExportFormat format) settings = (false, null, false, CoreUtils.ExportFormat.CSV); + Settings settings = new Settings(false, null, false, CoreUtils.ExportFormat.CSV); using var sw = new StringWriter(); Console.SetOut(sw); // Act - var result = CoreUtils.CheckSettings(settings); + var result = settings.IsValid(); // Assert Assert.False(result); @@ -305,12 +351,88 @@ 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 result = CoreUtils.GetExportFileNameWithExtension(fileName, format); + var fullFileName = Path.Combine(_testDirectory, fileName); + var result = CoreUtils.GetExportFileNameWithExtension(fullFileName, format); // Assert - Assert.Contains(Path.GetFileNameWithoutExtension(fileName), result); + Assert.Contains(Path.GetFileNameWithoutExtension(fullFileName), 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..ad56ea5 100644 --- a/CodeLineCounter.Tests/CsvHandlerTests.cs +++ b/CodeLineCounter.Tests/CsvHandlerTests.cs @@ -2,14 +2,24 @@ 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() { @@ -19,7 +29,7 @@ public void Serialize_ValidData_WritesToFile() 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); @@ -38,7 +48,7 @@ public void Serialize_ValidData_WritesToFile() public void Deserialize_ValidFile_ReturnsData() { // Arrange - string filePath = "test_2.csv"; + string filePath = Path.Combine(_testDirectory,"test_2.csv"); var data = new List { "Id,Name", @@ -64,7 +74,7 @@ public void Serialize_EmptyData_WritesEmptyFile() { // Arrange var data = new List(); - string filePath = "test_3.csv"; + string filePath = Path.Combine(_testDirectory,"test_3.csv"); // Act CsvHandler.Serialize(data, filePath); @@ -81,7 +91,7 @@ public void Serialize_EmptyData_WritesEmptyFile() public void Deserialize_EmptyFile_ReturnsEmptyList() { // Arrange - string filePath = "test_4.csv"; + string filePath = Path.Combine(_testDirectory,"test_4.csv"); File.WriteAllText(filePath, "Id,Name"); // Act @@ -93,5 +103,32 @@ public void Deserialize_EmptyFile_ReturnsEmptyList() // 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..8d0cd22 100644 --- a/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs +++ b/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs @@ -9,6 +9,8 @@ public class CyclomaticComplexityCalculatorTests [Fact] public void TestCalculateComplexity() { + using var sw = new StringWriter(); + Console.SetOut(sw); // Arrange var code = @" public class TestClass @@ -32,6 +34,8 @@ public void TestMethod() [Fact] public void Calculate_Should_Return_Correct_Complexity() { + using var sw = new StringWriter(); + Console.SetOut(sw); // Arrange var syntaxTree = CSharpSyntaxTree.ParseText(@" public class MyClass @@ -62,6 +66,8 @@ public void MyMethod() [Fact] public void Calculate_Should_Return_Correct_Complexity_6() { + using var sw = new StringWriter(); + Console.SetOut(sw); // Arrange var syntaxTree = CSharpSyntaxTree.ParseText(@" class Program diff --git a/CodeLineCounter.Tests/DataExporterTests.cs b/CodeLineCounter.Tests/DataExporterTests.cs index fa50c5c..33b210a 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,6 +19,8 @@ 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 = Path.Combine(_testDirectory, "test"); @@ -36,6 +37,8 @@ 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 { @@ -54,6 +57,8 @@ public void ExportCollection_WithMultipleItems_CreatesFile(CoreUtils.ExportForma [Fact] public void ExportMetrics_CreatesFileWithCorrectData() { + using var sw = new StringWriter(); + Console.SetOut(sw); // Arrange var metrics = new List { @@ -69,7 +74,7 @@ public void ExportMetrics_CreatesFileWithCorrectData() var filePath = Path.Combine(_testDirectory, "metrics"); // Act - DataExporter.ExportMetrics(filePath, metrics, projectTotals, 300, duplications, null, CoreUtils.ExportFormat.CSV); + DataExporter.ExportMetrics(filePath, metrics, projectTotals, 300, duplications, ".", CoreUtils.ExportFormat.CSV); // Assert Assert.True(File.Exists(filePath + ".csv")); @@ -78,6 +83,8 @@ public void ExportMetrics_CreatesFileWithCorrectData() [Fact] public void ExportDuplications_CreatesFileWithCorrectData() { + using var sw = new StringWriter(); + Console.SetOut(sw); // Arrange var duplications = new List { @@ -96,6 +103,8 @@ public void ExportDuplications_CreatesFileWithCorrectData() [Fact] public void get_duplication_counts_returns_correct_counts_for_duplicate_paths() { + using var sw = new StringWriter(); + Console.SetOut(sw); var duplications = new List { new DuplicationCode { FilePath = "file1.cs" }, @@ -113,6 +122,8 @@ public void get_duplication_counts_returns_correct_counts_for_duplicate_paths() [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 result = DataExporter.GetDuplicationCounts(duplications); @@ -123,6 +134,8 @@ public void get_duplication_counts_returns_empty_dictionary_for_empty_list() [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 { @@ -138,10 +151,12 @@ public void get_duplication_counts_handles_absolute_paths() [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 - { - { Path.Combine(Path.GetFullPath("."), "test.cs"), 5 } - }; + { + { Path.Combine(Path.GetFullPath("."), "test.cs"), 5 } + }; var metric = new NamespaceMetrics { FilePath = "test.cs" }; @@ -153,6 +168,8 @@ public void get_file_duplications_count_returns_correct_count_when_path_exists() [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 metric = new NamespaceMetrics { FilePath = "nonexistent.cs" }; @@ -165,6 +182,8 @@ public void get_file_duplications_count_returns_zero_when_path_not_found() [Fact] public void get_file_duplications_count_returns_zero_when_filepath_null() { + using var sw = new StringWriter(); + Console.SetOut(sw); var duplicationCounts = new Dictionary { { Path.Combine(Path.GetFullPath("."), "test.cs"), 5 } @@ -181,6 +200,8 @@ 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 { @@ -198,6 +219,8 @@ 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 { @@ -215,13 +238,15 @@ 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 { new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "file1.cs", StartLine = 10 }, }; - var testFilePath = "test_export"; + var testFilePath = Path.Combine(Path.GetFullPath("."),"test_export.dot"); var format = CoreUtils.ExportFormat.JSON; // Act @@ -260,6 +285,8 @@ public async Task export_dependencies_creates_files_in_correct_formats() [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" } }; diff --git a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs index a3c5eb1..6ad08b2 100644 --- a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs +++ b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs @@ -5,11 +5,23 @@ 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() { + using var sw = new StringWriter(); + Console.SetOut(sw); + // Arrange var dependencies = new List { @@ -17,11 +29,12 @@ public async Task generate_graph_with_valid_dependencies_creates_dot_file() 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 outputPath = Path.Combine(_testDirectory, "test_graph.dot"); // Act DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); + Directory.CreateDirectory(_testDirectory); await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph); // Assert @@ -38,9 +51,12 @@ public async Task generate_graph_with_valid_dependencies_creates_dot_file() [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 outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot"); + string outputPath = Path.Combine(_testDirectory, "empty_graph.dot"); // Act DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); @@ -58,6 +74,8 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph() [Fact] public void create_node_sets_correct_fillcolor_and_style_incoming_greater() { + using var sw = new StringWriter(); + Console.SetOut(sw); var vertexInfo = new Dictionary { { "TestVertex", (3, 2) } @@ -72,6 +90,8 @@ public void create_node_sets_correct_fillcolor_and_style_incoming_greater() [Fact] public void create_node_sets_correct_fillcolor_and_style_incoming_lower() { + using var sw = new StringWriter(); + Console.SetOut(sw); var vertexInfo = new Dictionary { { "TestVertex", (3, 4) } @@ -87,6 +107,8 @@ 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"; @@ -101,6 +123,8 @@ 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; @@ -115,14 +139,39 @@ public void enclose_string_in_quotes_returns_quoted_null_for_null_input() [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); } + 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..32bbcb3 100644 --- a/CodeLineCounter.Tests/FileUtilsTests.cs +++ b/CodeLineCounter.Tests/FileUtilsTests.cs @@ -2,8 +2,16 @@ 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() { @@ -36,7 +44,7 @@ public void GetBasePath_Should_Return_NonEmptyString() public void get_solution_files_throws_exception_for_nonexistent_directory() { // Arrange - var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var nonExistentPath = Path.Combine(_testDirectory, Guid.NewGuid().ToString()); // Act & Assert Assert.Throws(() => FileUtils.GetSolutionFiles(nonExistentPath)); @@ -47,7 +55,7 @@ public void get_solution_files_throws_exception_for_nonexistent_directory() public void get_project_files_throws_when_file_not_exists() { // Arrange - var nonExistentPath = "nonexistent.sln"; + var nonExistentPath = Path.Combine(_testDirectory, "nonexistent.sln"); // Act & Assert var exception = Assert.Throws(() => @@ -55,5 +63,32 @@ public void get_project_files_throws_when_file_not_exists() 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); + } + + // Dispose unmanaged resources (if any) + + _disposed = true; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~FileUtilsTests() + { + Dispose(false); + } } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index 5c8f71d..33b6294 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -4,9 +4,18 @@ namespace CodeLineCounter.Tests.Services { - public class SolutionAnalyzerTest + public class SolutionAnalyzerTest : IDisposable { + private readonly string _testDirectory; + private bool _disposed; + + public SolutionAnalyzerTest() + { + _testDirectory = Path.Combine(Path.GetTempPath(), "SolutionAnalyzerTest"); + Directory.CreateDirectory(_testDirectory); + } + [Fact] public void analyze_and_export_solution_succeeds_with_valid_inputs() { @@ -14,7 +23,7 @@ public void analyze_and_export_solution_succeeds_with_valid_inputs() var basePath = FileUtils.GetBasePath(); var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln"); - var sw = new StringWriter(); + using var sw = new StringWriter(); Console.SetOut(sw); var verbose = false; var format = CoreUtils.ExportFormat.JSON; @@ -48,7 +57,7 @@ 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(); + 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."); @@ -165,7 +174,7 @@ public void export_results_with_valid_input_exports_all_files() DependencyList = new List() }; - var basePath = FileUtils.GetBasePath(); + var basePath = _testDirectory; var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); solutionPath = Path.Combine(solutionPath, "TestSolution.sln"); @@ -184,5 +193,32 @@ public void export_results_with_valid_input_exports_all_files() Assert.True(File.Exists("TestSolution-CodeDependencies.csv")); } + 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); + } + + ~SolutionAnalyzerTest() + { + Dispose(false); + } + } } \ No newline at end of file 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/SolutionAnalyzer.cs b/CodeLineCounter/Services/SolutionAnalyzer.cs index 65f71db..2df6d78 100644 --- a/CodeLineCounter/Services/SolutionAnalyzer.cs +++ b/CodeLineCounter/Services/SolutionAnalyzer.cs @@ -8,13 +8,13 @@ namespace CodeLineCounter.Services public static partial class SolutionAnalyzer { - public static void AnalyzeAndExportSolution(string solutionPath, bool verbose, CoreUtils.ExportFormat format) + public static void AnalyzeAndExportSolution(string solutionPath, bool verbose, CoreUtils.ExportFormat format, string? outputPath = null) { try { var analysisResult = PerformAnalysis(solutionPath); OutputAnalysisResults(analysisResult, verbose); - ExportResults(analysisResult, solutionPath, format); + ExportResults(analysisResult, solutionPath, format, outputPath); } catch (Exception ex) @@ -65,20 +65,32 @@ public static void OutputAnalysisResults(AnalysisResult result, bool verbose) Console.WriteLine($"Time taken: {result.ProcessingTime:m\\:ss\\.fff}"); } - public static void ExportResults(AnalysisResult result, string solutionPath, CoreUtils.ExportFormat format) + public static void ExportResults(AnalysisResult result, string solutionPath, CoreUtils.ExportFormat format, string? outputPath = null) { - var metricsOutputFilePath = CoreUtils.GetExportFileNameWithExtension( - $"{result.SolutionFileName}-CodeMetrics.xxx", format); - var duplicationOutputFilePath = CoreUtils.GetExportFileNameWithExtension( - $"{result.SolutionFileName}-CodeDuplications.xxx", format); - var dependenciesOutputFilePath = CoreUtils.GetExportFileNameWithExtension( - $"{result.SolutionFileName}-CodeDependencies.xxx", format); + string baseFileName = Path.GetFileNameWithoutExtension(solutionPath); + + // Export des métriques + string metricsFileName = $"{baseFileName}-CodeMetrics"; + string metricsOutputPath = CoreUtils.GetExportFileNameWithExtension(metricsFileName, format, outputPath); + // Export des métriques... + + // Export des duplications + string duplicationsFileName = $"{baseFileName}-CodeDuplication"; + string duplicationsOutputPath = CoreUtils.GetExportFileNameWithExtension(duplicationsFileName, format, outputPath); + // Export des duplications... + + // Export du graphe de dépendances + string graphFileName = $"{baseFileName}-Dependencies.dot"; + string graphOutputPath = outputPath != null + ? Path.Combine(outputPath, graphFileName) + : graphFileName; + // Export du graphe... try { Parallel.Invoke( () => DataExporter.ExportMetrics( - metricsOutputFilePath, + metricsOutputPath, result.Metrics, result.ProjectTotals, result.TotalLines, @@ -86,18 +98,18 @@ public static void ExportResults(AnalysisResult result, string solutionPath, Cor solutionPath, format), () => DataExporter.ExportDuplications( - duplicationOutputFilePath, + duplicationsOutputPath, result.DuplicationMap, format), async () => await DataExporter.ExportDependencies( - dependenciesOutputFilePath, + graphOutputPath, 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..e4722a0 100644 --- a/CodeLineCounter/Utils/CoreUtils.cs +++ b/CodeLineCounter/Utils/CoreUtils.cs @@ -1,3 +1,4 @@ +using CodeLineCounter.Models; namespace CodeLineCounter.Utils { @@ -8,57 +9,67 @@ 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; + settings.Help = true; argIndex++; break; case "-verbose": - verbose = true; + settings.Verbose = true; argIndex++; break; case "-d": if (argIndex + 1 < args.Length) { - directoryPath = args[argIndex + 1]; - argIndex += 2; // Increment by 2 to skip the next argument + settings.DirectoryPath = args[argIndex + 1]; + argIndex += 2; } else { - argIndex++; // Increment by 1 if there's no next argument + argIndex++; } break; case "-format": if (argIndex + 1 < args.Length) { string formatString = args[argIndex + 1]; - argIndex += 2; // Increment by 2 to skip the next argument + argIndex += 2; if (Enum.TryParse(formatString, true, out ExportFormat result)) { - format = result; + settings.Format = result; } else { - Console.WriteLine($"Invalid format: {formatString}. Valid formats are: {string.Join(", ", Enum.GetNames())}. Using default format {format}"); + Console.WriteLine($"Invalid format: {formatString}. Valid formats are: {string.Join(", ", Enum.GetNames())}. Using default format {settings.Format}"); } } break; + case "-output": + if (argIndex + 1 < args.Length) + { + settings.OutputPath = args[argIndex + 1]; + argIndex += 2; + } + else + { + argIndex++; + } + break; default: argIndex++; break; } } - return (verbose, directoryPath, help, format); + return settings; } /// @@ -121,25 +132,13 @@ 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, CoreUtils.ExportFormat format, string? outputPath = null) { - if (settings.Help) - { - Console.WriteLine("Usage: CodeLineCounter.exe [-verbose] [-d ] [-help, -h] (-format )"); - return false; - } - - if (settings.DirectoryPath == null) + string fileName = Path.GetFileName(filePath); + if (filePath == null) { - Console.WriteLine("Please provide the directory path containing the solutions to analyze using the -d switch."); - return false; + filePath = "."; } - - return true; - } - - public static string GetExportFileNameWithExtension(string filePath, CoreUtils.ExportFormat format) - { string newExtension = format switch { CoreUtils.ExportFormat.CSV => ".csv", @@ -147,23 +146,16 @@ public static string GetExportFileNameWithExtension(string filePath, CoreUtils.E _ => 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 (!Path.GetExtension(fileName).Equals(newExtension, StringComparison.OrdinalIgnoreCase)) { - return filePath; + fileName = Path.ChangeExtension(fileName, newExtension); } - // If the file has no extension, add the new one - if (string.IsNullOrEmpty(currentExtension)) - { - return filePath + newExtension; - } - - // If the file has a different extension, replace it - return Path.ChangeExtension(filePath, newExtension); - } + // If an output directory is specified, combine the path + return outputPath != null + ? Path.Combine(Path.GetFullPath(outputPath), fileName) + : Path.Combine(Path.GetDirectoryName(filePath) ?? Path.GetFullPath("."), fileName); + } } - } \ No newline at end of file diff --git a/CodeLineCounter/Utils/DataExporter.cs b/CodeLineCounter/Utils/DataExporter.cs index a39e188..262036b 100644 --- a/CodeLineCounter/Utils/DataExporter.cs +++ b/CodeLineCounter/Utils/DataExporter.cs @@ -39,30 +39,49 @@ public static void ExportCollection(string? filePath, IEnumerable data, Co } } - public static void ExportDuplications(string filePath, List duplications, CoreUtils.ExportFormat format) + public static void ExportDuplications(string outputPath, List duplications, CoreUtils.ExportFormat format) { - ExportCollection(filePath, duplications, format); + string? directory = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + + ExportCollection(outputPath, duplications, format); } - public static async Task ExportDependencies(string filePath, List dependencies,CoreUtils.ExportFormat format) + public static async Task ExportDependencies(string outputPath, List dependencies, CoreUtils.ExportFormat format) { - string outputFilePath = CoreUtils.GetExportFileNameWithExtension(filePath, format); + string? directory = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + string outputFilePath = CoreUtils.GetExportFileNameWithExtension(outputPath, format); ExportCollection(outputFilePath, dependencies, format); - DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); - await DependencyGraphGenerator.CompileGraphAndWriteToFile(Path.ChangeExtension(outputFilePath, ".dot"), graph); + DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); + + await DependencyGraphGenerator.CompileGraphAndWriteToFile(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 outputPath, List metrics, + Dictionary projectTotals, int totalLines, + List duplicationMap, string solutionPath, CoreUtils.ExportFormat format) { + string? directory = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + var filePath = CoreUtils.GetExportFileNameWithExtension(outputPath, format); + try { string? currentProject = null; - filePath = CoreUtils.GetExportFileNameWithExtension(filePath, format); + List namespaceMetrics = []; - var duplicationCounts = GetDuplicationCounts(duplications); + var duplicationCounts = GetDuplicationCounts(duplicationMap); foreach (var metric in metrics) { @@ -99,12 +118,13 @@ public static void ExportMetrics(string filePath, List metrics }); } - ExportCollection(filePath, namespaceMetrics, format); + ExportCollection(outputPath, namespaceMetrics, format); } catch (IOException ex) { throw new IOException($"Failed to export metrics to {filePath}", ex); } + } public static Dictionary GetDuplicationCounts(List duplications) diff --git a/README.md b/README.md index 06944c8..76a181f 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. +### exemple + +```sh +CodeLineCounter.exe [-verbose] [-d ] [-output ] [-format ] [-help] +``` + +### Arguments disponibles + +```sh +- `-d ` : Chemin du répertoire contenant les solutions à analyser (obligatoire) +- `-output ` : Répertoire de destination pour les fichiers de résultats (optionnel, par défaut: répertoire courant) +- `-format ` : Format d'export des résultats (optionnel) + - Valeurs possibles : `csv`, `json` + - Par défaut : `csv` +- `-verbose` : Active les logs détaillés (optionnel) +- `-help` : Affiche l'aide +``` + ## 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 From f0f9aa93ccb403627d965f5114adb44cfd5b083b Mon Sep 17 00:00:00 2001 From: magic56 Date: Thu, 16 Jan 2025 17:03:45 +0100 Subject: [PATCH 2/9] refactor: improve argument parsing and enhance test output handling in CoreUtils --- CodeLineCounter.Tests/CoreUtilsTests.cs | 12 +++ CodeLineCounter/Services/SolutionAnalyzer.cs | 8 +- CodeLineCounter/Utils/CoreUtils.cs | 80 +++++++++++--------- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/CodeLineCounter.Tests/CoreUtilsTests.cs b/CodeLineCounter.Tests/CoreUtilsTests.cs index 3279d8f..fcf8a61 100644 --- a/CodeLineCounter.Tests/CoreUtilsTests.cs +++ b/CodeLineCounter.Tests/CoreUtilsTests.cs @@ -19,6 +19,8 @@ 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); @@ -33,6 +35,8 @@ 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); @@ -46,6 +50,8 @@ 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); @@ -60,6 +66,8 @@ 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); @@ -75,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); @@ -92,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); diff --git a/CodeLineCounter/Services/SolutionAnalyzer.cs b/CodeLineCounter/Services/SolutionAnalyzer.cs index 2df6d78..22eeb6b 100644 --- a/CodeLineCounter/Services/SolutionAnalyzer.cs +++ b/CodeLineCounter/Services/SolutionAnalyzer.cs @@ -69,22 +69,20 @@ public static void ExportResults(AnalysisResult result, string solutionPath, Cor { string baseFileName = Path.GetFileNameWithoutExtension(solutionPath); - // Export des métriques + // Export metrics string metricsFileName = $"{baseFileName}-CodeMetrics"; string metricsOutputPath = CoreUtils.GetExportFileNameWithExtension(metricsFileName, format, outputPath); - // Export des métriques... - // Export des duplications + // Export duplications string duplicationsFileName = $"{baseFileName}-CodeDuplication"; string duplicationsOutputPath = CoreUtils.GetExportFileNameWithExtension(duplicationsFileName, format, outputPath); // Export des duplications... - // Export du graphe de dépendances + // Export dependencies graph string graphFileName = $"{baseFileName}-Dependencies.dot"; string graphOutputPath = outputPath != null ? Path.Combine(outputPath, graphFileName) : graphFileName; - // Export du graphe... try { diff --git a/CodeLineCounter/Utils/CoreUtils.cs b/CodeLineCounter/Utils/CoreUtils.cs index e4722a0..127467a 100644 --- a/CodeLineCounter/Utils/CoreUtils.cs +++ b/CodeLineCounter/Utils/CoreUtils.cs @@ -14,64 +14,70 @@ public static Settings ParseArguments(string[] args) { var settings = new Settings(); int argIndex = 0; - + while (argIndex < args.Length) { switch (args[argIndex]) { case "-help": settings.Help = true; - argIndex++; break; case "-verbose": settings.Verbose = true; - argIndex++; break; case "-d": - if (argIndex + 1 < args.Length) - { - settings.DirectoryPath = args[argIndex + 1]; - argIndex += 2; - } - else - { - argIndex++; - } + HandleDirectory(args, settings, ref argIndex); break; case "-format": - if (argIndex + 1 < args.Length) - { - string formatString = args[argIndex + 1]; - argIndex += 2; - 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}"); - } - } + HandleFormat(args, settings, ref argIndex); break; case "-output": - if (argIndex + 1 < args.Length) - { - settings.OutputPath = args[argIndex + 1]; - argIndex += 2; - } - else - { - argIndex++; - } + 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++; + } + } + /// /// Gets a valid user selection from the available solutions. /// @@ -153,9 +159,9 @@ public static string GetExportFileNameWithExtension(string filePath, CoreUtils.E } // If an output directory is specified, combine the path - return outputPath != null + return outputPath != null ? Path.Combine(Path.GetFullPath(outputPath), fileName) : Path.Combine(Path.GetDirectoryName(filePath) ?? Path.GetFullPath("."), fileName); - } + } } } \ No newline at end of file From 22ddad3714e28f0a9fbd82af188593603370cc3b Mon Sep 17 00:00:00 2001 From: magic56 Date: Thu, 16 Jan 2025 17:49:06 +0100 Subject: [PATCH 3/9] test: refactor console output handling in various test cases --- CodeLineCounter.Tests/CodeAnalyzerTests.cs | 12 +++++ .../CodeDuplicationCheckerTests.cs | 9 ++++ CodeLineCounter.Tests/CoreUtilsTests.cs | 19 +++++--- CodeLineCounter.Tests/CsvHandlerTests.cs | 12 +++++ CodeLineCounter.Tests/FileUtilsTests.cs | 8 ++++ CodeLineCounter.Tests/HashUtilsTests.cs | 12 +++++ CodeLineCounter.Tests/JsonHandlerTests.cs | 44 ++++++++++++++++++- .../SolutionAnalyzerTests.cs | 7 ++- CodeLineCounter/Utils/CoreUtils.cs | 23 +++++++--- 9 files changed, 131 insertions(+), 15 deletions(-) diff --git a/CodeLineCounter.Tests/CodeAnalyzerTests.cs b/CodeLineCounter.Tests/CodeAnalyzerTests.cs index 32b3485..e2751b4 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,6 +31,9 @@ public void TestAnalyzeSolution() [Fact] public void AnalyzeSourceCode_Should_Set_CurrentNamespace() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange var projectNamespaceMetrics = new Dictionary(); var lines = new string[] @@ -48,6 +54,9 @@ public void AnalyzeSourceCode_Should_Set_CurrentNamespace() [Fact] public void AnalyzeSourceCode_Should_Set_FileLineCount() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange var projectNamespaceMetrics = new Dictionary(); var lines = new string[] @@ -68,6 +77,9 @@ public void AnalyzeSourceCode_Should_Set_FileLineCount() [Fact] public void AnalyzeSourceCode_Should_Set_FileCyclomaticComplexity() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange var projectNamespaceMetrics = new Dictionary(); var lines = new string[] diff --git a/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs index 1be8a14..fdef7ec 100644 --- a/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs +++ b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs @@ -16,6 +16,9 @@ public CodeDuplicationCheckerTests() [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"); @@ -67,6 +70,9 @@ public void AnotherTestMethod() [Fact] public void DetectCodeDuplicationInSourceCode_ShouldDetectDuplicates() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange var checker = new CodeDuplicationChecker(); @@ -111,6 +117,9 @@ public void AnotherTestMethod() [Fact] public void DetectCodeDuplicationInSourceCode_ShouldNotDetectDuplicatesForDifferentCode() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange var checker = new CodeDuplicationChecker(); diff --git a/CodeLineCounter.Tests/CoreUtilsTests.cs b/CodeLineCounter.Tests/CoreUtilsTests.cs index fcf8a61..9719ff5 100644 --- a/CodeLineCounter.Tests/CoreUtilsTests.cs +++ b/CodeLineCounter.Tests/CoreUtilsTests.cs @@ -121,15 +121,22 @@ public void ParseArguments_handles_invalid_format_option() { // Arrange string[] args = new[] { "-format", "INVALID" }; - using StringWriter consoleOutput = new(); - 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 + // Assert Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); - Assert.Contains("Invalid format", consoleOutput.ToString()); + Assert.Contains("Invalid format", sortieConsole.ToString()); + + } [Fact] @@ -226,7 +233,7 @@ public void GetUserChoice_handles_invalid_input(string input) [Fact] public void DisplaySolutions_Should_Write_Solutions_To_Console() { - + var envNewLine = Environment.NewLine; // Arrange List solutionFiles = diff --git a/CodeLineCounter.Tests/CsvHandlerTests.cs b/CodeLineCounter.Tests/CsvHandlerTests.cs index ad56ea5..fadafc8 100644 --- a/CodeLineCounter.Tests/CsvHandlerTests.cs +++ b/CodeLineCounter.Tests/CsvHandlerTests.cs @@ -23,6 +23,9 @@ public CsvHandlerTests() [Fact] public void Serialize_ValidData_WritesToFile() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange var data = new List { @@ -47,6 +50,9 @@ public void Serialize_ValidData_WritesToFile() [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 @@ -72,6 +78,9 @@ public void Deserialize_ValidFile_ReturnsData() [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"); @@ -90,6 +99,9 @@ public void Serialize_EmptyData_WritesEmptyFile() [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"); diff --git a/CodeLineCounter.Tests/FileUtilsTests.cs b/CodeLineCounter.Tests/FileUtilsTests.cs index 32bbcb3..e78b907 100644 --- a/CodeLineCounter.Tests/FileUtilsTests.cs +++ b/CodeLineCounter.Tests/FileUtilsTests.cs @@ -15,6 +15,8 @@ public FileUtilsTests() [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, "..", "..", "..", "..")); @@ -32,6 +34,8 @@ public void GetSolutionFiles_Should_Return_List_Of_Solution_Files() [Fact] public void GetBasePath_Should_Return_NonEmptyString() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); // Act string basePath = FileUtils.GetBasePath(); @@ -43,6 +47,8 @@ public void GetBasePath_Should_Return_NonEmptyString() [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()); @@ -54,6 +60,8 @@ public void get_solution_files_throws_exception_for_nonexistent_directory() [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"); diff --git a/CodeLineCounter.Tests/HashUtilsTests.cs b/CodeLineCounter.Tests/HashUtilsTests.cs index 8d99b04..97465dd 100644 --- a/CodeLineCounter.Tests/HashUtilsTests.cs +++ b/CodeLineCounter.Tests/HashUtilsTests.cs @@ -7,6 +7,9 @@ public class HashUtilsTests [Fact] public void ComputeHash_EmptyString_ReturnsEmptyString() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange string input = ""; @@ -20,6 +23,9 @@ public void ComputeHash_EmptyString_ReturnsEmptyString() [Fact] public void ComputeHash_NullString_ReturnsEmptyString() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange string? input = null; @@ -33,6 +39,9 @@ public void ComputeHash_NullString_ReturnsEmptyString() [Fact] public void ComputeHash_ValidString_ReturnsHash() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange string input = "Hello, World!"; @@ -47,6 +56,9 @@ public void ComputeHash_ValidString_ReturnsHash() [Fact] public void ComputeHash_DuplicateStrings_ReturnSameHash() { + using StringWriter consoleOutput = new(); + Console.SetOut(consoleOutput); + // Arrange string input1 = "Hello, World!"; string input2 = "Hello, World!"; diff --git a/CodeLineCounter.Tests/JsonHandlerTests.cs b/CodeLineCounter.Tests/JsonHandlerTests.cs index 5d90291..f2573a7 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,8 +30,11 @@ 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 = "test.json"; + var testFilePath = Path.Combine(_testDirectory, "test.json"); var expectedData = new[] { new TestClass(id: 1, name: "Test") }; File.WriteAllText(testFilePath, JsonSerializer.Serialize(expectedData)); @@ -36,5 +49,32 @@ public void deserialize_valid_json_file_returns_expected_objects() 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); + } + } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index 33b6294..d3f7c14 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -20,11 +20,12 @@ public SolutionAnalyzerTest() public void analyze_and_export_solution_succeeds_with_valid_inputs() { // Arrange + using var sw = new StringWriter(); + Console.SetOut(sw); 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); + var verbose = false; var format = CoreUtils.ExportFormat.JSON; @@ -39,6 +40,8 @@ public void analyze_and_export_solution_succeeds_with_valid_inputs() public void analyze_and_export_solution_throws_on_invalid_path() { // Arrange + using var sw = new StringWriter(); + Console.SetOut(sw); var invalidPath = ""; var verbose = false; var format = CoreUtils.ExportFormat.JSON; diff --git a/CodeLineCounter/Utils/CoreUtils.cs b/CodeLineCounter/Utils/CoreUtils.cs index 127467a..1c0bc5a 100644 --- a/CodeLineCounter/Utils/CoreUtils.cs +++ b/CodeLineCounter/Utils/CoreUtils.cs @@ -138,7 +138,7 @@ public static void DisplaySolutions(List solutionFiles) } } - public static string GetExportFileNameWithExtension(string filePath, CoreUtils.ExportFormat format, string? outputPath = null) + public static string GetExportFileNameWithExtension(string filePath, ExportFormat format, string? outputPath = null) { string fileName = Path.GetFileName(filePath); if (filePath == null) @@ -147,15 +147,28 @@ public static string GetExportFileNameWithExtension(string filePath, CoreUtils.E } 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 file already has the desired extension, keep it, otherwise change it - if (!Path.GetExtension(fileName).Equals(newExtension, StringComparison.OrdinalIgnoreCase)) + if (!currentExtension.Equals(newExtension, StringComparison.OrdinalIgnoreCase)) + { + if (!string.IsNullOrEmpty(currentExtension)) + { + fileName = Path.ChangeExtension(fileName, newExtension); + } + else + { + fileName = fileName + newExtension; + } + } + else { - fileName = Path.ChangeExtension(fileName, newExtension); + fileName = filePath; } // If an output directory is specified, combine the path From b7740ac2345077f36a7eafb5c71762ece77bb06b Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Fri, 17 Jan 2025 14:55:22 +0100 Subject: [PATCH 4/9] refactor: streamline file name handling and output path management in CoreUtils and DependencyGraphGenerator --- CodeLineCounter.Tests/CoreUtilsTests.cs | 7 +- CodeLineCounter.Tests/DataExporterTests.cs | 56 +++++++++------ .../DependencyGraphGeneratorTests.cs | 11 +-- CodeLineCounter.Tests/JsonHandlerTests.cs | 7 +- .../SolutionAnalyzerTests.cs | 60 +++++++++------- .../Services/DependencyGraphGenerator.cs | 5 +- CodeLineCounter/Services/SolutionAnalyzer.cs | 29 +++++--- CodeLineCounter/Utils/CoreUtils.cs | 11 ++- CodeLineCounter/Utils/DataExporter.cs | 70 ++++++++++--------- README.md | 18 ++--- 10 files changed, 157 insertions(+), 117 deletions(-) diff --git a/CodeLineCounter.Tests/CoreUtilsTests.cs b/CodeLineCounter.Tests/CoreUtilsTests.cs index 9719ff5..f457328 100644 --- a/CodeLineCounter.Tests/CoreUtilsTests.cs +++ b/CodeLineCounter.Tests/CoreUtilsTests.cs @@ -130,13 +130,10 @@ public void ParseArguments_handles_invalid_format_option() Console.SetOut(sw); result = CoreUtils.ParseArguments(args); sortieConsole = sw.ToString(); + Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); + Assert.Contains("Invalid format", sortieConsole.ToString()); } - // Assert - Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); - Assert.Contains("Invalid format", sortieConsole.ToString()); - - } [Fact] diff --git a/CodeLineCounter.Tests/DataExporterTests.cs b/CodeLineCounter.Tests/DataExporterTests.cs index 33b210a..faba5b6 100644 --- a/CodeLineCounter.Tests/DataExporterTests.cs +++ b/CodeLineCounter.Tests/DataExporterTests.cs @@ -23,13 +23,13 @@ public void Export_SingleItem_CreatesFileWithCorrectExtension(CoreUtils.ExportFo Console.SetOut(sw); // Arrange var testItem = new TestClass { Id = 1, Name = "Test" }; - var filePath = Path.Combine(_testDirectory, "test"); + var filePath = "test"; // Act - DataExporter.Export(filePath, testItem, format); + DataExporter.Export(filePath, _testDirectory, testItem, format); // Assert - Assert.True(File.Exists(filePath + expectedExtension)); + Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + expectedExtension))); } [Theory] @@ -48,7 +48,7 @@ public void ExportCollection_WithMultipleItems_CreatesFile(CoreUtils.ExportForma var filePath = Path.Combine(_testDirectory, "collection"); // Act - DataExporter.ExportCollection(filePath, items, format); + DataExporter.ExportCollection(filePath, _testDirectory, items, format); // Assert Assert.True(File.Exists(filePath + expectedExtension)); @@ -71,10 +71,19 @@ public void ExportMetrics_CreatesFileWithCorrectData() { "Project2", 200 } }; var duplications = new List(); - var filePath = Path.Combine(_testDirectory, "metrics"); + var filePath = "metrics"; + AnalysisResult result = new AnalysisResult + { + Metrics = metrics, + ProjectTotals = projectTotals, + DuplicationMap = duplications, + DependencyList = new List(), + TotalLines = 300, + SolutionFileName = "TestSolution" + }; // Act - DataExporter.ExportMetrics(filePath, metrics, projectTotals, 300, duplications, ".", CoreUtils.ExportFormat.CSV); + DataExporter.ExportMetrics(filePath, _testDirectory, result, ".",CoreUtils.ExportFormat.CSV); // Assert Assert.True(File.Exists(filePath + ".csv")); @@ -91,10 +100,10 @@ public void ExportDuplications_CreatesFileWithCorrectData() 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"); + var filePath = "duplications"; // Act - DataExporter.ExportDuplications(filePath, duplications, CoreUtils.ExportFormat.CSV); + DataExporter.ExportDuplications(filePath, _testDirectory, duplications, CoreUtils.ExportFormat.CSV); // Assert Assert.True(File.Exists(filePath + ".csv")); @@ -246,14 +255,14 @@ public async Task export_dependencies_creates_files_in_correct_formats() new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "file1.cs", StartLine = 10 }, }; - var testFilePath = Path.Combine(Path.GetFullPath("."),"test_export.dot"); + var testFilePath = "test_export.dot"; var format = CoreUtils.ExportFormat.JSON; // Act - await DataExporter.ExportDependencies(testFilePath, dependencies, format); + await DataExporter.ExportDependencies(testFilePath, _testDirectory, dependencies, format); // Assert - string expectedJsonPath = CoreUtils.GetExportFileNameWithExtension(testFilePath, format); + string expectedJsonPath = Path.Combine(_testDirectory, CoreUtils.GetExportFileNameWithExtension(testFilePath, format)); string expectedDotPath = Path.ChangeExtension(expectedJsonPath, ".dot"); Assert.True(File.Exists(expectedJsonPath)); @@ -285,17 +294,20 @@ public async Task export_dependencies_creates_files_in_correct_formats() [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, 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 6ad08b2..8b7a65c 100644 --- a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs +++ b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs @@ -29,13 +29,15 @@ public async Task generate_graph_with_valid_dependencies_creates_dot_file() 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, "test_graph.dot"); + string fileName = "test_graph.dot"; + + string outputPath = Path.Combine(_testDirectory, fileName); // Act DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); Directory.CreateDirectory(_testDirectory); - await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph); + await DependencyGraphGenerator.CompileGraphAndWriteToFile(fileName,_testDirectory, graph); // Assert Assert.True(File.Exists(outputPath)); @@ -56,11 +58,12 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph() // Arrange var dependencies = new List(); - string outputPath = Path.Combine(_testDirectory, "empty_graph.dot"); + string filename = "empty_graph.dot"; + string outputPath = Path.Combine(_testDirectory, filename); // Act DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); - await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph); + await DependencyGraphGenerator.CompileGraphAndWriteToFile(filename, _testDirectory, graph); // Assert Assert.True(File.Exists(outputPath)); diff --git a/CodeLineCounter.Tests/JsonHandlerTests.cs b/CodeLineCounter.Tests/JsonHandlerTests.cs index f2573a7..5d37975 100644 --- a/CodeLineCounter.Tests/JsonHandlerTests.cs +++ b/CodeLineCounter.Tests/JsonHandlerTests.cs @@ -30,8 +30,8 @@ public TestClass(int id, string name) [Fact] public void deserialize_valid_json_file_returns_expected_objects() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); + using (StringWriter consoleOutput = new()){ + Console.SetOut(consoleOutput); // Arrange var testFilePath = Path.Combine(_testDirectory, "test.json"); @@ -47,6 +47,9 @@ public void deserialize_valid_json_file_returns_expected_objects() Assert.Equal(expectedData[0].Name, result.First().Name); File.Delete(testFilePath); + + } + } protected virtual void Dispose(bool disposing) diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index d3f7c14..c01307e 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -20,37 +20,44 @@ public SolutionAnalyzerTest() public void analyze_and_export_solution_succeeds_with_valid_inputs() { // Arrange - using var sw = new StringWriter(); - Console.SetOut(sw); var basePath = FileUtils.GetBasePath(); var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln"); - + var verbose = false; var format = CoreUtils.ExportFormat.JSON; // Act & Assert - var exception = Record.Exception(() => + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + var exception = Record.Exception(() => SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, verbose, format)); - Assert.Null(exception); + Assert.Null(exception); + } } [Fact] public void analyze_and_export_solution_throws_on_invalid_path() { // Arrange - using var sw = new StringWriter(); - Console.SetOut(sw); + var invalidPath = ""; var verbose = false; var format = CoreUtils.ExportFormat.JSON; // Act & Assert - var exception = Assert.Throws(() => + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + 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] @@ -60,19 +67,23 @@ 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."); + 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] @@ -188,12 +199,13 @@ public void export_results_with_valid_input_exports_all_files() { Console.SetOut(sw); SolutionAnalyzer.ExportResults(result, solutionPath, format); + // Assert + Assert.True(File.Exists("TestSolution-CodeMetrics.csv")); + Assert.True(File.Exists("TestSolution-CodeDuplications.csv")); + Assert.True(File.Exists("TestSolution-CodeDependencies.csv")); } - // Assert - Assert.True(File.Exists("TestSolution-CodeMetrics.csv")); - Assert.True(File.Exists("TestSolution-CodeDuplications.csv")); - Assert.True(File.Exists("TestSolution-CodeDependencies.csv")); + } protected virtual void Dispose(bool disposing) 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( - metricsOutputPath, - result.Metrics, - result.ProjectTotals, - result.TotalLines, - result.DuplicationMap, + metricsFileName, + outputPath ??".", + result, solutionPath, format), () => DataExporter.ExportDuplications( - duplicationsOutputPath, + duplicationsFileName, + outputPath ?? ".", result.DuplicationMap, format), async () => await DataExporter.ExportDependencies( - graphOutputPath, + graphFileName, + outputPath ?? ".", result.DependencyList, format) ); diff --git a/CodeLineCounter/Utils/CoreUtils.cs b/CodeLineCounter/Utils/CoreUtils.cs index 1c0bc5a..539b94c 100644 --- a/CodeLineCounter/Utils/CoreUtils.cs +++ b/CodeLineCounter/Utils/CoreUtils.cs @@ -138,13 +138,14 @@ public static void DisplaySolutions(List solutionFiles) } } - public static string GetExportFileNameWithExtension(string filePath, ExportFormat format, string? outputPath = null) + public static string GetExportFileNameWithExtension(string filePath, ExportFormat format) { - string fileName = Path.GetFileName(filePath); if (filePath == null) { - filePath = "."; + filePath = "export."; } + string fileName = Path.GetFileName(filePath); + string newExtension = format switch { ExportFormat.CSV => ".csv", @@ -172,9 +173,7 @@ public static string GetExportFileNameWithExtension(string filePath, ExportForma } // If an output directory is specified, combine the path - return outputPath != null - ? Path.Combine(Path.GetFullPath(outputPath), fileName) - : Path.Combine(Path.GetDirectoryName(filePath) ?? Path.GetFullPath("."), fileName); + return fileName; } } } \ No newline at end of file diff --git a/CodeLineCounter/Utils/DataExporter.cs b/CodeLineCounter/Utils/DataExporter.cs index 262036b..5afeff0 100644 --- a/CodeLineCounter/Utils/DataExporter.cs +++ b/CodeLineCounter/Utils/DataExporter.cs @@ -14,32 +14,33 @@ 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 outputPath, List duplications, CoreUtils.ExportFormat format) + public static void ExportDuplications(string baseFileName, string outputPath, List duplications, CoreUtils.ExportFormat format) { string? directory = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(directory)) @@ -47,43 +48,46 @@ public static void ExportDuplications(string outputPath, List d Directory.CreateDirectory(directory); } - ExportCollection(outputPath, duplications, format); + ExportCollection(baseFileName,outputPath, duplications, format); } - public static async Task ExportDependencies(string outputPath, List dependencies, CoreUtils.ExportFormat format) + public static async Task ExportDependencies(string baseFileName,string outputPath, List dependencies, CoreUtils.ExportFormat format) { string? directory = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(directory)) { Directory.CreateDirectory(directory); } - string outputFilePath = CoreUtils.GetExportFileNameWithExtension(outputPath, format); - ExportCollection(outputFilePath, dependencies, format); + string filename = CoreUtils.GetExportFileNameWithExtension(baseFileName, format); + + + ExportCollection(filename,outputPath, dependencies, format); DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies); - await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph); + filename = Path.ChangeExtension(filename, ".dot"); + + await DependencyGraphGenerator.CompileGraphAndWriteToFile(filename, outputPath, graph); } - public static void ExportMetrics(string outputPath, List metrics, - Dictionary projectTotals, int totalLines, - List duplicationMap, 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); } - var filePath = CoreUtils.GetExportFileNameWithExtension(outputPath, format); + string TOTAL = "Total"; + var filePath = CoreUtils.GetExportFileNameWithExtension(baseFilename, format); try { string? currentProject = null; List namespaceMetrics = []; - var duplicationCounts = GetDuplicationCounts(duplicationMap); + 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)) { @@ -92,8 +96,8 @@ public static void ExportMetrics(string outputPath, List metri namespaceMetrics.Add(new NamespaceMetrics { ProjectName = currentProject, - ProjectPath = "Total", - LineCount = projectTotals[currentProject] + ProjectPath = TOTAL, + LineCount = analyzeMetrics.ProjectTotals[currentProject] }); } currentProject = metric.ProjectName; @@ -107,22 +111,22 @@ public static void ExportMetrics(string outputPath, List metri 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(outputPath, 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); } } @@ -153,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 76a181f..6952cf6 100644 --- a/README.md +++ b/README.md @@ -80,22 +80,22 @@ dotnet run --project CodeLineCounter/CodeLineCounter.csproj -verbose -d "path/to - Select the solution to analyze by entering the corresponding number. -### exemple +### Example ```sh CodeLineCounter.exe [-verbose] [-d ] [-output ] [-format ] [-help] ``` -### Arguments disponibles +### Available Arguments ```sh -- `-d ` : Chemin du répertoire contenant les solutions à analyser (obligatoire) -- `-output ` : Répertoire de destination pour les fichiers de résultats (optionnel, par défaut: répertoire courant) -- `-format ` : Format d'export des résultats (optionnel) - - Valeurs possibles : `csv`, `json` - - Par défaut : `csv` -- `-verbose` : Active les logs détaillés (optionnel) -- `-help` : Affiche l'aide +- `-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 From d40fbc8d7cf17e5a9e280c55fc83444541aa63da Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Fri, 17 Jan 2025 17:43:03 +0100 Subject: [PATCH 5/9] refactor: improve test structure by rearranging Arrange, Act, and Assert sections in various test cases --- CodeLineCounter.Tests/CodeAnalyzerTests.cs | 76 ++-- .../CodeDuplicationCheckerTests.cs | 74 ++-- CodeLineCounter.Tests/CoreUtilsTests.cs | 304 +++++++++------- CodeLineCounter.Tests/CsvHandlerTests.cs | 128 ++++--- .../CyclomaticComplexityCalculatorTests.cs | 111 +++--- CodeLineCounter.Tests/DataExporterTests.cs | 333 ++++++++++-------- .../DependencyGraphGeneratorTests.cs | 151 ++++---- CodeLineCounter.Tests/FileUtilsTests.cs | 84 +++-- CodeLineCounter.Tests/HashUtilsTests.cs | 88 +++-- CodeLineCounter.Tests/JsonHandlerTests.cs | 31 +- .../SolutionAnalyzerTests.cs | 35 +- 11 files changed, 794 insertions(+), 621 deletions(-) diff --git a/CodeLineCounter.Tests/CodeAnalyzerTests.cs b/CodeLineCounter.Tests/CodeAnalyzerTests.cs index e2751b4..4eb13c9 100644 --- a/CodeLineCounter.Tests/CodeAnalyzerTests.cs +++ b/CodeLineCounter.Tests/CodeAnalyzerTests.cs @@ -31,70 +31,80 @@ public void TestAnalyzeSolution() [Fact] public void AnalyzeSourceCode_Should_Set_CurrentNamespace() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); - - // 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() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); - - // 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() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); - - // 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 fdef7ec..c45dfbd 100644 --- a/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs +++ b/CodeLineCounter.Tests/CodeDuplicationCheckerTests.cs @@ -2,7 +2,7 @@ namespace CodeLineCounter.Tests { - public class CodeDuplicationCheckerTests : IDisposable + public class CodeDuplicationCheckerTests : IDisposable { private readonly string _testDirectory; private bool _disposed; @@ -12,13 +12,13 @@ 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 = Path.Combine(_testDirectory, "TestFile1.cs"); var file2 = Path.Combine(_testDirectory, "TestFile2.cs"); @@ -70,13 +70,14 @@ public void AnotherTestMethod() [Fact] public void DetectCodeDuplicationInSourceCode_ShouldDetectDuplicates() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); + 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() @@ -88,7 +89,7 @@ public void TestMethod() } }"; - var sourceCode2 = @" + var sourceCode2 = @" public class AnotherTestClass { public void AnotherTestMethod() @@ -100,30 +101,33 @@ 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 } [Fact] public void DetectCodeDuplicationInSourceCode_ShouldNotDetectDuplicatesForDifferentCode() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); + 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() @@ -135,7 +139,7 @@ public void TestMethod() } }"; - var sourceCode2 = @" + var sourceCode2 = @" public class AnotherTestClass { public void AnotherTestMethod() @@ -144,19 +148,21 @@ 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 } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) { if (!_disposed) { diff --git a/CodeLineCounter.Tests/CoreUtilsTests.cs b/CodeLineCounter.Tests/CoreUtilsTests.cs index f457328..7c49806 100644 --- a/CodeLineCounter.Tests/CoreUtilsTests.cs +++ b/CodeLineCounter.Tests/CoreUtilsTests.cs @@ -131,7 +131,7 @@ public void ParseArguments_handles_invalid_format_option() result = CoreUtils.ParseArguments(args); sortieConsole = sw.ToString(); Assert.Equal(CoreUtils.ExportFormat.CSV, result.Format); - Assert.Contains("Invalid format", sortieConsole.ToString()); + Assert.Contains("Invalid format", sortieConsole); } } @@ -160,16 +160,20 @@ 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); + 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); + } + } } [Fact] @@ -178,16 +182,22 @@ 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); + 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 @@ -196,16 +206,22 @@ 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); + 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] @@ -215,16 +231,22 @@ public void GetUserChoice_returns_valid_selection_for_valid_input() public void GetUserChoice_handles_invalid_input(string input) { // Arrange - using var consoleInput = new StringReader(input); - using 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] @@ -241,45 +263,51 @@ public void DisplaySolutions_Should_Write_Solutions_To_Console() ]; // Redirect console output to a StringWriter - using StringWriter sw = new(); - Console.SetOut(sw); + using (StringWriter sw = new()) + { + Console.SetOut(sw); - // Act - CoreUtils.DisplaySolutions(solutionFiles); + // 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 + 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // 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); + } + } @@ -288,15 +316,18 @@ public void CheckSettings_WhenHelpIsTrue_ReturnsFalse() { // Arrange Settings settings = new Settings(true, null, ".", true, CoreUtils.ExportFormat.JSON); - using var sw = new StringWriter(); - Console.SetOut(sw); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); - // Act - var result = settings.IsValid(); + // Act + var result = settings.IsValid(); + + // Assert + Assert.True(result); + Assert.Contains("Usage:", sw.ToString()); + } - // Assert - Assert.True(result); - Assert.Contains("Usage:", sw.ToString()); } [Fact] @@ -304,44 +335,53 @@ 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); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); - // Act - var result = settings.IsValid(); + // 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // Arrange - Settings settings = new 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 = settings.IsValid(); + // Act + var result = settings.IsValid(); + + // Assert + Assert.True(result); + } - // 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); + 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(); + // Act + var result = settings.IsValid(); + + // Assert + Assert.True(result); + } - // Assert - Assert.True(result); } [Fact] @@ -349,14 +389,17 @@ public void CheckSettings_WhenSettingsAreInvalid_ReturnsFalse() { // Arrange Settings settings = new Settings(false, null, false, CoreUtils.ExportFormat.CSV); - using var sw = new StringWriter(); - Console.SetOut(sw); + using (var sw = new StringWriter()) + { + Console.SetOut(sw); - // Act - var result = settings.IsValid(); + // Act + var result = settings.IsValid(); + + // Assert + Assert.False(result); + } - // Assert - Assert.False(result); } [Theory] @@ -367,61 +410,70 @@ 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); + 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(fullFileName), 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 + 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"); + 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]); + } - // 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"); + 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 result = settings.IsValid(); + Assert.True(result); + Assert.True(Directory.Exists(settings.OutputPath)); + Directory.Delete(settings.OutputPath); + } - Assert.True(result); - Assert.True(Directory.Exists(settings.OutputPath)); - Directory.Delete(settings.OutputPath); } protected virtual void Dispose(bool disposing) diff --git a/CodeLineCounter.Tests/CsvHandlerTests.cs b/CodeLineCounter.Tests/CsvHandlerTests.cs index fadafc8..03b9125 100644 --- a/CodeLineCounter.Tests/CsvHandlerTests.cs +++ b/CodeLineCounter.Tests/CsvHandlerTests.cs @@ -23,97 +23,109 @@ public CsvHandlerTests() [Fact] public void Serialize_ValidData_WritesToFile() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); + 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"); + 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() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); - - // Arrange - string filePath = Path.Combine(_testDirectory,"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); - // Assert - Assert.Equal(2, result.Count); - Assert.Equal("Alice", result[0].Name); - Assert.Equal("Bob", result[1].Name); + // 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); + } - // Cleanup - File.Delete(filePath); } [Fact] public void Serialize_EmptyData_WritesEmptyFile() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); + 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); + } - // 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"); + 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(); + + // Assert + Assert.Empty(result); + + // Cleanup + File.Delete(filePath); + } - // Cleanup - File.Delete(filePath); } protected virtual void Dispose(bool disposing) diff --git a/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs b/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs index 8d0cd22..be937bc 100644 --- a/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs +++ b/CodeLineCounter.Tests/CyclomaticComplexityCalculatorTests.cs @@ -9,10 +9,11 @@ public class CyclomaticComplexityCalculatorTests [Fact] public void TestCalculateComplexity() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // Arrange - var code = @" + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + var code = @" public class TestClass { public void TestMethod() @@ -21,23 +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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // Arrange - var syntaxTree = CSharpSyntaxTree.ParseText(@" + using (var sw = new StringWriter()) + { + Console.SetOut(sw); + // Arrange + var syntaxTree = CSharpSyntaxTree.ParseText(@" public class MyClass { public void MyMethod() @@ -52,56 +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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // 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 faba5b6..a6b19d5 100644 --- a/CodeLineCounter.Tests/DataExporterTests.cs +++ b/CodeLineCounter.Tests/DataExporterTests.cs @@ -19,17 +19,20 @@ 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"; + 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, _testDirectory, testItem, format); + // Assert + Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + expectedExtension))); + } - // Assert - Assert.True(File.Exists(Path.Combine(_testDirectory, filePath + expectedExtension))); } [Theory] @@ -37,257 +40,293 @@ 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 + 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, _testDirectory, items, format); + + // Assert + Assert.True(File.Exists(filePath + expectedExtension)); + } - // 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 - { - 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 + using (var sw = new StringWriter()) { - Metrics = metrics, - ProjectTotals = projectTotals, - DuplicationMap = duplications, - DependencyList = new List(), - TotalLines = 300, - SolutionFileName = "TestSolution" - }; - - // Act - DataExporter.ExportMetrics(filePath, _testDirectory, result, ".",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(filePath + ".csv")); + } - // Assert - Assert.True(File.Exists(filePath + ".csv")); } [Fact] public void ExportDuplications_CreatesFileWithCorrectData() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // 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 = "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, _testDirectory, duplications, CoreUtils.ExportFormat.CSV); + + // Assert + Assert.True(File.Exists(filePath + ".csv")); + } - // Assert - Assert.True(File.Exists(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 + 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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 = "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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // 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.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)) + 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 diff --git a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs index 8b7a65c..6631aef 100644 --- a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs +++ b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs @@ -19,135 +19,156 @@ public DependencyGraphGeneratorTests() [Fact] public async Task generate_graph_with_valid_dependencies_creates_dot_file() { - using var sw = new StringWriter(); - Console.SetOut(sw); + 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 = "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 fileName = "test_graph.dot"; + string fileName = "test_graph.dot"; - string outputPath = Path.Combine(_testDirectory, fileName); + 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); + 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); - // 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); + 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); + + File.Delete(outputPath); + } - File.Delete(outputPath); } [Fact] public void create_node_sets_correct_fillcolor_and_style_incoming_greater() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - // 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() { - using var sw = new StringWriter(); - Console.SetOut(sw); - 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) diff --git a/CodeLineCounter.Tests/FileUtilsTests.cs b/CodeLineCounter.Tests/FileUtilsTests.cs index e78b907..3465949 100644 --- a/CodeLineCounter.Tests/FileUtilsTests.cs +++ b/CodeLineCounter.Tests/FileUtilsTests.cs @@ -15,61 +15,73 @@ public FileUtilsTests() [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)); + 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)); + } + } [Fact] public void GetBasePath_Should_Return_NonEmptyString() { - 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."); + 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."); + } + } [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()); + 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() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); - // Arrange - var nonExistentPath = Path.Combine(_testDirectory, "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)); - // Act & Assert - var exception = Assert.Throws(() => - FileUtils.GetProjectFiles(nonExistentPath)); + Assert.Contains(nonExistentPath, exception.Message); + } - Assert.Contains(nonExistentPath, exception.Message); } protected virtual void Dispose(bool disposing) diff --git a/CodeLineCounter.Tests/HashUtilsTests.cs b/CodeLineCounter.Tests/HashUtilsTests.cs index 97465dd..02bcd8a 100644 --- a/CodeLineCounter.Tests/HashUtilsTests.cs +++ b/CodeLineCounter.Tests/HashUtilsTests.cs @@ -7,68 +7,80 @@ public class HashUtilsTests [Fact] public void ComputeHash_EmptyString_ReturnsEmptyString() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); + using (StringWriter consoleOutput = new()) + { + Console.SetOut(consoleOutput); - // Arrange - string input = ""; + // Arrange + string input = ""; - // Act - string result = HashUtils.ComputeHash(input); + // Act + string result = HashUtils.ComputeHash(input); + + // Assert + Assert.Equal("", result); + } - // Assert - Assert.Equal("", result); } [Fact] public void ComputeHash_NullString_ReturnsEmptyString() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); + 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); } [Fact] public void ComputeHash_ValidString_ReturnsHash() { - using StringWriter consoleOutput = new(); - Console.SetOut(consoleOutput); + 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); + } - // 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); - - // Assert - Assert.Equal(result1, result2); + 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); + + // Assert + Assert.Equal(result1, result2); + } + } } } \ No newline at end of file diff --git a/CodeLineCounter.Tests/JsonHandlerTests.cs b/CodeLineCounter.Tests/JsonHandlerTests.cs index 5d37975..a78f08c 100644 --- a/CodeLineCounter.Tests/JsonHandlerTests.cs +++ b/CodeLineCounter.Tests/JsonHandlerTests.cs @@ -3,7 +3,7 @@ namespace CodeLineCounter.Tests { - public class JsonHandlerTests :IDisposable + public class JsonHandlerTests : IDisposable { private readonly string _testDirectory; @@ -30,29 +30,30 @@ public TestClass(int id, string name) [Fact] public void deserialize_valid_json_file_returns_expected_objects() { - using (StringWriter consoleOutput = new()){ + 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); + // 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) + protected virtual void Dispose(bool disposing) { if (!_disposed) { diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index c01307e..4c1fc69 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -177,26 +177,25 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals() [Fact] public void export_results_with_valid_input_exports_all_files() { - // Arrange - var result = new AnalysisResult - { - SolutionFileName = "TestSolution", - Metrics = new List(), - ProjectTotals = new Dictionary(), - TotalLines = 1000, - DuplicationMap = new List(), - DependencyList = new List() - }; - - var basePath = _testDirectory; - var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); - - solutionPath = Path.Combine(solutionPath, "TestSolution.sln"); - var format = CoreUtils.ExportFormat.CSV; - - // Act + // Act using (var sw = new StringWriter()) { + // Arrange + var result = new AnalysisResult + { + SolutionFileName = "TestSolution", + Metrics = new List(), + ProjectTotals = new Dictionary(), + TotalLines = 1000, + DuplicationMap = new List(), + DependencyList = new List() + }; + + var basePath = _testDirectory; + var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..")); + + solutionPath = Path.Combine(solutionPath, "TestSolution.sln"); + var format = CoreUtils.ExportFormat.CSV; Console.SetOut(sw); SolutionAnalyzer.ExportResults(result, solutionPath, format); // Assert From 0268e0bbc9735a737f8dbb322b1ac483283c6154 Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Sat, 18 Jan 2025 11:28:24 +0100 Subject: [PATCH 6/9] refactor: enhance test structure and improve output handling in SolutionAnalyzerTests --- .../SolutionAnalyzerTests.cs | 162 +++++++++++------- 1 file changed, 97 insertions(+), 65 deletions(-) diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index 4c1fc69..320ef2f 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -8,49 +8,82 @@ 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\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 solutionPath = _testSolutionPath; + var verbose = true; + var format = CoreUtils.ExportFormat.CSV; + var outputPath = _outputPath; - var verbose = false; - var format = CoreUtils.ExportFormat.JSON; + try + { + // Redirect console output + using (StringWriter stringWriter = new StringWriter()) + { - // Act & Assert - using (var sw = 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"))); + } + + } + finally { - Console.SetOut(sw); - var exception = Record.Exception(() => - SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, verbose, format)); + // Cleanup - Assert.Null(exception); + 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; - - // Act & Assert 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(() => SolutionAnalyzer.AnalyzeAndExportSolution(invalidPath, verbose, format)); @@ -89,24 +122,23 @@ public void PerformAnalysis_ShouldReturnCorrectAnalysisResult() [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); @@ -124,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); @@ -177,7 +208,7 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals() [Fact] public void export_results_with_valid_input_exports_all_files() { - // Act + // Act using (var sw = new StringWriter()) { // Arrange @@ -214,6 +245,7 @@ protected virtual void Dispose(bool disposing) if (disposing && Directory.Exists(_testDirectory)) { // Dispose managed resources + File.Delete(_testSolutionPath); Directory.Delete(_testDirectory, true); } From 1f798dc12b99a59b506692ae256ac7fbc3fbc61b Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Sat, 18 Jan 2025 11:36:59 +0100 Subject: [PATCH 7/9] refactor: update file path handling in DataExporterTests to use Path.Combine for improved clarity --- CodeLineCounter.Tests/DataExporterTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeLineCounter.Tests/DataExporterTests.cs b/CodeLineCounter.Tests/DataExporterTests.cs index a6b19d5..0f80e6e 100644 --- a/CodeLineCounter.Tests/DataExporterTests.cs +++ b/CodeLineCounter.Tests/DataExporterTests.cs @@ -93,7 +93,7 @@ public void ExportMetrics_CreatesFileWithCorrectData() DataExporter.ExportMetrics(filePath, _testDirectory, result, ".", CoreUtils.ExportFormat.CSV); // Assert - Assert.True(File.Exists(filePath + ".csv")); + Assert.True(File.Exists(Path.Combine(_testDirectory,filePath + ".csv"))); } } @@ -116,7 +116,7 @@ public void ExportDuplications_CreatesFileWithCorrectData() DataExporter.ExportDuplications(filePath, _testDirectory, duplications, CoreUtils.ExportFormat.CSV); // Assert - Assert.True(File.Exists(filePath + ".csv")); + Assert.True(File.Exists(Path.Combine(_testDirectory,filePath + ".csv"))); } } From af636d74ecbdef41a820a07faccff2253142cd9c Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Sat, 18 Jan 2025 11:56:19 +0100 Subject: [PATCH 8/9] refactor: update solution file name and output paths in SolutionAnalyzerTests for consistency --- CodeLineCounter.Tests/SolutionAnalyzerTests.cs | 16 ++++++++-------- CodeLineCounter/Services/SolutionAnalyzer.cs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index 320ef2f..6e9ed02 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -214,7 +214,7 @@ public void export_results_with_valid_input_exports_all_files() // Arrange var result = new AnalysisResult { - SolutionFileName = "TestSolution", + SolutionFileName = "CodelineCounter.sln", Metrics = new List(), ProjectTotals = new Dictionary(), TotalLines = 1000, @@ -222,17 +222,17 @@ public void export_results_with_valid_input_exports_all_files() DependencyList = new List() }; - var basePath = _testDirectory; - 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 solutionPath = Path.Combine(baseSolutionPath, "CodelineCounter.sln"); var format = CoreUtils.ExportFormat.CSV; Console.SetOut(sw); - SolutionAnalyzer.ExportResults(result, solutionPath, format); + SolutionAnalyzer.ExportResults(result, solutionPath, format, baseSolutionPath); // Assert - Assert.True(File.Exists("TestSolution-CodeMetrics.csv")); - Assert.True(File.Exists("TestSolution-CodeDuplications.csv")); - Assert.True(File.Exists("TestSolution-CodeDependencies.csv")); + 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"))); } diff --git a/CodeLineCounter/Services/SolutionAnalyzer.cs b/CodeLineCounter/Services/SolutionAnalyzer.cs index 971a3d5..01eec05 100644 --- a/CodeLineCounter/Services/SolutionAnalyzer.cs +++ b/CodeLineCounter/Services/SolutionAnalyzer.cs @@ -82,7 +82,7 @@ public static void ExportResults(AnalysisResult result, string solutionPath, Cor : metricsFileName; // Export duplications - string duplicationsFileName = $"{baseFileName}-CodeDuplication"; + string duplicationsFileName = $"{baseFileName}-CodeDuplications"; duplicationsFileName = CoreUtils.GetExportFileNameWithExtension(duplicationsFileName, format); string duplicationsOutputPath = outputPath != null ? Path.Combine(outputPath, duplicationsFileName) From 1e2562fda59c908144257cfff75b2d52ddcb9bb6 Mon Sep 17 00:00:00 2001 From: Gildas Le Bournault Date: Sat, 18 Jan 2025 12:03:02 +0100 Subject: [PATCH 9/9] refactor: correct project file path in SolutionAnalyzerTests for consistency --- CodeLineCounter.Tests/SolutionAnalyzerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs index 6e9ed02..c99261e 100644 --- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs +++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs @@ -26,7 +26,7 @@ public SolutionAnalyzerTest() # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""TestProject"", ""TestProject\TestProject.csproj"", ""{F60AAFF8-A3B6-4D3C-9372-06A0F1B6F82B}"" +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""TestProject"", ""TestProject.csproj"", ""{F60AAFF8-A3B6-4D3C-9372-06A0F1B6F82B}"" EndProject"); } }