Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CodeLineCounter.Tests/CodeAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public void TestAnalyzeSolution()
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..", "CodeLineCounter.sln"));

// Act
var (metrics, projectTotals, totalLines, totalFiles, duplicationMap) = CodeMetricsAnalyzer.AnalyzeSolution(solutionPath);
var (metrics, projectTotals, totalLines, totalFiles, duplicationMap, dependencies) = CodeMetricsAnalyzer.AnalyzeSolution(solutionPath);

// Assert
Assert.NotNull(metrics);
Expand All @@ -21,6 +21,7 @@ public void TestAnalyzeSolution()
Assert.NotEqual(0, totalLines);
Assert.NotEqual(0, totalFiles);
Assert.NotNull(duplicationMap);
Assert.NotNull(dependencies);
}

[Fact]
Expand Down
45 changes: 45 additions & 0 deletions CodeLineCounter.Tests/DataExporterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,51 @@ public void get_file_duplications_count_uses_current_dir_for_null_solution_path(
Assert.Equal(4, result);
}

// Successfully exports dependencies to specified format (CSV/JSON) and creates DOT file
[Fact]
public void export_dependencies_creates_files_in_correct_formats()
{
// Arrange
var dependencies = new List<DependencyRelation>
{
new DependencyRelation { SourceClass = "ClassA", TargetClass = "ClassB", FilePath = "file1.cs", StartLine = 10 },
};

var testFilePath = "test_export";
var format = CoreUtils.ExportFormat.JSON;

// Act
DataExporter.ExportDependencies(testFilePath, dependencies, format);

// Assert
string expectedJsonPath = CoreUtils.GetExportFileNameWithExtension(testFilePath, format);
string expectedDotPath = Path.ChangeExtension(expectedJsonPath, ".dot");

Assert.True(File.Exists(expectedJsonPath));
Assert.True(File.Exists(expectedDotPath));

try
{
if (File.Exists(expectedJsonPath))
{
File.Delete(expectedJsonPath);
}

if (File.Exists(expectedDotPath))
{
File.Delete(expectedDotPath);
}
}
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);
}
}

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
Expand Down
59 changes: 59 additions & 0 deletions CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using CodeLineCounter.Utils;
using CodeLineCounter.Models;
using CodeLineCounter.Services;
using System.Collections.Generic;
using System.IO;
using Xunit;

namespace CodeLineCounter.Tests
{
public class DependencyGraphGeneratorTests
{
[Fact]
public void generate_graph_with_valid_dependencies_creates_dot_file()
{
// Arrange
var dependencies = new List<DependencyRelation>
{
new DependencyRelation { SourceClass = "ClassA", TargetClass = "ClassB" , FilePath = "path/to/file", StartLine = 1},
new DependencyRelation { SourceClass = "ClassB", TargetClass = "ClassC", FilePath = "path/to/file", StartLine = 1}
};

string outputPath = Path.Combine(Path.GetTempPath(), "test_graph.dot");

// Act
DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);

// Assert
Assert.True(File.Exists(outputPath));
string content = File.ReadAllText(outputPath);
Assert.Contains("ClassA", content);
Assert.Contains("ClassB", content);
Assert.Contains("ClassC", content);

File.Delete(outputPath);
}

// Empty dependencies list
[Fact]
public void generate_graph_with_empty_dependencies_creates_empty_graph()
{
// Arrange
var dependencies = new List<DependencyRelation>();
string outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot");

// Act
DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);

// Assert
Assert.True(File.Exists(outputPath));
string content = File.ReadAllText(outputPath);
Assert.Contains("digraph", content);
Assert.DoesNotContain("->", content);

File.Delete(outputPath);
}


}
}
41 changes: 41 additions & 0 deletions CodeLineCounter.Tests/SolutionAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ public void PerformAnalysis_ShouldReturnCorrectAnalysisResult()
var basePath = FileUtils.GetBasePath();
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln");
var sw = new StringWriter();
Console.SetOut(sw);
Console.WriteLine($"Constructed solution path: {solutionPath}");
Assert.True(File.Exists(solutionPath), $"The solution file '{solutionPath}' does not exist.");
Console.WriteLine($"Constructed solution path: {solutionPath}");
Assert.True(File.Exists(solutionPath), $"The solution file '{solutionPath}' does not exist.");

