Skip to content

Commit 25c7ed6

Browse files
authored
Merge pull request #25 from magic5644:fix-github
Enhance tests and refactorisations
2 parents 7bf6bb7 + ed53387 commit 25c7ed6

8 files changed

Lines changed: 165 additions & 45 deletions

File tree

.vscode/launch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010
"cwd": "${workspaceFolder}",
1111
"stopAtEntry": false,
1212
"console": "internalConsole"
13+
},
14+
{
15+
"name": "C#: CodeLineCounter - Isacompta",
16+
"type": "coreclr",
17+
"request": "launch",
18+
"program": "${workspaceFolder}/CodeLineCounter/bin/Debug/net9.0/CodeLineCounter.dll",
19+
"args": ["-d", "E:/_TFS/Isagri.CO/Isagri_Dev_CO_Compta/Isagri.CO/Main/Isaco/DotNet/Sources"],
20+
"cwd": "${workspaceFolder}",
21+
"stopAtEntry": false,
22+
"console": "internalConsole"
1323
}
1424
]
1525
}

CodeLineCounter.Tests/CodeLineCounter.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PrivateAssets>all</PrivateAssets>
1616
</PackageReference>
1717
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
18+
<PackageReference Include="Moq" Version="4.20.72" />
1819
<PackageReference Include="xunit" Version="2.9.2" />
1920
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0">
2021
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

CodeLineCounter.Tests/DataExporterTests.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CodeLineCounter.Models;
22
using CodeLineCounter.Utils;
3+
using Moq;
34

45
namespace CodeLineCounter.Tests
56
{
@@ -212,7 +213,7 @@ public void get_file_duplications_count_uses_current_dir_for_null_solution_path(
212213

213214
// Successfully exports dependencies to specified format (CSV/JSON) and creates DOT file
214215
[Fact]
215-
public void export_dependencies_creates_files_in_correct_formats()
216+
public async Task export_dependencies_creates_files_in_correct_formats()
216217
{
217218
// Arrange
218219
var dependencies = new List<DependencyRelation>
@@ -224,7 +225,7 @@ public void export_dependencies_creates_files_in_correct_formats()
224225
var format = CoreUtils.ExportFormat.JSON;
225226

226227
// Act
227-
DataExporter.ExportDependencies(testFilePath, dependencies, format);
228+
await DataExporter.ExportDependencies(testFilePath, dependencies, format);
228229

229230
// Assert
230231
string expectedJsonPath = CoreUtils.GetExportFileNameWithExtension(testFilePath, format);
@@ -240,7 +241,7 @@ public void export_dependencies_creates_files_in_correct_formats()
240241
File.Delete(expectedJsonPath);
241242
}
242243

243-
if (File.Exists(expectedDotPath))
244+
if (File.Exists(expectedDotPath))
244245
{
245246
File.Delete(expectedDotPath);
246247
}
@@ -249,12 +250,27 @@ public void export_dependencies_creates_files_in_correct_formats()
249250
{
250251
throw new IOException($"Error deleting files: {ex.Message}", ex);
251252
}
252-
catch (UnauthorizedAccessException ex)
253+
catch (UnauthorizedAccessException ex)
253254
{
254255
throw new UnauthorizedAccessException($"Access denied while deleting files: {ex.Message}", ex);
255256
}
256257
}
257258

259+
// Throws ArgumentException when file path is null
260+
[Fact]
261+
public void export_collection_throws_when_filepath_null()
262+
{
263+
// Arrange
264+
string? filePath = null;
265+
var testData = new List<TestClass> { new TestClass { Id = 1, Name = "Test" } };
266+
var format = CoreUtils.ExportFormat.CSV;
267+
268+
// Act & Assert
269+
var exception = Assert.Throws<ArgumentException>(() =>
270+
DataExporter.ExportCollection(filePath, testData, format));
271+
Assert.Equal("File path cannot be null or empty (Parameter 'filePath')", exception.Message);
272+
}
273+
258274
protected virtual void Dispose(bool disposing)
259275
{
260276
if (!_disposed)

CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using CodeLineCounter.Models;
22
using CodeLineCounter.Services;
3+
using DotNetGraph.Core;
4+
using DotNetGraph.Extensions;
35

46
namespace CodeLineCounter.Tests
57
{
@@ -18,7 +20,9 @@ public async Task generate_graph_with_valid_dependencies_creates_dot_file()
1820
string outputPath = Path.Combine(Path.GetTempPath(), "test_graph.dot");
1921

2022
// Act
21-
await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
23+
24+
DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);
25+
await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph);
2226

2327
// Assert
2428
Assert.True(File.Exists(outputPath));
@@ -39,7 +43,8 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph()
3943
string outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot");
4044

4145
// Act
42-
await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
46+
DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);
47+
await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph);
4348

4449
// Assert
4550
Assert.True(File.Exists(outputPath));
@@ -50,6 +55,34 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph()
5055
File.Delete(outputPath);
5156
}
5257

