diff --git a/.gitignore b/.gitignore
index 217b24f..44248d8 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
.vs
.vscode
+.idea
bin
obj
.DS_Store
app
+*.DotSettings.user
diff --git a/Dockerfile b/Dockerfile
index 61bc883..7093663 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS sdk
+FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS sdk
RUN mkdir -p /src/Stahz
WORKDIR /src
COPY Strazh/Strazh.csproj Strazh/Strazh.csproj
diff --git a/Strazh.Tests/AnalyzerTests.cs b/Strazh.Tests/AnalyzerTests.cs
new file mode 100644
index 0000000..39244f5
--- /dev/null
+++ b/Strazh.Tests/AnalyzerTests.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Buildalyzer;
+using Strazh.Analysis;
+using Xunit;
+
+namespace Strazh.Tests;
+
+public class AnalyzerTests
+{
+ ///
+ /// Regression test for the bug where projects pulled into the Roslyn workspace as transitive
+ /// references by an earlier project's AddToWorkspace(workspace, addProjectReferences: true)
+ /// call were subsequently skipped by the deduplication check and never entered
+ /// context.Projects — causing them to receive no CONTAINS triple and no analysis.
+ ///
+ /// The fixture solution (SystemUnderTest.sln) lists ProjectA before ProjectB.
+ /// ProjectA references ProjectB, so when ProjectA is added to the workspace with
+ /// addProjectReferences: true, Buildalyzer pulls ProjectB in as a reference. Without the
+ /// fix, ProjectB's loop iteration finds it already in the workspace and is dropped.
+ ///
+ [Fact]
+ public async Task GetAnalysisContext_IncludesProjectsAddedAsTransitiveReferences()
+ {
+ var solutionPath = Path.Combine(GetRepoRoot(), "SystemUnderTest", "SystemUnderTest.sln");
+ var manager = new AnalyzerManager(solutionPath);
+
+ var context = await Analyzer.GetAnalysisContext(manager);
+
+ var projectFileNames = context.Projects
+ .Select(p => Path.GetFileName(p.Item2.ProjectFilePath))
+ .ToList();
+
+ Assert.Contains("Strazh.Tests.ProjectA.csproj", projectFileNames);
+ Assert.Contains("Strazh.Tests.ProjectB.csproj", projectFileNames);
+ }
+
+ // [CallerFilePath] gives the compile-time absolute path of this source file.
+ // From Strazh.Tests/AnalyzerTests.cs, two levels up reaches the repo root.
+ private static string GetRepoRoot([CallerFilePath] string callerFile = "") =>
+ Path.GetFullPath(Path.Combine(callerFile, "../.."));
+}
diff --git a/Strazh.Tests/Strazh.Tests.csproj b/Strazh.Tests/Strazh.Tests.csproj
new file mode 100644
index 0000000..d0a460d
--- /dev/null
+++ b/Strazh.Tests/Strazh.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net10.0
+ false
+ enable
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/Strazh/Analysis/Analyzer.cs b/Strazh/Analysis/Analyzer.cs
index fc13743..983b0a0 100755
--- a/Strazh/Analysis/Analyzer.cs
+++ b/Strazh/Analysis/Analyzer.cs
@@ -1,3 +1,4 @@
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
@@ -7,11 +8,9 @@
using Buildalyzer.Workspaces;
using System.Collections.Generic;
using System;
-using System.Collections.Concurrent;
using Strazh.Database;
using static Strazh.Analysis.AnalyzerConfig;
using System.IO;
-using Microsoft.Build.Construction;
namespace Strazh.Analysis
{
@@ -30,71 +29,99 @@ public static async Task Analyze(AnalyzerConfig config)
: config.Projects.Select(x => manager.GetProject(x))).ToList();
Console.WriteLine($"Analyzer ready to analyze {projectAnalyzers.Count} project/s.");
-
- Console.WriteLine("Building workspace...");
- var context = GetAnalysisContext(manager);
- Console.WriteLine("done.");
-
- Console.WriteLine("Analyzing workspace...");
-
- for (var index = 0; index < context.Projects.Count; index++)
+
+ // Delete graph data upfront before parallel analysis begins, so that no project
+ // races against the delete.
+ if (config.IsDelete)
{
- var triples = new List();
+ await DbManager.DeleteData(config.Credentials);
+ }
- if (config.IsSolutionBased)
- {
- var solutionRoot = GetRoot(manager.SolutionFilePath);
- var solutionRootNode = new FolderNode(solutionRoot, solutionRoot);
-
- var solutionName = GetSolutionName(manager.SolutionFilePath);
- var solutionNode = new SolutionNode(solutionName);
- triples.Add(new TripleIncludedIn(solutionNode, solutionRootNode));
-
- var projectNode = new ProjectNode(GetProjectName(context.Projects[index].Item1.Name));
- triples.Add(new TripleContains(solutionNode, projectNode));
- }
+ var workspace = CreateWorkspace(manager);
- Console.WriteLine($"+ [{index + 1}/{context.Projects.Count} {context.Projects[index].Item1.Name}: analyze - starting");
- var projectTriples = await AnalyzeProject(index + 1, context.Projects[index], config.Tier);
- Console.WriteLine($"+ [{index + 1}/{context.Projects.Count} {context.Projects[index].Item1.Name}: analyze - finished");
-
- triples.AddRange(projectTriples);
-
- Console.WriteLine($"+ [{index + 1}/{context.Projects.Count} {context.Projects[index].Item1.Name}: grouping - starting");
- try
- {
- triples = triples.GroupBy(x => x.ToString()).Select(x => x.First()).OrderBy(x => x.NodeA.Label)
- .ToList();
- }
- catch (Exception error)
+ // Limit concurrent Neo4j connections to one per logical processor.
+ var semaphore = new SemaphoreSlim(Environment.ProcessorCount);
+ var total = projectAnalyzers.Count;
+ var tasks = new List();
+ var index = 0;
+
+ // Stream projects through the Build → Load pipeline and launch an Analyze + Insert
+ // task for each one as soon as it becomes available, without waiting for all
+ // projects to finish building and loading first.
+ await foreach (var entry in StreamProjectsAsync(manager, workspace))
+ {
+ var capturedEntry = entry;
+ var capturedIndex = index++;
+
+ tasks.Add(Task.Run(async () =>
{
- Console.WriteLine("Error detected. Dumping detailed logging data.");
- Console.WriteLine("[");
- var first = true;
- foreach (var triple in triples)
+ var triples = new List();
+
+ if (config.IsSolutionBased)
{
- if (!first)
+ var solutionRoot = GetRoot(manager.SolutionFilePath);
+ var solutionRootNode = new FolderNode(solutionRoot, solutionRoot);
+
+ var solutionName = GetSolutionName(manager.SolutionFilePath);
+ var solutionNode = new SolutionNode(solutionName);
+ triples.Add(new TripleIncludedIn(solutionNode, solutionRootNode));
+
+ var projectNode = new ProjectNode(GetProjectName(capturedEntry.Item1.Name));
+ triples.Add(new TripleContains(solutionNode, projectNode));
+ }
+
+ Console.WriteLine($"+ [{capturedIndex + 1}/{total}] {capturedEntry.Item1.Name}: analyze - starting");
+ var projectTriples = await AnalyzeProject(capturedIndex + 1, capturedEntry, config.Tier);
+ Console.WriteLine($"+ [{capturedIndex + 1}/{total}] {capturedEntry.Item1.Name}: analyze - finished");
+
+ triples.AddRange(projectTriples);
+
+ Console.WriteLine($"+ [{capturedIndex + 1}/{total}] {capturedEntry.Item1.Name}: grouping - starting");
+ try
+ {
+ triples = triples.GroupBy(x => x.ToString()).Select(x => x.First()).OrderBy(x => x.NodeA.Label)
+ .ToList();
+ }
+ catch (Exception)
+ {
+ Console.WriteLine("Error detected. Dumping detailed logging data.");
+ Console.WriteLine("[");
+ var first = true;
+ foreach (var triple in triples)
{
- Console.WriteLine(",");
+ if (!first)
+ {
+ Console.WriteLine(",");
+ }
+ Console.Write($$"""{ "triple": {{ triple.ToInspection()}} }""");
+
+ first = false;
+ }
+ if (triples.Any())
+ {
+ Console.WriteLine("");
}
- Console.Write($$"""{ "triple": {{ triple.ToInspection()}} }""");
+ Console.WriteLine("]");
+ throw;
+ }
+ Console.WriteLine($"+ [{capturedIndex + 1}/{total}] {capturedEntry.Item1.Name}: grouping - finished");
- first = false;
+ Console.WriteLine($"+ [{capturedIndex + 1}/{total}] {capturedEntry.Item1.Name}: insert - starting");
+ await semaphore.WaitAsync();
+ try
+ {
+ await DbManager.InsertData(triples, config.Credentials);
}
- if (triples.Any())
+ finally
{
- Console.WriteLine("");
+ semaphore.Release();
}
- Console.WriteLine("]");
- throw;
- }
- Console.WriteLine($"+ [{index + 1}/{context.Projects.Count} {context.Projects[index].Item1.Name}: grouping - finished");
-
- Console.WriteLine($"+ [{index + 1}/{context.Projects.Count} {context.Projects[index].Item1.Name}: inserting - starting");
- await DbManager.InsertData(triples, config.Credentials, config.IsDelete && index == 0);
- Console.WriteLine($"+ [{index + 1}/{context.Projects.Count} {context.Projects[index].Item1.Name}: inserting - finished");
+ Console.WriteLine($"+ [{capturedIndex + 1}/{total}] {capturedEntry.Item1.Name}: insert - finished");
+ }));
}
- context.Workspace.Dispose();
+
+ await Task.WhenAll(tasks);
+ workspace.Dispose();
}
public class AnalysisContext(AdhocWorkspace workspace, List<(Project, IAnalyzerResult)> projects)
@@ -104,53 +131,96 @@ public class AnalysisContext(AdhocWorkspace workspace, List<(Project, IAnalyzerR
}
// Based on https://github.com/phmonte/Buildalyzer/blob/9db3390b49dca033fd3f70439bab3a6327440a47/src/Buildalyzer.Workspaces/AnalyzerManagerExtensions.cs#L24-L60
- public static AnalysisContext GetAnalysisContext(IAnalyzerManager manager)
+ public static async Task GetAnalysisContext(IAnalyzerManager manager)
{
if (manager is null)
{
throw new ArgumentNullException(nameof(manager));
}
-
- var projectResults = new ConcurrentBag<(Project, IAnalyzerResult)>();
- Console.WriteLine("Building projects - starting");
-
- List results = manager.Projects.Values
- .Select(p =>
- {
- Console.WriteLine($"Building projects - {p.ProjectFile.Name} - starting");
- var result = p.Build().FirstOrDefault();
- Console.WriteLine($"Building projects - {p.ProjectFile.Name} - finished");
- return result;
- })
- .Where(x => x != null)
- .ToList();
- Console.WriteLine("Building projects - finished.");
-
- // Create a new workspace and add the solution (if there was one)
- AdhocWorkspace workspace = new AdhocWorkspace();
- if (!string.IsNullOrEmpty(manager.SolutionFilePath))
+ var workspace = CreateWorkspace(manager);
+ var projects = new List<(Project, IAnalyzerResult)>();
+ await foreach (var item in StreamProjectsAsync(manager, workspace))
{
- SolutionInfo solutionInfo = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Default, manager.SolutionFilePath);
- workspace.AddSolution(solutionInfo);
-
- // Sort the projects so the order that they're added to the workspace in the same order as the solution file
- List projectsInOrder = [.. manager.SolutionFile.ProjectsInOrder];
- results = [.. results.OrderBy(p => projectsInOrder.FindIndex(g => g.AbsolutePath == p.ProjectFilePath))];
+ projects.Add(item);
}
- // Add each result to the new workspace (sorted in solution order above, if we have a solution)
- foreach (IAnalyzerResult result in results)
+ return new AnalysisContext(workspace, projects);
+ }
+
+ // Runs all MSBuild design-time builds in parallel (Build stage), waits for all to
+ // complete, then feeds results sequentially into the Roslyn AdhocWorkspace (Load stage),
+ // yielding each (Project, IAnalyzerResult) pair immediately so the Analyze and Insert
+ // stages can begin on each project without waiting for all projects to finish loading.
+ //
+ // Build and Load cannot be interleaved: AddToWorkspace(addProjectReferences: true) calls
+ // analyzer.Build() internally for any referenced project not yet in the workspace. If
+ // other builds are still in-flight when this happens, two concurrent builds of the same
+ // project race and Buildalyzer's environment detection fails. Waiting for all builds to
+ // complete first avoids the race while still streaming Load → Analyze.
+ //
+ // AdhocWorkspace is not thread-safe, so workspace mutations remain sequential.
+ private static async IAsyncEnumerable<(Project, IAnalyzerResult)> StreamProjectsAsync(
+ IAnalyzerManager manager, AdhocWorkspace workspace)
+ {
+ // Build stage: run MSBuild design-time builds in parallel, capped at the number
+ // of logical processors so we don't spawn an unbounded number of dotnet processes
+ var buildSemaphore = new SemaphoreSlim(Environment.ProcessorCount);
+ IAnalyzerResult?[] results = await Task.WhenAll(
+ manager.Projects.Values.Select(p => Task.Run(async () =>
+ {
+ await buildSemaphore.WaitAsync();
+ try
+ {
+ Console.WriteLine($"Build - {p.ProjectFile.Name} - starting");
+ var result = p.Build().FirstOrDefault();
+ Console.WriteLine($"Build - {p.ProjectFile.Name} - finished");
+ return result;
+ }
+ finally
+ {
+ buildSemaphore.Release();
+ }
+ })));
+
+ // Load stage: add each completed result to the workspace and yield immediately,
+ // so analysis can begin on each project without waiting for all to be loaded
+ foreach (var result in results)
{
- // Check for duplicate project files and don't add them
- if (workspace.CurrentSolution.Projects.All(p => p.FilePath != result.ProjectFilePath))
+ if (result is null) continue;
+
+ Console.WriteLine($"Load - {Path.GetFileName(result.ProjectFilePath)} - starting");
+ var existingProject = workspace.CurrentSolution.Projects
+ .FirstOrDefault(p => p.FilePath == result.ProjectFilePath);
+ if (existingProject is null)
{
+ // AddToWorkspace with addProjectReferences: true eagerly adds referenced projects
+ // into the workspace. Those will be picked up via existingProject on their own
+ // iteration below.
var project = result.AddToWorkspace(workspace, true);
- projectResults.Add((project, result));
+ Console.WriteLine($"Load - {Path.GetFileName(result.ProjectFilePath)} - finished");
+ yield return (project, result);
+ }
+ else
+ {
+ // Already in the workspace because an earlier project pulled it in as a
+ // transitive reference. Still include it so it gets CONTAINS triples and
+ // is analyzed — just reuse the workspace Project object already there.
+ Console.WriteLine($"Load - {Path.GetFileName(result.ProjectFilePath)} - finished");
+ yield return (existingProject, result);
}
}
+ }
- return new AnalysisContext(workspace, projectResults.ToList());
+ private static AdhocWorkspace CreateWorkspace(IAnalyzerManager manager)
+ {
+ var workspace = new AdhocWorkspace();
+ if (!string.IsNullOrEmpty(manager.SolutionFilePath))
+ {
+ SolutionInfo solutionInfo = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Default, manager.SolutionFilePath);
+ workspace.AddSolution(solutionInfo);
+ }
+ return workspace;
}
private static async Task> AnalyzeProject(int index, (Project project, IAnalyzerResult projectAnalyzerResult) item, Tiers mode)
diff --git a/Strazh/Database/DbManager.cs b/Strazh/Database/DbManager.cs
index 04df7af..5170b31 100644
--- a/Strazh/Database/DbManager.cs
+++ b/Strazh/Database/DbManager.cs
@@ -11,23 +11,30 @@ public static class DbManager
{
private const string CONNECTION = "neo4j://localhost:7687";
- public static async Task InsertData(IList triples, CredentialsConfig credentials, bool isDelete)
+ public static async Task DeleteData(CredentialsConfig credentials)
+ {
+ if (credentials == null)
+ {
+ throw new ArgumentException($"Please, provide credentials.");
+ }
+ Console.WriteLine($"Deleting graph data of \"{credentials.Database}\" database...");
+ await using var driver = GraphDatabase.Driver(CONNECTION, AuthTokens.Basic(credentials.User, credentials.Password));
+ await using var session = driver.AsyncSession(o => o.WithDatabase(credentials.Database));
+ await session.RunAsync("MATCH (n) DETACH DELETE n;");
+ Console.WriteLine($"Deleting graph data of \"{credentials.Database}\" database complete.");
+ }
+
+ public static async Task InsertData(IList triples, CredentialsConfig credentials)
{
if (credentials == null)
{
throw new ArgumentException($"Please, provide credentials.");
}
Console.WriteLine($"Code Knowledge Graph use \"{credentials.Database}\" Neo4j database.");
- var driver = GraphDatabase.Driver(CONNECTION, AuthTokens.Basic(credentials.User, credentials.Password));
- var session = driver.AsyncSession(o => o.WithDatabase(credentials.Database));
+ await using var driver = GraphDatabase.Driver(CONNECTION, AuthTokens.Basic(credentials.User, credentials.Password));
+ await using var session = driver.AsyncSession(o => o.WithDatabase(credentials.Database));
try
{
- if (isDelete)
- {
- Console.WriteLine($"Deleting graph data of \"{credentials.Database}\" database...");
- await session.RunAsync("MATCH (n) DETACH DELETE n;");
- Console.WriteLine($"Deleting graph data of \"{credentials.Database}\" database complete.");
- }
Console.WriteLine($"Processing {triples.Count} triples...");
foreach (var triple in triples)
{
@@ -39,11 +46,6 @@ public static async Task InsertData(IList triples, CredentialsConfig cre
{
Console.WriteLine(ex.Message);
}
- finally
- {
- await session.CloseAsync();
- await driver.CloseAsync();
- }
}
}
}
\ No newline at end of file
diff --git a/Strazh/Domain/Nodes.cs b/Strazh/Domain/Nodes.cs
index 61c774a..c7859ae 100755
--- a/Strazh/Domain/Nodes.cs
+++ b/Strazh/Domain/Nodes.cs
@@ -1,4 +1,7 @@
+using System;
using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
namespace Strazh.Domain
{
@@ -24,7 +27,17 @@ public Node(string fullName, string name)
protected virtual void SetPrimaryKey()
{
- Pk = FullName.GetHashCode().ToString();
+ Pk = DeterministicHash(FullName);
+ }
+
+ // string.GetHashCode() is randomized per-process in .NET Core and later, so using it
+ // as a Neo4j pk causes every run to generate different values for the same node,
+ // defeating MERGE and creating duplicate nodes. MD5 gives a stable, collision-resistant
+ // identifier across runs. (This is not a security use — stability is all that matters.)
+ protected static string DeterministicHash(string value)
+ {
+ var bytes = MD5.HashData(Encoding.UTF8.GetBytes(value));
+ return Convert.ToHexString(bytes);
}
public virtual string Set(string node) =>
@@ -36,46 +49,26 @@ public string ToInspection() =>
// Code
- public abstract class CodeNode : Node
+ public abstract class CodeNode(string fullName, string name, string[] modifiers = null) : Node(fullName, name)
{
- public CodeNode(string fullName, string name, string[] modifiers = null)
- : base(fullName, name)
- {
-
- Modifiers = modifiers == null ? "" : string.Join(", ", modifiers);
- }
-
- public string Modifiers { get; }
+ public string Modifiers { get; } = modifiers == null ? "" : string.Join(", ", modifiers);
public override string Set(string node)
=> $"{base.Set(node)}{(string.IsNullOrEmpty(Modifiers) ? "" : $", {node}.modifiers = \"{Modifiers}\"")}";
}
- public abstract class TypeNode : CodeNode
- {
- public TypeNode(string fullName, string name, string[] modifiers = null)
- : base(fullName, name, modifiers)
- {
- }
- }
+ public abstract class TypeNode(string fullName, string name, string[] modifiers = null)
+ : CodeNode(fullName, name, modifiers);
- public class ClassNode : TypeNode
+ public class ClassNode(string fullName, string name, string[] modifiers = null)
+ : TypeNode(fullName, name, modifiers)
{
- public ClassNode(string fullName, string name, string[] modifiers = null)
- : base(fullName, name, modifiers)
- {
- }
-
public override string Label { get; } = "Class";
}
- public class InterfaceNode : TypeNode
+ public class InterfaceNode(string fullName, string name, string[] modifiers = null)
+ : TypeNode(fullName, name, modifiers)
{
- public InterfaceNode(string fullName, string name, string[] modifiers = null)
- : base(fullName, name, modifiers)
- {
- }
-
public override string Label { get; } = "Interface";
}
@@ -100,25 +93,19 @@ public override string Set(string node)
protected override void SetPrimaryKey()
{
- Pk = $"{FullName}{Arguments}{ReturnType}".GetHashCode().ToString();
+ Pk = DeterministicHash($"{FullName}{Arguments}{ReturnType}");
}
}
// Structure
- public class FileNode : Node
+ public class FileNode(string fullName, string name) : Node(fullName, name)
{
- public FileNode(string fullName, string name)
- : base(fullName, name) { }
-
public override string Label { get; } = "File";
}
- public class FolderNode : Node
+ public class FolderNode(string fullName, string name) : Node(fullName, name)
{
- public FolderNode(string fullName, string name)
- : base(fullName, name) { }
-
public override string Label { get; } = "Folder";
}
@@ -127,14 +114,11 @@ public class SolutionNode(string name) : Node(name, name)
public override string Label => "Solution";
}
- public class ProjectNode : Node
+ public class ProjectNode(string fullName, string name) : Node(fullName, name)
{
public ProjectNode(string name)
: this(name, name) { }
- public ProjectNode(string fullName, string name)
- : base(fullName, name) { }
-
public override string Label { get; } = "Project";
}
@@ -156,7 +140,7 @@ public override string Set(string node)
protected override void SetPrimaryKey()
{
- Pk = $"{FullName}{Version}".GetHashCode().ToString();
+ Pk = DeterministicHash($"{FullName}{Version}");
}
}
}
\ No newline at end of file
diff --git a/Strazh/Domain/Triples.cs b/Strazh/Domain/Triples.cs
index e213591..0e83d94 100755
--- a/Strazh/Domain/Triples.cs
+++ b/Strazh/Domain/Triples.cs
@@ -1,19 +1,12 @@
namespace Strazh.Domain
{
- public abstract class Triple : IInspectable
+ public abstract class Triple(Node nodeA, Node nodeB, Relationship relationship) : IInspectable
{
- public Node NodeA { get; set; }
+ public Node NodeA { get; set; } = nodeA;
- public Node NodeB { get; set; }
+ public Node NodeB { get; set; } = nodeB;
- public Relationship Relationship { get; set; }
-
- protected Triple(Node nodeA, Node nodeB, Relationship relationship)
- {
- NodeA = nodeA;
- NodeB = nodeB;
- Relationship = relationship;
- }
+ public Relationship Relationship { get; set; } = relationship;
public override string ToString()
=> $"MERGE (a:{NodeA.Label} {{ pk: \"{NodeA.Pk}\" }}) ON CREATE SET {NodeA.Set("a")} ON MATCH SET {NodeA.Set("a")} MERGE (b:{NodeB.Label} {{ pk: \"{NodeB.Pk}\" }}) ON CREATE SET {NodeB.Set("b")} ON MATCH SET {NodeB.Set("b")} MERGE (a)-[:{Relationship.Type}]->(b);";
@@ -24,23 +17,13 @@ public string ToInspection() =>
// Structure
- public class TripleDependsOnProject : Triple
- {
- public TripleDependsOnProject(
- ProjectNode projectA,
- ProjectNode projectB)
- : base(projectA, projectB, new DependsOnRelationship())
- { }
- }
+ public class TripleDependsOnProject(
+ ProjectNode projectA,
+ ProjectNode projectB) : Triple(projectA, projectB, new DependsOnRelationship());
- public class TripleDependsOnPackage : Triple
- {
- public TripleDependsOnPackage(
- ProjectNode projectA,
- PackageNode packageB)
- : base(projectA, packageB, new DependsOnRelationship())
- { }
- }
+ public class TripleDependsOnPackage(
+ ProjectNode projectA,
+ PackageNode packageB) : Triple(projectA, packageB, new DependsOnRelationship());
public class TripleIncludedIn : Triple
{
@@ -71,53 +54,27 @@ public TripleIncludedIn(
}
- public class TripleContains : Triple
- {
- public TripleContains(
- SolutionNode solution,
- ProjectNode project)
- : base(solution, project, new ContainsRelationship())
- {
- }
- }
+ public class TripleContains(
+ SolutionNode solution,
+ ProjectNode project) : Triple(solution, project, new ContainsRelationship());
- public class TripleDeclaredAt : Triple
- {
- public TripleDeclaredAt(
- TypeNode typeA,
- FileNode fileB)
- : base(typeA, fileB, new DeclaredAtRelationship())
- { }
- }
+ public class TripleDeclaredAt(
+ TypeNode typeA,
+ FileNode fileB) : Triple(typeA, fileB, new DeclaredAtRelationship());
// Code
- public class TripleInvoke : Triple
- {
- public TripleInvoke(
- MethodNode methodA,
- MethodNode methodB)
- : base(methodA, methodB, new InvokeRelationship())
- { }
- }
+ public class TripleInvoke(
+ MethodNode methodA,
+ MethodNode methodB) : Triple(methodA, methodB, new InvokeRelationship());
- public class TripleHave : Triple
- {
- public TripleHave(
- TypeNode typeA,
- MethodNode methodB)
- : base(typeA, methodB, new HaveRelationship())
- { }
- }
+ public class TripleHave(
+ TypeNode typeA,
+ MethodNode methodB) : Triple(typeA, methodB, new HaveRelationship());
- public class TripleConstruct : Triple
- {
- public TripleConstruct(
- MethodNode methodA,
- ClassNode classB)
- : base(methodA, classB, new ConstructRelationship())
- { }
- }
+ public class TripleConstruct(
+ MethodNode methodA,
+ ClassNode classB) : Triple(methodA, classB, new ConstructRelationship());
public class TripleOfType : Triple
{
diff --git a/Strazh/Program.cs b/Strazh/Program.cs
index ae0a8c9..c43421f 100644
--- a/Strazh/Program.cs
+++ b/Strazh/Program.cs
@@ -1,86 +1,94 @@
-using System;
-using System.CommandLine;
-using System.CommandLine.Invocation;
-using Microsoft.Build.Logging.StructuredLogger;
-using Strazh.Analysis;
-using Task = System.Threading.Tasks.Task;
-
-namespace Strazh
-{
- public class Program
- {
-
- public static async Task Main(params string[] args)
- {
-#if DEBUG
- // There is an issue with using Neo4j.Driver 4.2.0
- // System.IO.FileNotFoundException: Could not load file or assembly '4.2.37.0'. The system cannot find the file specified.
- // Workaround to load assembly and avoid issue
- System.Reflection.Assembly.Load("Neo4j.Driver");
-#endif
- var rootCommand = new RootCommand();
-
- var optionCredentials = new Option("--credentials", "required information in format `dbname:user:password` to connect to Neo4j Database");
- optionCredentials.AddAlias("-c");
- optionCredentials.IsRequired = true;
- rootCommand.Add(optionCredentials);
-
- var optionMode = new Option("--tier", "optional flag as `project` or `code` or 'all' (default `all`) selected tier to scan in a codebase");
- optionMode.AddAlias("-t");
- optionMode.IsRequired = false;
- rootCommand.Add(optionMode);
-
- var optionDelete = new Option("--delete", "optional flag as `true` or `false` or no flag (default `true`) to delete data in graph before execution");
- optionDelete.AddAlias("-d");
- optionDelete.IsRequired = false;
- rootCommand.Add(optionDelete);
-
- var optionSolution = new Option("--solution", "optional absolute path to only one `.sln` file (can't be used together with -p / --projects)");
- optionSolution.AddAlias("-s");
- optionSolution.IsRequired = false;
- rootCommand.Add(optionSolution);
-
- var optionProjects = new Option("--projects", "optional list of absolute path to one or many `.csproj` files (can't be used together with -s / --solution)");
- optionProjects.AddAlias("-p");
- optionProjects.IsRequired = false;
- rootCommand.Add(optionProjects);
-
- rootCommand.SetHandler(BuildKnowledgeGraph, optionCredentials, optionMode, optionDelete, optionSolution, optionProjects);
-
- await rootCommand.InvokeAsync(args);
- }
-
- private static async Task BuildKnowledgeGraph(string credentials, string tier, string delete, string solution, string[] projects)
- {
- try
- {
- var config = new AnalyzerConfig(
- credentials,
- tier,
- delete,
- solution,
- projects
- );
- if (!config.IsValid)
- {
- Console.WriteLine("Please submit only one thing: `--solution` (-s) or `--projects` (-p)");
- return;
- }
- var isNeo4jReady = await Healthcheck.IsNeo4jReady();
- if (!isNeo4jReady)
- {
- Console.WriteLine("Strazh failed to start. There is no Neo4j instance ready to use.");
- return;
- }
-
- Console.WriteLine($"Brewing a Code Knowledge Graph of tier \"{config.Tier}\".");
- await Analyzer.Analyze(config);
- Console.WriteLine("Code Knowledge Graph created.");
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- }
- }
- }
-}
+using System;
+using System.CommandLine;
+using System.Threading;
+using System.Threading.Tasks;
+using Strazh.Analysis;
+
+namespace Strazh
+{
+ public class Program
+ {
+
+ public static async Task Main(params string[] args)
+ {
+ var rootCommand = new RootCommand();
+
+ var optionCredentials = new Option("--credentials", "-c")
+ {
+ Description = "required information in format `dbname:user:password` to connect to Neo4j Database",
+ Required = true
+ };
+ rootCommand.Options.Add(optionCredentials);
+
+ var optionMode = new Option("--tier", "-t")
+ {
+ Description = "optional flag as `project` or `code` or 'all' (default `all`) selected tier to scan in a codebase"
+ };
+ rootCommand.Options.Add(optionMode);
+
+ var optionDelete = new Option("--delete", "-d")
+ {
+ Description = "optional flag as `true` or `false` or no flag (default `true`) to delete data in graph before execution"
+ };
+ rootCommand.Options.Add(optionDelete);
+
+ var optionSolution = new Option("--solution", "-s")
+ {
+ Description = "optional absolute path to only one `.sln` file (can't be used together with -p / --projects)"
+ };
+ rootCommand.Options.Add(optionSolution);
+
+ var optionProjects = new Option("--projects", "-p")
+ {
+ Description = "optional list of absolute path to one or many `.csproj` files (can't be used together with -s / --solution)",
+ AllowMultipleArgumentsPerToken = true
+ };
+ rootCommand.Options.Add(optionProjects);
+
+ rootCommand.SetAction(async (ParseResult parseResult, CancellationToken token) =>
+ {
+ await BuildKnowledgeGraph(
+ parseResult.GetValue(optionCredentials),
+ parseResult.GetValue(optionMode),
+ parseResult.GetValue(optionDelete),
+ parseResult.GetValue(optionSolution),
+ parseResult.GetValue(optionProjects));
+ });
+
+ return await rootCommand.Parse(args).InvokeAsync();
+ }
+
+ private static async Task BuildKnowledgeGraph(string credentials, string tier, string delete, string solution, string[] projects)
+ {
+ try
+ {
+ var config = new AnalyzerConfig(
+ credentials,
+ tier,
+ delete,
+ solution,
+ projects
+ );
+ if (!config.IsValid)
+ {
+ Console.WriteLine("Please submit only one thing: `--solution` (-s) or `--projects` (-p)");
+ return;
+ }
+ var isNeo4jReady = await Healthcheck.IsNeo4jReady();
+ if (!isNeo4jReady)
+ {
+ Console.WriteLine("Strazh failed to start. There is no Neo4j instance ready to use.");
+ return;
+ }
+
+ Console.WriteLine($"Brewing a Code Knowledge Graph of tier \"{config.Tier}\".");
+ await Analyzer.Analyze(config);
+ Console.WriteLine("Code Knowledge Graph created.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ }
+ }
+ }
+}
diff --git a/Strazh/Strazh.csproj b/Strazh/Strazh.csproj
index 5ebf613..e0d0ff1 100644
--- a/Strazh/Strazh.csproj
+++ b/Strazh/Strazh.csproj
@@ -2,19 +2,18 @@
Exe
- net9.0
+ net10.0
1.0.0-beta.1
enable
-
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Strazh/Strazh.sln b/Strazh/Strazh.sln
index fb230ae..b892bd2 100755
--- a/Strazh/Strazh.sln
+++ b/Strazh/Strazh.sln
@@ -1,49 +1,91 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.808.7
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strazh", "Strazh.csproj", "{1C92D6A7-867F-42CC-933B-DB78249BA75B}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B52B8BC0-5B94-4169-8EE1-CFF023E6F34E}"
- ProjectSection(SolutionItems) = preProject
- ..\docker-compose.yml = ..\docker-compose.yml
- ..\Dockerfile = ..\Dockerfile
- EndProjectSection
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strazh.Tests.ProjectA", "..\SystemUnderTest\Strazh.Tests.ProjectA\Strazh.Tests.ProjectA.csproj", "{CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strazh.Tests.ProjectB", "..\SystemUnderTest\Strazh.Tests.ProjectB\Strazh.Tests.ProjectB.csproj", "{D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{EF685B52-D0F9-4350-9956-90CF2DE13610}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Release|Any CPU.Build.0 = Release|Any CPU
- {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|Any CPU.Build.0 = Release|Any CPU
- {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C} = {EF685B52-D0F9-4350-9956-90CF2DE13610}
- {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC} = {EF685B52-D0F9-4350-9956-90CF2DE13610}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {2B976F72-0E21-452F-9EAE-868AEEF480D8}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.808.7
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strazh", "Strazh.csproj", "{1C92D6A7-867F-42CC-933B-DB78249BA75B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B52B8BC0-5B94-4169-8EE1-CFF023E6F34E}"
+ ProjectSection(SolutionItems) = preProject
+ ..\docker-compose.yml = ..\docker-compose.yml
+ ..\Dockerfile = ..\Dockerfile
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strazh.Tests.ProjectA", "..\SystemUnderTest\Strazh.Tests.ProjectA\Strazh.Tests.ProjectA.csproj", "{CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strazh.Tests.ProjectB", "..\SystemUnderTest\Strazh.Tests.ProjectB\Strazh.Tests.ProjectB.csproj", "{D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{EF685B52-D0F9-4350-9956-90CF2DE13610}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strazh.Tests", "..\Strazh.Tests\Strazh.Tests.csproj", "{267F7E5F-419B-434D-B9C0-27A12FC95B77}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Debug|x64.Build.0 = Debug|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Debug|x86.Build.0 = Debug|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Release|x64.ActiveCfg = Release|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Release|x64.Build.0 = Release|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Release|x86.ActiveCfg = Release|Any CPU
+ {1C92D6A7-867F-42CC-933B-DB78249BA75B}.Release|x86.Build.0 = Release|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|x64.Build.0 = Debug|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|x86.Build.0 = Debug|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|x64.ActiveCfg = Release|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|x64.Build.0 = Release|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|x86.ActiveCfg = Release|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|x86.Build.0 = Release|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|x64.Build.0 = Debug|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|x86.Build.0 = Debug|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|x64.ActiveCfg = Release|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|x64.Build.0 = Release|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|x86.ActiveCfg = Release|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|x86.Build.0 = Release|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Debug|x64.Build.0 = Debug|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Debug|x86.Build.0 = Debug|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Release|Any CPU.Build.0 = Release|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Release|x64.ActiveCfg = Release|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Release|x64.Build.0 = Release|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Release|x86.ActiveCfg = Release|Any CPU
+ {267F7E5F-419B-434D-B9C0-27A12FC95B77}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C} = {EF685B52-D0F9-4350-9956-90CF2DE13610}
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC} = {EF685B52-D0F9-4350-9956-90CF2DE13610}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {2B976F72-0E21-452F-9EAE-868AEEF480D8}
+ EndGlobalSection
+EndGlobal
diff --git a/SystemUnderTest/SystemUnderTest.sln b/SystemUnderTest/SystemUnderTest.sln
new file mode 100644
index 0000000..9251aef
--- /dev/null
+++ b/SystemUnderTest/SystemUnderTest.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strazh.Tests.ProjectA", "Strazh.Tests.ProjectA\Strazh.Tests.ProjectA.csproj", "{CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strazh.Tests.ProjectB", "Strazh.Tests.ProjectB\Strazh.Tests.ProjectB.csproj", "{D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBE72A8F-DDA9-45DC-987B-B55FA9A3EC2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D8AEF0F8-1FC1-4CAF-AC35-C62D29A83ABC}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/docker-compose.yml b/docker-compose.yml
index 1d52306..9c0beb6 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,3 @@
-version: '3'
services:
strazh:
@@ -7,7 +6,7 @@ services:
container_name: strazh
network_mode: host
volumes:
- - C:\src\github\strazh\SystemUnderTest:/dest
+ - ./SystemUnderTest:/dest
environment:
- c=neo4j:neo4j:strazhpass
- p=/dest/Strazh.Tests.ProjectB/Strazh.Tests.ProjectB.csproj /dest/Strazh.Tests.ProjectA/Strazh.Tests.ProjectA.csproj
@@ -15,7 +14,7 @@ services:
- neo4j
neo4j:
- image: neo4j:4.2.0
+ image: neo4j:2026.03.1
container_name: strazh_neo4j
restart: unless-stopped
ports:
@@ -23,8 +22,8 @@ services:
- 7687:7687
environment:
NEO4J_AUTH: neo4j/strazhpass
- NEO4J_dbms_memory_pagecache_size: 1G
- NEO4J_dbms.memory.heap.initial_size: 1G
- NEO4J_dbms_memory_heap_max__size: 1G
- NEO4JLABS_PLUGINS: "[\"apoc\",\"graph-data-science\"]"
+ NEO4J_server_memory_pagecache_size: 1G
+ NEO4J_server_memory_heap_initial__size: 1G
+ NEO4J_server_memory_heap_max__size: 1G
+ NEO4J_PLUGINS: "[\"apoc\",\"graph-data-science\"]"
\ No newline at end of file