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
25 changes: 25 additions & 0 deletions CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph()

}

[Fact]
public async Task CompileGraphAndWriteToFile_HandlesInvalidOutputPathGracefully()
{
// Arrange
var dependencies = new List<DependencyRelation>
{
new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA",
SourceAssembly = "AssemblyA", TargetClass = "ClassB", TargetNamespace = "NamespaceB",
TargetAssembly = "AssemblyB", FilePath = "path/to/file", StartLine = 1 }
};
DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);

// Invalid path with characters that aren't allowed in file paths
string invalidOutputPath = Path.Combine(_testDirectory, "invalid_path*?:|<>\\");
string fileName = "test.dot";

// Act & Assert
// The method should handle the exception internally and not throw it to the caller
await DependencyGraphGenerator.CompileGraphAndWriteToFile(fileName, invalidOutputPath, graph);

// Verify the file wasn't created due to the invalid path
Assert.False(File.Exists(Path.Combine(invalidOutputPath, fileName)));
}


[Fact]
public void create_node_sets_correct_fillcolor_and_style_incoming_greater()
{
Expand Down
18 changes: 6 additions & 12 deletions CodeLineCounter.Tests/FileUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void get_solution_files_throws_exception_for_nonexistent_directory()
var nonExistentPath = Path.Combine(_testDirectory, Guid.NewGuid().ToString());

// Act & Assert
Assert.Throws<UnauthorizedAccessException>(() => FileUtils.GetSolutionFiles(nonExistentPath));
Assert.Throws<DirectoryNotFoundException>(() => FileUtils.GetSolutionFiles(nonExistentPath));
}

// Throws UnauthorizedAccessException when solution file does not exist
Expand All @@ -59,29 +59,23 @@ public void get_project_files_throws_when_file_not_exists()
var nonExistentPath = Path.Combine(_testDirectory, "nonexistent.sln");

// Act & Assert
var exception = Assert.Throws<UnauthorizedAccessException>(() =>
var exception = Assert.Throws<FileNotFoundException>(() =>
FileUtils.GetProjectFiles(nonExistentPath));

Assert.Contains(nonExistentPath, exception.Message);
}

protected override void Dispose(bool disposing)
{

// Ensure the file is closed before attempting to delete it
if (disposing)
if (disposing && Directory.Exists(_testDirectory))
{
if (Directory.Exists(_testDirectory))
{
// Dispose managed resources
Directory.Delete(_testDirectory, true);
}
// Dispose managed resources
Directory.Delete(_testDirectory, true);
}

// Dispose unmanaged resources (if any)
base.Dispose(disposing);

}

}
}
}
10 changes: 3 additions & 7 deletions CodeLineCounter.Tests/JsonHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,14 @@ public void deserialize_valid_json_file_returns_expected_objects()

protected override void Dispose(bool disposing)
{
if (disposing)
if (disposing && Directory.Exists(_testDirectory))
{
if (Directory.Exists(_testDirectory))
{
// Dispose managed resources
Directory.Delete(_testDirectory, true);
}
// Dispose managed resources
Directory.Delete(_testDirectory, true);
}

// Dispose unmanaged resources (if any)
base.Dispose(disposing);

}


Expand Down
22 changes: 8 additions & 14 deletions CodeLineCounter.Tests/SolutionAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ public void analyze_and_export_solution_succeeds_with_valid_inputs()
var format = CoreUtils.ExportFormat.CSV;
var outputPath = _outputPath;