58+
[Fact]
59+
public void create_node_sets_correct_fillcolor_and_style_incoming_greater()
60+
{
61+
var vertexInfo = new Dictionary<string, (int incoming, int outgoing)>
62+
{
63+
{ "TestVertex", (3, 2) }
64+
};
65+
66+
DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex");
67+
68+
Assert.Equal(DotColor.MediumSeaGreen.ToHexString(), node.FillColor.Value);
69+
Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value);
70+
}
71+
72+
[Fact]
73+
public void create_node_sets_correct_fillcolor_and_style_incoming_lower()
74+
{
75+
var vertexInfo = new Dictionary<string, (int incoming, int outgoing)>
76+
{
77+
{ "TestVertex", (3, 4) }
78+
};
79+
80+
DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex");
81+
82+
Assert.Equal(DotColor.Salmon.ToHexString(), node.FillColor.Value);
83+
Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value);
84+
}
85+
5386

5487
}
5588
}

CodeLineCounter.Tests/FileUtilsTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,29 @@ public void GetBasePath_Should_Return_NonEmptyString()
3131
Assert.False(string.IsNullOrEmpty(basePath), "Base path should not be null or empty.");
3232
Assert.True(Directory.Exists(basePath), "Base path should be a valid directory.");
3333
}
34+
35+
[Fact]
36+
public void get_solution_files_throws_exception_for_nonexistent_directory()
37+
{
38+
// Arrange
39+
var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
40+
41+
// Act & Assert
42+
Assert.Throws<UnauthorizedAccessException>(() => FileUtils.GetSolutionFiles(nonExistentPath));
43+
}
44+
45+
// Throws UnauthorizedAccessException when solution file does not exist
46+
[Fact]
47+
public void get_project_files_throws_when_file_not_exists()
48+
{
49+
// Arrange
50+
var nonExistentPath = "nonexistent.sln";
51+
52+
// Act & Assert
53+
var exception = Assert.Throws<UnauthorizedAccessException>(() =>
54+
FileUtils.GetProjectFiles(nonExistentPath));
55+
56+
Assert.Contains(nonExistentPath, exception.Message);
57+
}
3458
}
3559
}

CodeLineCounter.Tests/SolutionAnalyzerTests.cs

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,40 @@ namespace CodeLineCounter.Tests.Services
77
public class SolutionAnalyzerTest
88
{
99

10+
[Fact]
11+
public void analyze_and_export_solution_succeeds_with_valid_inputs()
12+
{
13+
// Arrange
14+
var basePath = FileUtils.GetBasePath();
15+
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
16+
solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln");
17+
var sw = new StringWriter();
18+
Console.SetOut(sw);
19+
var verbose = false;
20+
var format = CoreUtils.ExportFormat.JSON;
21+
22+
// Act & Assert
23+
var exception = Record.Exception(() =>
24+
SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, verbose, format));
25+
26+
Assert.Null(exception);
27+
}
28+
29+
[Fact]
30+
public void analyze_and_export_solution_throws_on_invalid_path()
31+
{
32+
// Arrange
33+
var invalidPath = "";
34+
var verbose = false;
35+
var format = CoreUtils.ExportFormat.JSON;
36+
37+
// Act & Assert
38+
var exception = Assert.Throws<UnauthorizedAccessException>(() =>
39+
SolutionAnalyzer.AnalyzeAndExportSolution(invalidPath, verbose, format));
40+
41+
Assert.Contains("Access to the path '' is denied.", exception.Message);
42+
}
43+
1044
[Fact]
1145
public void PerformAnalysis_ShouldReturnCorrectAnalysisResult()
1246
{
@@ -106,7 +140,7 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
106140
SolutionAnalyzer.OutputDetailedMetrics(metrics, projectTotals);
107141

108142
// Assert
109-
var expectedOutput =
143+
var expectedOutput =
110144
$"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}" +
111145
$"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}" +
112146
$"Project Project1 has 100 total lines of code.{Environment.NewLine}" +
@@ -116,39 +150,39 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
116150
}
117151
}
118152

