up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using StellaOps.Scanner.Reachability.Lifters;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Reachability.FixtureTests;
|
||||
|
||||
public sealed class ReachabilityLifterTests : IDisposable
|
||||
{
|
||||
private readonly string _tempDir;
|
||||
|
||||
public ReachabilityLifterTests()
|
||||
{
|
||||
_tempDir = Path.Combine(Path.GetTempPath(), $"lifter-test-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_tempDir))
|
||||
{
|
||||
Directory.Delete(_tempDir, true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NodeLifter_ExtractsPackageInfo()
|
||||
{
|
||||
// Arrange
|
||||
var packageJson = """
|
||||
{
|
||||
"name": "my-app",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"express": "^4.18.0",
|
||||
"lodash": "^4.17.0"
|
||||
}
|
||||
}
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package.json"), packageJson);
|
||||
|
||||
var lifter = new NodeReachabilityLifter();
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = _tempDir,
|
||||
AnalysisId = "test-analysis-001"
|
||||
};
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
|
||||
// Act
|
||||
await lifter.LiftAsync(context, builder, CancellationToken.None);
|
||||
var graph = builder.ToUnionGraph("node");
|
||||
|
||||
// Assert
|
||||
graph.Nodes.Should().NotBeEmpty();
|
||||
graph.Nodes.Should().Contain(n => n.Display == "my-app");
|
||||
graph.Nodes.Should().Contain(n => n.Display == "express");
|
||||
graph.Nodes.Should().Contain(n => n.Display == "lodash");
|
||||
|
||||
graph.Edges.Should().NotBeEmpty();
|
||||
graph.Edges.Should().Contain(e => e.EdgeType == "import");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NodeLifter_ExtractsEntrypoints()
|
||||
{
|
||||
// Arrange
|
||||
var packageJson = """
|
||||
{
|
||||
"name": "my-cli",
|
||||
"version": "2.0.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.mjs",
|
||||
"bin": {
|
||||
"mycli": "bin/cli.js"
|
||||
}
|
||||
}
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package.json"), packageJson);
|
||||
|
||||
var lifter = new NodeReachabilityLifter();
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = _tempDir,
|
||||
AnalysisId = "test-analysis-002"
|
||||
};
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
|
||||
// Act
|
||||
await lifter.LiftAsync(context, builder, CancellationToken.None);
|
||||
var graph = builder.ToUnionGraph("node");
|
||||
|
||||
// Assert
|
||||
graph.Nodes.Should().Contain(n => n.Kind == "entrypoint");
|
||||
graph.Nodes.Should().Contain(n => n.Kind == "binary");
|
||||
graph.Edges.Should().Contain(e => e.EdgeType == "loads");
|
||||
graph.Edges.Should().Contain(e => e.EdgeType == "spawn");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NodeLifter_ExtractsImportsFromSource()
|
||||
{
|
||||
// Arrange
|
||||
var packageJson = """
|
||||
{
|
||||
"name": "my-app",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package.json"), packageJson);
|
||||
|
||||
var sourceCode = """
|
||||
import express from 'express';
|
||||
const lodash = require('lodash');
|
||||
import('./dynamic-module.js');
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "index.js"), sourceCode);
|
||||
|
||||
var lifter = new NodeReachabilityLifter();
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = _tempDir,
|
||||
AnalysisId = "test-analysis-003"
|
||||
};
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
|
||||
// Act
|
||||
await lifter.LiftAsync(context, builder, CancellationToken.None);
|
||||
var graph = builder.ToUnionGraph("node");
|
||||
|
||||
// Assert
|
||||
graph.Nodes.Should().Contain(n => n.Display == "express");
|
||||
graph.Nodes.Should().Contain(n => n.Display == "lodash");
|
||||
graph.Edges.Count(e => e.EdgeType == "import").Should().BeGreaterOrEqualTo(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DotNetLifter_ExtractsProjectInfo()
|
||||
{
|
||||
// Arrange
|
||||
var csproj = """
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>MyApp</AssemblyName>
|
||||
<RootNamespace>MyCompany.MyApp</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "MyApp.csproj"), csproj);
|
||||
|
||||
var lifter = new DotNetReachabilityLifter();
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = _tempDir,
|
||||
AnalysisId = "test-analysis-004"
|
||||
};
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
|
||||
// Act
|
||||
await lifter.LiftAsync(context, builder, CancellationToken.None);
|
||||
var graph = builder.ToUnionGraph("dotnet");
|
||||
|
||||
// Assert
|
||||
graph.Nodes.Should().NotBeEmpty();
|
||||
graph.Nodes.Should().Contain(n => n.Display == "MyApp");
|
||||
graph.Nodes.Should().Contain(n => n.Display == "Newtonsoft.Json");
|
||||
graph.Nodes.Should().Contain(n => n.Display == "Serilog");
|
||||
graph.Nodes.Should().Contain(n => n.Kind == "namespace" && n.Display == "MyCompany.MyApp");
|
||||
|
||||
graph.Edges.Should().NotBeEmpty();
|
||||
graph.Edges.Count(e => e.EdgeType == "import").Should().BeGreaterOrEqualTo(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DotNetLifter_ExtractsProjectReferences()
|
||||
{
|
||||
// Arrange
|
||||
var libDir = Path.Combine(_tempDir, "Lib");
|
||||
Directory.CreateDirectory(libDir);
|
||||
|
||||
var libCsproj = """
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>MyLib</AssemblyName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(libDir, "MyLib.csproj"), libCsproj);
|
||||
|
||||
var appCsproj = """
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>MyApp</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="Lib/MyLib.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "MyApp.csproj"), appCsproj);
|
||||
|
||||
var lifter = new DotNetReachabilityLifter();
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = _tempDir,
|
||||
AnalysisId = "test-analysis-005"
|
||||
};
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
|
||||
// Act
|
||||
await lifter.LiftAsync(context, builder, CancellationToken.None);
|
||||
var graph = builder.ToUnionGraph("dotnet");
|
||||
|
||||
// Assert
|
||||
graph.Nodes.Should().Contain(n => n.Display == "MyApp");
|
||||
graph.Nodes.Should().Contain(n => n.Display == "MyLib");
|
||||
graph.Edges.Should().Contain(e => e.EdgeType == "import");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LifterRegistry_CombinesMultipleLanguages()
|
||||
{
|
||||
// Arrange
|
||||
var packageJson = """
|
||||
{
|
||||
"name": "hybrid-app",
|
||||
"version": "1.0.0",
|
||||
"dependencies": { "express": "^4.0.0" }
|
||||
}
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package.json"), packageJson);
|
||||
|
||||
var csproj = """
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>HybridBackend</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "Backend.csproj"), csproj);
|
||||
|
||||
var registry = new ReachabilityLifterRegistry();
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = _tempDir,
|
||||
AnalysisId = "test-analysis-006"
|
||||
};
|
||||
|
||||
// Act
|
||||
var graph = await registry.LiftAllAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
registry.Lifters.Should().HaveCountGreaterOrEqualTo(2);
|
||||
graph.Nodes.Should().Contain(n => n.Lang == "node");
|
||||
graph.Nodes.Should().Contain(n => n.Lang == "dotnet");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LifterRegistry_SelectsSpecificLanguages()
|
||||
{
|
||||
// Arrange
|
||||
var packageJson = """
|
||||
{
|
||||
"name": "node-only",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package.json"), packageJson);
|
||||
|
||||
var registry = new ReachabilityLifterRegistry();
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = _tempDir,
|
||||
AnalysisId = "test-analysis-007"
|
||||
};
|
||||
|
||||
// Act
|
||||
var graph = await registry.LiftAsync(context, new[] { "node" }, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
graph.Nodes.Should().OnlyContain(n => n.Lang == "node");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LifterRegistry_LiftAndWrite_CreatesOutputFiles()
|
||||
{
|
||||
// Arrange
|
||||
var packageJson = """
|
||||
{
|
||||
"name": "write-test",
|
||||
"version": "1.0.0",
|
||||
"dependencies": { "lodash": "^4.0.0" }
|
||||
}
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(_tempDir, "package.json"), packageJson);
|
||||
|
||||
var outputDir = Path.Combine(_tempDir, "output");
|
||||
Directory.CreateDirectory(outputDir);
|
||||
|
||||
var registry = new ReachabilityLifterRegistry();
|
||||
var context = new ReachabilityLifterContext
|
||||
{
|
||||
RootPath = _tempDir,
|
||||
AnalysisId = "test-write-001"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await registry.LiftAndWriteAsync(context, outputDir, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
File.Exists(result.MetaPath).Should().BeTrue();
|
||||
File.Exists(result.Nodes.Path).Should().BeTrue();
|
||||
File.Exists(result.Edges.Path).Should().BeTrue();
|
||||
result.Nodes.RecordCount.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GraphBuilder_AddsRichNodes()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
|
||||
// Act
|
||||
builder.AddNode(
|
||||
symbolId: "sym:test:abc123",
|
||||
lang: "test",
|
||||
kind: "function",
|
||||
display: "myFunction",
|
||||
sourceFile: "src/main.ts",
|
||||
sourceLine: 42,
|
||||
attributes: new System.Collections.Generic.Dictionary<string, string>
|
||||
{
|
||||
["visibility"] = "public",
|
||||
["async"] = "true"
|
||||
});
|
||||
|
||||
var graph = builder.ToUnionGraph("test");
|
||||
|
||||
// Assert
|
||||
graph.Nodes.Should().HaveCount(1);
|
||||
var node = graph.Nodes.First();
|
||||
node.SymbolId.Should().Be("sym:test:abc123");
|
||||
node.Lang.Should().Be("test");
|
||||
node.Kind.Should().Be("function");
|
||||
node.Display.Should().Be("myFunction");
|
||||
node.Attributes.Should().ContainKey("visibility");
|
||||
node.Source.Should().NotBeNull();
|
||||
node.Source!.Evidence.Should().Contain("src/main.ts:42");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GraphBuilder_AddsRichEdges()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new ReachabilityGraphBuilder();
|
||||
|
||||
// Act
|
||||
builder.AddEdge(
|
||||
from: "sym:test:from",
|
||||
to: "sym:test:to",
|
||||
edgeType: EdgeTypes.Call,
|
||||
confidence: EdgeConfidence.High,
|
||||
origin: "static",
|
||||
provenance: Provenance.Il,
|
||||
evidence: "file:src/main.cs:100");
|
||||
|
||||
var graph = builder.ToUnionGraph("test");
|
||||
|
||||
// Assert
|
||||
graph.Edges.Should().HaveCount(1);
|
||||
var edge = graph.Edges.First();
|
||||
edge.From.Should().Be("sym:test:from");
|
||||
edge.To.Should().Be("sym:test:to");
|
||||
edge.EdgeType.Should().Be("call");
|
||||
edge.Confidence.Should().Be("high");
|
||||
edge.Source.Should().NotBeNull();
|
||||
edge.Source!.Origin.Should().Be("static");
|
||||
edge.Source.Provenance.Should().Be("il");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Reachability.FixtureTests;
|
||||
|
||||
public sealed class SymbolIdTests
|
||||
{
|
||||
[Fact]
|
||||
public void ForJava_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForJava("com.example", "MyClass", "doSomething", "(Ljava/lang/String;)V");
|
||||
|
||||
id.Should().StartWith("sym:java:");
|
||||
id.Should().HaveLength("sym:java:".Length + 43); // Base64url SHA-256 without padding = 43 chars
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForJava_IsDeterministic()
|
||||
{
|
||||
var id1 = SymbolId.ForJava("com.example", "MyClass", "doSomething", "(Ljava/lang/String;)V");
|
||||
var id2 = SymbolId.ForJava("com.example", "MyClass", "doSomething", "(Ljava/lang/String;)V");
|
||||
|
||||
id1.Should().Be(id2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForJava_IsCaseInsensitive()
|
||||
{
|
||||
var id1 = SymbolId.ForJava("com.example", "MyClass", "doSomething", "()V");
|
||||
var id2 = SymbolId.ForJava("COM.EXAMPLE", "MYCLASS", "DOSOMETHING", "()V");
|
||||
|
||||
id1.Should().Be(id2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForDotNet_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForDotNet("MyAssembly", "MyNamespace", "MyClass", "MyMethod(System.String)");
|
||||
|
||||
id.Should().StartWith("sym:dotnet:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForDotNet_DifferentSignaturesProduceDifferentIds()
|
||||
{
|
||||
var id1 = SymbolId.ForDotNet("MyAssembly", "MyNamespace", "MyClass", "Method(String)");
|
||||
var id2 = SymbolId.ForDotNet("MyAssembly", "MyNamespace", "MyClass", "Method(Int32)");
|
||||
|
||||
id1.Should().NotBe(id2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForNode_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForNode("express", "lib/router", "function");
|
||||
|
||||
id.Should().StartWith("sym:node:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForNode_HandlesScopedPackages()
|
||||
{
|
||||
var id1 = SymbolId.ForNode("@angular/core", "src/render", "function");
|
||||
var id2 = SymbolId.ForNode("@angular/core", "src/render", "function");
|
||||
|
||||
id1.Should().Be(id2);
|
||||
id1.Should().StartWith("sym:node:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForGo_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForGo("github.com/example/repo", "pkg/http", "Server", "HandleRequest");
|
||||
|
||||
id.Should().StartWith("sym:go:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForGo_FunctionWithoutReceiver()
|
||||
{
|
||||
var id = SymbolId.ForGo("github.com/example/repo", "pkg/main", "", "main");
|
||||
|
||||
id.Should().StartWith("sym:go:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForRust_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForRust("my_crate", "foo::bar", "my_function", "_ZN8my_crate3foo3bar11my_functionE");
|
||||
|
||||
id.Should().StartWith("sym:rust:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForSwift_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForSwift("MyModule", "MyClass", "myMethod", null);
|
||||
|
||||
id.Should().StartWith("sym:swift:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForShell_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForShell("scripts/deploy.sh", "run_migration");
|
||||
|
||||
id.Should().StartWith("sym:shell:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForBinary_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForBinary("7f6e5d4c3b2a1908", ".text", "_start");
|
||||
|
||||
id.Should().StartWith("sym:binary:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForPython_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForPython("requests", "requests.api", "get");
|
||||
|
||||
id.Should().StartWith("sym:python:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForRuby_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForRuby("rails", "ActiveRecord::Base", "#save");
|
||||
|
||||
id.Should().StartWith("sym:ruby:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForPhp_CreatesCanonicalSymbolId()
|
||||
{
|
||||
var id = SymbolId.ForPhp("laravel/framework", "Illuminate\\Http", "Request::input");
|
||||
|
||||
id.Should().StartWith("sym:php:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ValidSymbolId_ReturnsComponents()
|
||||
{
|
||||
var id = SymbolId.ForJava("com.example", "MyClass", "method", "()V");
|
||||
|
||||
var result = SymbolId.Parse(id);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.Value.Lang.Should().Be("java");
|
||||
result.Value.Fragment.Should().HaveLength(43);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_InvalidSymbolId_ReturnsNull()
|
||||
{
|
||||
SymbolId.Parse("invalid").Should().BeNull();
|
||||
SymbolId.Parse("sym:").Should().BeNull();
|
||||
SymbolId.Parse("sym:java").Should().BeNull();
|
||||
SymbolId.Parse("").Should().BeNull();
|
||||
SymbolId.Parse(null!).Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromTuple_CreatesSymbolIdFromRawTuple()
|
||||
{
|
||||
var tuple = "my\0canonical\0tuple";
|
||||
var id = SymbolId.FromTuple("custom", tuple);
|
||||
|
||||
id.Should().StartWith("sym:custom:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllLanguagesAreDifferent()
|
||||
{
|
||||
// Same tuple data should produce different IDs for different languages
|
||||
var java = SymbolId.ForJava("pkg", "cls", "meth", "()V");
|
||||
var dotnet = SymbolId.ForDotNet("pkg", "cls", "meth", "()V");
|
||||
var node = SymbolId.ForNode("pkg", "cls", "meth");
|
||||
|
||||
java.Should().NotBe(dotnet);
|
||||
dotnet.Should().NotBe(node);
|
||||
java.Should().NotBe(node);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user