diff --git a/.vscode/launch.json b/.vscode/launch.json
index b1cf6dd..60f073a 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,6 +10,16 @@
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
+ },
+ {
+ "name": "C#: CodeLineCounter - Isacompta",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceFolder}/CodeLineCounter/bin/Debug/net9.0/CodeLineCounter.dll",
+ "args": ["-d", "E:/_TFS/Isagri.CO/Isagri_Dev_CO_Compta/Isagri.CO/Main/Isaco/DotNet/Sources"],
+ "cwd": "${workspaceFolder}",
+ "stopAtEntry": false,
+ "console": "internalConsole"
}
]
}
\ No newline at end of file
diff --git a/CodeLineCounter.Tests/CodeLineCounter.Tests.csproj b/CodeLineCounter.Tests/CodeLineCounter.Tests.csproj
index 3fe20b0..adc377b 100644
--- a/CodeLineCounter.Tests/CodeLineCounter.Tests.csproj
+++ b/CodeLineCounter.Tests/CodeLineCounter.Tests.csproj
@@ -15,6 +15,7 @@
all
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/CodeLineCounter.Tests/DataExporterTests.cs b/CodeLineCounter.Tests/DataExporterTests.cs
index c5fc571..fa50c5c 100644
--- a/CodeLineCounter.Tests/DataExporterTests.cs
+++ b/CodeLineCounter.Tests/DataExporterTests.cs
@@ -1,5 +1,6 @@
using CodeLineCounter.Models;
using CodeLineCounter.Utils;
+using Moq;
namespace CodeLineCounter.Tests
{
@@ -212,7 +213,7 @@ public void get_file_duplications_count_uses_current_dir_for_null_solution_path(
// Successfully exports dependencies to specified format (CSV/JSON) and creates DOT file
[Fact]
- public void export_dependencies_creates_files_in_correct_formats()
+ public async Task export_dependencies_creates_files_in_correct_formats()
{
// Arrange
var dependencies = new List
@@ -224,7 +225,7 @@ public void export_dependencies_creates_files_in_correct_formats()
var format = CoreUtils.ExportFormat.JSON;
// Act
- DataExporter.ExportDependencies(testFilePath, dependencies, format);
+ await DataExporter.ExportDependencies(testFilePath, dependencies, format);
// Assert
string expectedJsonPath = CoreUtils.GetExportFileNameWithExtension(testFilePath, format);
@@ -240,7 +241,7 @@ public void export_dependencies_creates_files_in_correct_formats()
File.Delete(expectedJsonPath);
}
- if (File.Exists(expectedDotPath))
+ if (File.Exists(expectedDotPath))
{
File.Delete(expectedDotPath);
}
@@ -249,12 +250,27 @@ public void export_dependencies_creates_files_in_correct_formats()
{
throw new IOException($"Error deleting files: {ex.Message}", ex);
}
- catch (UnauthorizedAccessException ex)
+ catch (UnauthorizedAccessException ex)
{
throw new UnauthorizedAccessException($"Access denied while deleting files: {ex.Message}", ex);
}
}
+ // Throws ArgumentException when file path is null
+ [Fact]
+ public void export_collection_throws_when_filepath_null()
+ {
+ // Arrange
+ string? filePath = null;
+ var testData = new List { new TestClass { Id = 1, Name = "Test" } };
+ var format = CoreUtils.ExportFormat.CSV;
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ DataExporter.ExportCollection(filePath, testData, format));
+ Assert.Equal("File path cannot be null or empty (Parameter 'filePath')", exception.Message);
+ }
+
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
diff --git a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs
index b6b8fc2..b78f1bd 100644
--- a/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs
+++ b/CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs
@@ -1,5 +1,7 @@
using CodeLineCounter.Models;
using CodeLineCounter.Services;
+using DotNetGraph.Core;
+using DotNetGraph.Extensions;
namespace CodeLineCounter.Tests
{
@@ -18,7 +20,9 @@ public async Task generate_graph_with_valid_dependencies_creates_dot_file()
string outputPath = Path.Combine(Path.GetTempPath(), "test_graph.dot");
// Act
- await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
+
+ DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);
+ await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph);
// Assert
Assert.True(File.Exists(outputPath));
@@ -39,7 +43,8 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph()
string outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot");
// Act
- await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
+ DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);
+ await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph);
// Assert
Assert.True(File.Exists(outputPath));
@@ -50,6 +55,34 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph()
File.Delete(outputPath);
}
+ [Fact]
+ public void create_node_sets_correct_fillcolor_and_style_incoming_greater()
+ {
+ var vertexInfo = new Dictionary
+ {
+ { "TestVertex", (3, 2) }
+ };
+
+ DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex");
+
+ Assert.Equal(DotColor.MediumSeaGreen.ToHexString(), node.FillColor.Value);
+ Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value);
+ }
+
+ [Fact]
+ public void create_node_sets_correct_fillcolor_and_style_incoming_lower()
+ {
+ var vertexInfo = new Dictionary
+ {
+ { "TestVertex", (3, 4) }
+ };
+
+ DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex");
+
+ Assert.Equal(DotColor.Salmon.ToHexString(), node.FillColor.Value);
+ Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value);
+ }
+
}
}
diff --git a/CodeLineCounter.Tests/FileUtilsTests.cs b/CodeLineCounter.Tests/FileUtilsTests.cs
index 91d146a..ea4c5f0 100644
--- a/CodeLineCounter.Tests/FileUtilsTests.cs
+++ b/CodeLineCounter.Tests/FileUtilsTests.cs
@@ -31,5 +31,29 @@ public void GetBasePath_Should_Return_NonEmptyString()
Assert.False(string.IsNullOrEmpty(basePath), "Base path should not be null or empty.");
Assert.True(Directory.Exists(basePath), "Base path should be a valid directory.");
}
+
+ [Fact]
+ public void get_solution_files_throws_exception_for_nonexistent_directory()
+ {
+ // Arrange
+ var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+
+ // Act & Assert
+ Assert.Throws(() => FileUtils.GetSolutionFiles(nonExistentPath));
+ }
+
+ // Throws UnauthorizedAccessException when solution file does not exist
+ [Fact]
+ public void get_project_files_throws_when_file_not_exists()
+ {
+ // Arrange
+ var nonExistentPath = "nonexistent.sln";
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ FileUtils.GetProjectFiles(nonExistentPath));
+
+ Assert.Contains(nonExistentPath, exception.Message);
+ }
}
}
\ No newline at end of file
diff --git a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs
index bdd665e..5c8f71d 100644
--- a/CodeLineCounter.Tests/SolutionAnalyzerTests.cs
+++ b/CodeLineCounter.Tests/SolutionAnalyzerTests.cs
@@ -7,6 +7,40 @@ namespace CodeLineCounter.Tests.Services
public class SolutionAnalyzerTest
{
+ [Fact]
+ public void analyze_and_export_solution_succeeds_with_valid_inputs()
+ {
+ // Arrange
+ var basePath = FileUtils.GetBasePath();
+ var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
+ solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln");
+ var sw = new StringWriter();
+ Console.SetOut(sw);
+ var verbose = false;
+ var format = CoreUtils.ExportFormat.JSON;
+
+ // Act & Assert
+ var exception = Record.Exception(() =>
+ SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, verbose, format));
+
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void analyze_and_export_solution_throws_on_invalid_path()
+ {
+ // Arrange
+ var invalidPath = "";
+ var verbose = false;
+ var format = CoreUtils.ExportFormat.JSON;
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ SolutionAnalyzer.AnalyzeAndExportSolution(invalidPath, verbose, format));
+
+ Assert.Contains("Access to the path '' is denied.", exception.Message);
+ }
+
[Fact]
public void PerformAnalysis_ShouldReturnCorrectAnalysisResult()
{
@@ -106,7 +140,7 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
SolutionAnalyzer.OutputDetailedMetrics(metrics, projectTotals);
// Assert
- var expectedOutput =
+ var expectedOutput =
$"Project Project1 (/path/to/project1) - Namespace Namespace1 in file File1.cs (/path/to/project1/File1.cs) has 100 lines of code and a cyclomatic complexity of 10.{Environment.NewLine}" +
$"Project Project2 (/path/to/project2) - Namespace Namespace2 in file File2.cs (/path/to/project2/File2.cs) has 200 lines of code and a cyclomatic complexity of 20.{Environment.NewLine}" +
$"Project Project1 has 100 total lines of code.{Environment.NewLine}" +
@@ -116,39 +150,39 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
}
}
- // Export metrics, duplications and dependencies data in parallel for valid input
-[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 = FileUtils.GetBasePath();
- var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
-
- solutionPath = Path.Combine(solutionPath, "TestSolution.sln");
- var format = CoreUtils.ExportFormat.CSV;
-
- // Act
- using (var sw = new StringWriter())
- {
- Console.SetOut(sw);
- SolutionAnalyzer.ExportResults(result, solutionPath, format);
- }
+ // Export metrics, duplications and dependencies data in parallel for valid input
+ [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 = FileUtils.GetBasePath();
+ var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
+
+ solutionPath = Path.Combine(solutionPath, "TestSolution.sln");
+ var format = CoreUtils.ExportFormat.CSV;
- // Assert
- Assert.True(File.Exists("TestSolution-CodeMetrics.csv"));
- Assert.True(File.Exists("TestSolution-CodeDuplications.csv"));
- Assert.True(File.Exists("TestSolution-CodeDependencies.csv"));
-}
+ // Act
+ using (var sw = new StringWriter())
+ {
+ 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"));
+ }
}
}
\ No newline at end of file
diff --git a/CodeLineCounter/Services/DependencyGraphGenerator.cs b/CodeLineCounter/Services/DependencyGraphGenerator.cs
index b9331f1..d2c56b0 100644
--- a/CodeLineCounter/Services/DependencyGraphGenerator.cs
+++ b/CodeLineCounter/Services/DependencyGraphGenerator.cs
@@ -10,7 +10,7 @@ namespace CodeLineCounter.Services
{
public static class DependencyGraphGenerator
{
- public static async Task GenerateGraph(List dependencies, string outputPath, string? filterNamespace = null, string? filterAssembly = null)
+ public static DotGraph GenerateGraphOnly(List dependencies, string? filterNamespace = null, string? filterAssembly = null)
{
var filteredDependencies = dependencies;
filteredDependencies = FilterNamespaceFromDependencies(dependencies, filterNamespace, filteredDependencies);
@@ -51,7 +51,7 @@ public static async Task GenerateGraph(List dependencies, st
graph.Elements.Add(edge);
}
- await CompileGraphAndWriteToFile(outputPath, graph);
+ return graph;
}
private static DotEdge CreateEdge(DependencyRelation dep)
@@ -80,7 +80,7 @@ private static DotSubgraph CreateCluster(KeyValuePair> nsGr
return cluster;
}
- private static DotNode CreateNode(Dictionary vertexInfo, string vertex)
+ public static DotNode CreateNode(Dictionary vertexInfo, string vertex)
{
var info = vertexInfo[vertex];
var node = new DotNode();
@@ -142,7 +142,7 @@ private static void GroupByNamespace(Dictionary(string filePath, T data, CoreUtils.ExportFormat for
ExportCollection(filePath, [data], format);
}
- public static void ExportCollection(string filePath, IEnumerable data, CoreUtils.ExportFormat format) where T : class
+ public static void ExportCollection(string? filePath, IEnumerable data, CoreUtils.ExportFormat format) where T : class
{
if (string.IsNullOrEmpty(filePath))
throw new ArgumentException("File path cannot be null or empty", nameof(filePath));
@@ -48,7 +49,8 @@ public static async Task ExportDependencies(string filePath, List metrics,