Skip to content

Commit a41ae19

Browse files
authored
Merge pull request #18 from magic5644/feat-dependencyCheck
Add dependency analysis and export functionality
2 parents 985cb7d + 0db91e7 commit a41ae19

14 files changed

+629
-22
lines changed

CodeLineCounter.Tests/CodeAnalyzerTests.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public void TestAnalyzeSolution()
1212
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..", "CodeLineCounter.sln"));
1313

1414
// Act
15-
var (metrics, projectTotals, totalLines, totalFiles, duplicationMap) = CodeMetricsAnalyzer.AnalyzeSolution(solutionPath);
15+
var (metrics, projectTotals, totalLines, totalFiles, duplicationMap, dependencies) = CodeMetricsAnalyzer.AnalyzeSolution(solutionPath);
1616

1717
// Assert
1818
Assert.NotNull(metrics);
@@ -21,6 +21,7 @@ public void TestAnalyzeSolution()
2121
Assert.NotEqual(0, totalLines);
2222
Assert.NotEqual(0, totalFiles);
2323
Assert.NotNull(duplicationMap);
24+
Assert.NotNull(dependencies);
2425
}
2526

2627
[Fact]

CodeLineCounter.Tests/DataExporterTests.cs

+45
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,51 @@ public void get_file_duplications_count_uses_current_dir_for_null_solution_path(
210210
Assert.Equal(4, result);
211211
}
212212

213+
// Successfully exports dependencies to specified format (CSV/JSON) and creates DOT file
214+
[Fact]
215+
public void export_dependencies_creates_files_in_correct_formats()
216+
{
217+
// Arrange
218+
var dependencies = new List<DependencyRelation>
219+
{
220+
new DependencyRelation { SourceClass = "ClassA", TargetClass = "ClassB", FilePath = "file1.cs", StartLine = 10 },
221+
};
222+
223+
var testFilePath = "test_export";
224+
var format = CoreUtils.ExportFormat.JSON;
225+
226+
// Act
227+
DataExporter.ExportDependencies(testFilePath, dependencies, format);
228+
229+
// Assert
230+
string expectedJsonPath = CoreUtils.GetExportFileNameWithExtension(testFilePath, format);
231+
string expectedDotPath = Path.ChangeExtension(expectedJsonPath, ".dot");
232+
233+
Assert.True(File.Exists(expectedJsonPath));
234+
Assert.True(File.Exists(expectedDotPath));
235+
236+
try
237+
{
238+
if (File.Exists(expectedJsonPath))
239+
{
240+
File.Delete(expectedJsonPath);
241+
}
242+
243+
if (File.Exists(expectedDotPath))
244+
{
245+
File.Delete(expectedDotPath);
246+
}
247+
}
248+
catch (IOException ex)
249+
{
250+
throw new IOException($"Error deleting files: {ex.Message}", ex);
251+
}
252+
catch (UnauthorizedAccessException ex)
253+
{
254+
throw new UnauthorizedAccessException($"Access denied while deleting files: {ex.Message}", ex);
255+
}
256+
}
257+
213258
protected virtual void Dispose(bool disposing)
214259
{
215260
if (!_disposed)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using CodeLineCounter.Utils;
2+
using CodeLineCounter.Models;
3+
using CodeLineCounter.Services;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using Xunit;
7+
8+
namespace CodeLineCounter.Tests
9+
{
10+
public class DependencyGraphGeneratorTests
11+
{
12+
[Fact]
13+
public void generate_graph_with_valid_dependencies_creates_dot_file()
14+
{
15+
// Arrange
16+
var dependencies = new List<DependencyRelation>
17+
{
18+
new DependencyRelation { SourceClass = "ClassA", TargetClass = "ClassB" , FilePath = "path/to/file", StartLine = 1},
19+
new DependencyRelation { SourceClass = "ClassB", TargetClass = "ClassC", FilePath = "path/to/file", StartLine = 1}
20+
};
21+
22+
string outputPath = Path.Combine(Path.GetTempPath(), "test_graph.dot");
23+
24+
// Act
25+
DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
26+
27+
// Assert
28+
Assert.True(File.Exists(outputPath));
29+
string content = File.ReadAllText(outputPath);
30+
Assert.Contains("ClassA", content);
31+
Assert.Contains("ClassB", content);
32+
Assert.Contains("ClassC", content);
33+
34+
File.Delete(outputPath);
35+
}
36+
37+
// Empty dependencies list
38+
[Fact]
39+
public void generate_graph_with_empty_dependencies_creates_empty_graph()
40+
{
41+
// Arrange
42+
var dependencies = new List<DependencyRelation>();
43+
string outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot");
44+
45+
// Act
46+
DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
47+
48+
// Assert
49+
Assert.True(File.Exists(outputPath));
50+
string content = File.ReadAllText(outputPath);
51+
Assert.Contains("digraph", content);
52+
Assert.DoesNotContain("->", content);
53+
54+
File.Delete(outputPath);
55+
}
56+
57+
58+
}
59+
}

CodeLineCounter.Tests/SolutionAnalyzerTests.cs

+41
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ public void PerformAnalysis_ShouldReturnCorrectAnalysisResult()
1414
var basePath = FileUtils.GetBasePath();
1515
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
1616
solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln");
17+
var sw = new StringWriter();
18+
Console.SetOut(sw);
19+
Console.WriteLine($"Constructed solution path: {solutionPath}");
20+
Assert.True(File.Exists(solutionPath), $"The solution file '{solutionPath}' does not exist.");
21+
Console.WriteLine($"Constructed solution path: {solutionPath}");
22+
Assert.True(File.Exists(solutionPath), $"The solution file '{solutionPath}' does not exist.");
1723

1824
// Act
1925
var result = SolutionAnalyzer.PerformAnalysis(solutionPath);
@@ -34,6 +40,7 @@ public void OutputAnalysisResults_ShouldPrintCorrectOutput()
3440
TotalLines = 1000,
3541
TotalFiles = 10,
3642
DuplicationMap = new List<DuplicationCode>(),
43+
DependencyList = new List<DependencyRelation>(),
3744
ProcessingTime = TimeSpan.FromSeconds(10),
3845
SolutionFileName = "CodeLineCounter.sln",
3946
DuplicatedLines = 100
@@ -109,5 +116,39 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
109116
}
110117
}
111118

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+
}
146+
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+
}
152+
112153
}
113154
}