119-
// Export metrics, duplications and dependencies data in parallel for valid input
120-
[Fact]
121-
public void export_results_with_valid_input_exports_all_files()
122-
{
123-
// Arrange
124-
var result = new AnalysisResult
125-
{
126-
SolutionFileName = "TestSolution",
127-
Metrics = new List<NamespaceMetrics>(),
128-
ProjectTotals = new Dictionary<string, int >(),
129-
TotalLines = 1000,
130-
DuplicationMap = new List<DuplicationCode>(),
131-
DependencyList = new List<DependencyRelation>()
132-
};
133-
134-
var basePath = FileUtils.GetBasePath();
135-
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
136-
137-
solutionPath = Path.Combine(solutionPath, "TestSolution.sln");
138-
var format = CoreUtils.ExportFormat.CSV;
139-
140-
// Act
141-
using (var sw = new StringWriter())
142-
{
143-
Console.SetOut(sw);
144-
SolutionAnalyzer.ExportResults(result, solutionPath, format);
145-
}
153+
// Export metrics, duplications and dependencies data in parallel for valid input
154+
[Fact]
155+
public void export_results_with_valid_input_exports_all_files()
156+
{
157+
// Arrange
158+
var result = new AnalysisResult
159+
{
160+
SolutionFileName = "TestSolution",
161+
Metrics = new List<NamespaceMetrics>(),
162+
ProjectTotals = new Dictionary<string, int>(),
163+
TotalLines = 1000,
164+
DuplicationMap = new List<DuplicationCode>(),
165+
DependencyList = new List<DependencyRelation>()
166+
};
167+
168+
var basePath = FileUtils.GetBasePath();
169+
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
170+
171+
solutionPath = Path.Combine(solutionPath, "TestSolution.sln");
172+
var format = CoreUtils.ExportFormat.CSV;
146173

147-
// Assert
148-
Assert.True(File.Exists("TestSolution-CodeMetrics.csv"));
149-
Assert.True(File.Exists("TestSolution-CodeDuplications.csv"));
150-
Assert.True(File.Exists("TestSolution-CodeDependencies.csv"));
151-
}
174+
// Act
175+
using (var sw = new StringWriter())
176+
{
177+
Console.SetOut(sw);
178+
SolutionAnalyzer.ExportResults(result, solutionPath, format);
179+
}
180+
181+
// Assert
182+
Assert.True(File.Exists("TestSolution-CodeMetrics.csv"));
183+
Assert.True(File.Exists("TestSolution-CodeDuplications.csv"));
184+
Assert.True(File.Exists("TestSolution-CodeDependencies.csv"));
185+
}
152186

153187
}
154188
}

CodeLineCounter/Services/DependencyGraphGenerator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace CodeLineCounter.Services
1010
{
1111
public static class DependencyGraphGenerator
1212
{
13-
public static async Task GenerateGraph(List<DependencyRelation> dependencies, string outputPath, string? filterNamespace = null, string? filterAssembly = null)
13+
public static DotGraph GenerateGraphOnly(List<DependencyRelation> dependencies, string? filterNamespace = null, string? filterAssembly = null)
1414
{
1515
var filteredDependencies = dependencies;
1616
filteredDependencies = FilterNamespaceFromDependencies(dependencies, filterNamespace, filteredDependencies);
@@ -51,7 +51,7 @@ public static async Task GenerateGraph(List<DependencyRelation> dependencies, st
5151
graph.Elements.Add(edge);
5252
}
5353

54-
await CompileGraphAndWriteToFile(outputPath, graph);
54+
return graph;
5555
}
5656

5757
private static DotEdge CreateEdge(DependencyRelation dep)
@@ -80,7 +80,7 @@ private static DotSubgraph CreateCluster(KeyValuePair<string, List<string>> nsGr
8080
return cluster;
8181
}
8282

83-
private static DotNode CreateNode(Dictionary<string, (int incoming, int outgoing)> vertexInfo, string vertex)
83+
public static DotNode CreateNode(Dictionary<string, (int incoming, int outgoing)> vertexInfo, string vertex)
8484
{
8585
var info = vertexInfo[vertex];
8686
var node = new DotNode();
@@ -142,7 +142,7 @@ private static void GroupByNamespace(Dictionary<string, (int incoming, int outgo
142142
}
143143
}
144144

145-
private static async Task CompileGraphAndWriteToFile(string outputPath, DotGraph graph)
145+
public static async Task CompileGraphAndWriteToFile(string outputPath, DotGraph graph)
146146
{
147147
await using var writer = new StringWriter();
148148
var options = new CompilationOptions

CodeLineCounter/Utils/DataExporter.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CodeLineCounter.Models;
22
using CodeLineCounter.Services;
3+
using DotNetGraph.Core;
34

45

56
namespace CodeLineCounter.Utils
@@ -20,7 +21,7 @@ public static void Export<T>(string filePath, T data, CoreUtils.ExportFormat for
2021
ExportCollection(filePath, [data], format);
2122
}
2223

23-
public static void ExportCollection<T>(string filePath, IEnumerable<T> data, CoreUtils.ExportFormat format) where T : class
24+
public static void ExportCollection<T>(string? filePath, IEnumerable<T> data, CoreUtils.ExportFormat format) where T : class
2425
{
2526
if (string.IsNullOrEmpty(filePath))
2627
throw new ArgumentException("File path cannot be null or empty", nameof(filePath));
@@ -48,7 +49,8 @@ public static async Task ExportDependencies(string filePath, List<DependencyRela
4849
string outputFilePath = CoreUtils.GetExportFileNameWithExtension(filePath, format);
4950
ExportCollection(outputFilePath, dependencies, format);
5051

51-
await DependencyGraphGenerator.GenerateGraph(dependencies, Path.ChangeExtension(outputFilePath, ".dot"));
52+
DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);
53+
await DependencyGraphGenerator.CompileGraphAndWriteToFile(Path.ChangeExtension(outputFilePath, ".dot"), graph);
5254
}
5355

5456
public static void ExportMetrics(string filePath, List<NamespaceMetrics> metrics,

0 commit comments

Comments
 (0)