// Act
var result = SolutionAnalyzer.PerformAnalysis(solutionPath);
Expand All @@ -34,6 +40,7 @@ public void OutputAnalysisResults_ShouldPrintCorrectOutput()
TotalLines = 1000,
TotalFiles = 10,
DuplicationMap = new List<DuplicationCode>(),
DependencyList = new List<DependencyRelation>(),
ProcessingTime = TimeSpan.FromSeconds(10),
SolutionFileName = "CodeLineCounter.sln",
DuplicatedLines = 100
Expand Down Expand Up @@ -109,5 +116,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<NamespaceMetrics>(),
ProjectTotals = new Dictionary<string, int >(),
TotalLines = 1000,
DuplicationMap = new List<DuplicationCode>(),
DependencyList = new List<DependencyRelation>()
};

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);
}

// Assert
Assert.True(File.Exists("TestSolution-CodeMetrics.csv"));
Assert.True(File.Exists("TestSolution-CodeDuplications.csv"));
Assert.True(File.Exists("TestSolution-CodeDependencies.csv"));
}

}
}
4 changes: 4 additions & 0 deletions CodeLineCounter/CodeLineCounter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand All @@ -14,6 +15,9 @@
</PackageReference>
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageReference Include="QuikGraph.Graphviz" Version="2.5.0" />
<PackageReference Include="Graphviz.NET" Version="1.0.0" />

</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions CodeLineCounter/Models/AnalysisResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class AnalysisResult
public int TotalLines { get; set; }
public int TotalFiles { get; set; }
public required List<DuplicationCode> DuplicationMap { get; set; }
public required List<DependencyRelation> DependencyList { get; set; }
public TimeSpan ProcessingTime { get; set; }
public required string SolutionFileName { get; set; }
public int DuplicatedLines { get; set; }
Expand Down
34 changes: 34 additions & 0 deletions CodeLineCounter/Models/DependencyRelation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using CsvHelper.Configuration.Attributes;
namespace CodeLineCounter.Models
{
public class DependencyRelation
{
[Name("SourceClass")]
public required string SourceClass { get; set; }

[Name("TargetClass")]
public required string TargetClass { get; set; }

[Name("FilePath")]
public required string FilePath { get; set; }

[Name("StartLine")]
public int StartLine { get; set; }

public override bool Equals(object? obj)
{
if (obj is not DependencyRelation other)
return false;

return SourceClass == other.SourceClass &&
TargetClass == other.TargetClass &&
FilePath == other.FilePath &&
StartLine == other.StartLine;
}

public override int GetHashCode()
{
return HashCode.Combine(SourceClass, TargetClass, FilePath, StartLine);
}
}
}
10 changes: 7 additions & 3 deletions CodeLineCounter/Services/CodeMetricsAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,30 @@ namespace CodeLineCounter.Services
{
public static class CodeMetricsAnalyzer
{
public static (List<NamespaceMetrics>, Dictionary<string, int>, int, int, List<DuplicationCode>) AnalyzeSolution(string solutionFilePath)
public static (List<NamespaceMetrics>, Dictionary<string, int>, int, int, List<DuplicationCode>, List<DependencyRelation>) AnalyzeSolution(string solutionFilePath)
{
string solutionDirectory = Path.GetDirectoryName(solutionFilePath) ?? string.Empty;
var projectFiles = FileUtils.GetProjectFiles(solutionFilePath);

var namespaceMetrics = new List<NamespaceMetrics>();
var projectTotals = new Dictionary<string, int>();
var codeDuplicationChecker = new CodeDuplicationChecker();

int totalLines = 0;
int totalFilesAnalyzed = 0;

Parallel.Invoke(
() => AnalyzeAllProjects(solutionDirectory, projectFiles, namespaceMetrics, projectTotals, ref totalLines, ref totalFilesAnalyzed),
() => codeDuplicationChecker.DetectCodeDuplicationInFiles(FileUtils.GetAllCsFiles(solutionDirectory))
() => codeDuplicationChecker.DetectCodeDuplicationInFiles(FileUtils.GetAllCsFiles(solutionDirectory)),
() => DependencyAnalyzer.AnalyzeSolution(solutionFilePath)
);

var duplicationMap = codeDuplicationChecker.GetCodeDuplicationMap();
var duplicationList = duplicationMap.Values.SelectMany(v => v).ToList();
var dependencyList = DependencyAnalyzer.GetDependencies();
DependencyAnalyzer.Clear();

return (namespaceMetrics, projectTotals, totalLines, totalFilesAnalyzed, duplicationList);
return (namespaceMetrics, projectTotals, totalLines, totalFilesAnalyzed, duplicationList, dependencyList);
}

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