CodeLineCounter/CodeLineCounter.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<TargetFramework>net9.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8+
<NoWarn>NU1701</NoWarn>
89
</PropertyGroup>
910

1011
<ItemGroup>
@@ -14,6 +15,9 @@
1415
</PackageReference>
1516
<PackageReference Include="CsvHelper" Version="33.0.1" />
1617
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
18+
<PackageReference Include="QuikGraph.Graphviz" Version="2.5.0" />
19+
<PackageReference Include="Graphviz.NET" Version="1.0.0" />
20+
1721
</ItemGroup>
1822

1923
</Project>

CodeLineCounter/Models/AnalysisResult.cs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class AnalysisResult
99
public int TotalLines { get; set; }
1010
public int TotalFiles { get; set; }
1111
public required List<DuplicationCode> DuplicationMap { get; set; }
12+
public required List<DependencyRelation> DependencyList { get; set; }
1213
public TimeSpan ProcessingTime { get; set; }
1314
public required string SolutionFileName { get; set; }
1415
public int DuplicatedLines { get; set; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using CsvHelper.Configuration.Attributes;
2+
namespace CodeLineCounter.Models
3+
{
4+
public class DependencyRelation
5+
{
6+
[Name("SourceClass")]
7+
public required string SourceClass { get; set; }
8+
9+
[Name("TargetClass")]
10+
public required string TargetClass { get; set; }
11+
12+
[Name("FilePath")]
13+
public required string FilePath { get; set; }
14+
15+
[Name("StartLine")]
16+
public int StartLine { get; set; }
17+
18+
public override bool Equals(object? obj)
19+
{
20+
if (obj is not DependencyRelation other)
21+
return false;
22+
23+
return SourceClass == other.SourceClass &&
24+
TargetClass == other.TargetClass &&
25+
FilePath == other.FilePath &&
26+
StartLine == other.StartLine;
27+
}
28+
29+
public override int GetHashCode()
30+
{
31+
return HashCode.Combine(SourceClass, TargetClass, FilePath, StartLine);
32+
}
33+
}
34+
}

CodeLineCounter/Services/CodeMetricsAnalyzer.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,30 @@ namespace CodeLineCounter.Services
77
{
88
public static class CodeMetricsAnalyzer
99
{
10-
public static (List<NamespaceMetrics>, Dictionary<string, int>, int, int, List<DuplicationCode>) AnalyzeSolution(string solutionFilePath)
10+
public static (List<NamespaceMetrics>, Dictionary<string, int>, int, int, List<DuplicationCode>, List<DependencyRelation>) AnalyzeSolution(string solutionFilePath)
1111
{
1212
string solutionDirectory = Path.GetDirectoryName(solutionFilePath) ?? string.Empty;
1313
var projectFiles = FileUtils.GetProjectFiles(solutionFilePath);
1414

1515
var namespaceMetrics = new List<NamespaceMetrics>();
1616
var projectTotals = new Dictionary<string, int>();
1717
var codeDuplicationChecker = new CodeDuplicationChecker();
18+
1819
int totalLines = 0;
1920
int totalFilesAnalyzed = 0;
2021

2122
Parallel.Invoke(
2223
() => AnalyzeAllProjects(solutionDirectory, projectFiles, namespaceMetrics, projectTotals, ref totalLines, ref totalFilesAnalyzed),
23-
() => codeDuplicationChecker.DetectCodeDuplicationInFiles(FileUtils.GetAllCsFiles(solutionDirectory))
24+
() => codeDuplicationChecker.DetectCodeDuplicationInFiles(FileUtils.GetAllCsFiles(solutionDirectory)),
25+
() => DependencyAnalyzer.AnalyzeSolution(solutionFilePath)
2426
);
2527

2628
var duplicationMap = codeDuplicationChecker.GetCodeDuplicationMap();
2729
var duplicationList = duplicationMap.Values.SelectMany(v => v).ToList();
30+
var dependencyList = DependencyAnalyzer.GetDependencies();
31+
DependencyAnalyzer.Clear();
2832

29-
return (namespaceMetrics, projectTotals, totalLines, totalFilesAnalyzed, duplicationList);
33+
return (namespaceMetrics, projectTotals, totalLines, totalFilesAnalyzed, duplicationList, dependencyList);
3034
}
3135

3236
private static void AnalyzeAllProjects(string solutionDirectory, List<string> projectFiles, List<NamespaceMetrics> namespaceMetrics, Dictionary<string, int> projectTotals, ref int totalLines, ref int totalFilesAnalyzed)

0 commit comments

Comments
 (0)