try
{

Expand Down Expand Up @@ -80,10 +78,10 @@ public void analyze_and_export_solution_throws_on_invalid_path()
var format = CoreUtils.ExportFormat.JSON;

// Act & Assert
var exception = Assert.Throws<UnauthorizedAccessException>(() =>
var exception = Assert.Throws<FileNotFoundException>(() =>
SolutionAnalyzer.AnalyzeAndExportSolution(invalidPath, verbose, format));

Assert.Contains("Access to the path '' is denied.", exception.Message);
Assert.Contains("File '' not found.", exception.Message);

}

Expand Down Expand Up @@ -147,8 +145,7 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
// Arrange
var metrics = new List<NamespaceMetrics>
{
new NamespaceMetrics
{
new() {
ProjectName = "Project1",
ProjectPath = "/path/to/project1",
NamespaceName = "Namespace1",
Expand All @@ -157,7 +154,7 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
LineCount = 100,
CyclomaticComplexity = 10
},
new NamespaceMetrics
new ()
{
ProjectName = "Project2",
ProjectPath = "/path/to/project2",
Expand Down Expand Up @@ -233,14 +230,11 @@ public void export_results_with_valid_input_exports_all_files()
protected override void Dispose(bool disposing)
{

if (disposing)
if (disposing && Directory.Exists(_testDirectory))
{
if (Directory.Exists(_testDirectory))
{
// Dispose managed resources
File.Delete(_testSolutionPath);
Directory.Delete(_testDirectory, true);
}
// Dispose managed resources
File.Delete(_testSolutionPath);
Directory.Delete(_testDirectory, true);
}

// Dispose unmanaged resources (if any)
Expand Down
39 changes: 27 additions & 12 deletions CodeLineCounter/Services/DependencyGraphGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,21 +163,36 @@ public static DotNode CreateNode(Dictionary<string, (int incoming, int outgoing)

public static async Task CompileGraphAndWriteToFile(string fileName, string outputPath, DotGraph graph)
{
// Use memory buffer
using var memoryStream = MemoryStreamManager.GetStream();
using var writer = new StreamWriter(memoryStream);

var options = new CompilationOptions { Indented = true };
var context = new CompilationContext(writer, options);
graph.Directed = true;
context.DirectedGraph = true;
// Use memory buffer
using var memoryStream = MemoryStreamManager.GetStream();
using var writer = new StreamWriter(memoryStream);

var options = new CompilationOptions { Indented = true };
var context = new CompilationContext(writer, options);
graph.Directed = true;
context.DirectedGraph = true;

await graph.CompileAsync(context);
await writer.FlushAsync(); // Ensure all data is written to memory

await graph.CompileAsync(context);
await writer.FlushAsync(); // Ensure all data is written to memory
memoryStream.Position = 0; // Reset position to start
await WriteMemoryStreamToFile(fileName, outputPath, memoryStream);

}

memoryStream.Position = 0; // Reset position to start
using var fileStream = File.Create(Path.Combine(outputPath, fileName));
await memoryStream.CopyToAsync(fileStream); // Write complete buffer to file
private static async Task WriteMemoryStreamToFile(string fileName, string outputPath, RecyclableMemoryStream memoryStream)
{
try
{
using var fileStream = File.Create(Path.Combine(outputPath, fileName));
await memoryStream.CopyToAsync(fileStream); // Write complete buffer to file
}
catch (Exception ex)
{
// Log or handle the exception as needed
await Console.Error.WriteLineAsync($"Error writing memory stream to file: {ex.Message}");
}
}

public static string EncloseNotEmptyOrNullStringInQuotes(string? str)
Expand Down
39 changes: 26 additions & 13 deletions CodeLineCounter/Utils/FileUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,46 @@ namespace CodeLineCounter.Utils
{
public static partial class FileUtils
{
public static List<string> GetAllCsFiles(string rootPath)
// Default file extensions
private const string DEFAULT_CODE_EXTENSION = "*.cs";
private const string DEFAULT_SOLUTION_EXTENSION = "*.sln";
private const string DEFAULT_PROJECT_EXTENSION = ".csproj";

public static List<string> GetAllCodeFiles(string rootPath, string fileExtension = DEFAULT_CODE_EXTENSION)
{
return Directory.GetFiles(rootPath, "*.cs", SearchOption.AllDirectories)
.Where(f => !f.Contains(@"\obj\"))
.ToList();
var excludeFolders = new[] { @"\obj\", @"\bin\", @"\.*" };
return Directory.GetFiles(rootPath, fileExtension, SearchOption.AllDirectories)
.Where(f => !excludeFolders.Any(ef => f.Contains(ef)))
.ToList();
}

public static List<string> GetSolutionFiles(string rootPath)
// For backward compatibility
public static List<string> GetAllCsFiles(string rootPath) => GetAllCodeFiles(rootPath);

public static List<string> GetSolutionFiles(string rootPath, string fileExtension = DEFAULT_SOLUTION_EXTENSION)
{
if (!Directory.Exists(rootPath))
{
throw new UnauthorizedAccessException($"Access to the path '{rootPath}' is denied.");
throw new DirectoryNotFoundException($"Directory '{rootPath}' not found.");
}

return Directory.GetFiles(rootPath, "*.sln", SearchOption.TopDirectoryOnly).ToList();
return Directory.GetFiles(rootPath, fileExtension, SearchOption.TopDirectoryOnly).ToList();
}

public static List<string> GetProjectFiles(string solutionFilePath)
public static List<string> GetProjectFiles(string solutionFilePath, string projectExtension = DEFAULT_PROJECT_EXTENSION)
{
if (!File.Exists(solutionFilePath))
{
throw new UnauthorizedAccessException($"Access to the path '{solutionFilePath}' is denied.");
throw new FileNotFoundException($"File '{solutionFilePath}' not found.");
}

var projectFiles = new List<string>();
var lines = File.ReadAllLines(solutionFilePath);

foreach (var line in lines)
{
// Search for lines containing projects (Project("...") = "...", "...", "...")
var match = MyRegex().Match(line);
// Search for lines containing projects with the specified extension
var match = GenerateProjectRegex(projectExtension).Match(line);
if (match.Success)
{
var relativePath = match.Groups[1].Value;
Expand All @@ -48,11 +57,15 @@ public static List<string> GetProjectFiles(string solutionFilePath)

public static string GetBasePath()
{
// Arrange
return Path.GetDirectoryName(AppContext.BaseDirectory) ?? string.Empty;
}

private static Regex GenerateProjectRegex(string projectExtension)
{
return new Regex($@"Project\(""{{.*}}""\) = "".*"", ""(.*{Regex.Escape(projectExtension)})""", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(100));
}

[GeneratedRegex(@"Project\(""{.*}""\) = "".*"", ""(.*\.csproj)""")]
private static partial Regex MyRegex();
}
}
}
Loading