feat: integrate performance upgrade and agent inspection tooling
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using NUnit.Framework;
|
||||
using ShrinkSDK.CodeGen.Analyzers;
|
||||
|
||||
public class AnalyzerTests
|
||||
{
|
||||
private static readonly MetadataReference[] SystemReferences = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!).Split(Path.PathSeparator).Select(p => MetadataReference.CreateFromFile(p)).ToArray();
|
||||
private static CSharpCompilation Compile(string name, string source, params MetadataReference[] references) => CSharpCompilation.Create(name, [CSharpSyntaxTree.ParseText(source)], SystemReferences.Concat(references), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
private static MetadataReference Emit(CSharpCompilation compilation)
|
||||
{
|
||||
using var stream = new MemoryStream(); var result = compilation.Emit(stream);
|
||||
Assert.That(result.Success, Is.True, string.Join(";", result.Diagnostics));
|
||||
return MetadataReference.CreateFromImage(stream.ToArray());
|
||||
}
|
||||
[Test] public async Task DuplicateContractsAcrossReferencedAssembliesAreErrors()
|
||||
{
|
||||
var contract = Emit(Compile("Contract", "namespace ShrinkNetwork { public class ShrinkNetworkMessageAttribute : System.Attribute { public ShrinkNetworkMessageAttribute(int opcode, string route) {} } }"));
|
||||
var a = Emit(Compile("A", "[ShrinkNetwork.ShrinkNetworkMessage(1, \"one\")] public class A {}".Replace("\\", ""), contract));
|
||||
var b = Emit(Compile("B", "[ShrinkNetwork.ShrinkNetworkMessage(1, \"two\")] public class B {}".Replace("\\", ""), contract));
|
||||
var diagnostics = await Compile("Consumer", "class Consumer {}", contract, a, b).WithAnalyzers(ImmutableArray.Create<DiagnosticAnalyzer>(new ShrinkCodeGenAnalyzer())).GetAnalyzerDiagnosticsAsync();
|
||||
Assert.That(diagnostics.Count(d => d.Id == "SHRINK002"), Is.EqualTo(1));
|
||||
}
|
||||
[Test] public async Task DefiniteKeyAndSubscriptionErrorsHaveStableCodes()
|
||||
{
|
||||
var compilation = Compile("Consumer", """
|
||||
namespace ShrinkContext { public class ShrinkKey<T> { public ShrinkKey(string package, string name, int major) {} } }
|
||||
namespace ShrinkEventBus { public interface IShrinkEvent {} public class ShrinkSubscribeAttribute : System.Attribute {} }
|
||||
class Consumer { object key = new ShrinkContext.ShrinkKey<int>("", "rage", 0); [ShrinkEventBus.ShrinkSubscribe] void On(int wrong) {} }
|
||||
""");
|
||||
var diagnostics = await compilation.WithAnalyzers(ImmutableArray.Create<DiagnosticAnalyzer>(new ShrinkCodeGenAnalyzer())).GetAnalyzerDiagnosticsAsync();
|
||||
Assert.That(diagnostics.Count(d => d.Id == "SHRINK004"), Is.EqualTo(2));
|
||||
Assert.That(diagnostics.Count(d => d.Id == "SHRINK001"), Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Text.Json;
|
||||
using NUnit.Framework;
|
||||
using ShrinkSDK.Inspect;
|
||||
using Index = ShrinkSDK.Inspect.Index;
|
||||
|
||||
public class IdentityTests
|
||||
{
|
||||
[TestCase("a", true)] [TestCase("b", false)] [TestCase("", false)]
|
||||
public void SnapshotNeedsMatchingVerifiedBuild(string build, bool accepted)
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
File.WriteAllText(path, "{\"BuildId\":\"a\",\"Relations\":[{\"From\":\"type:Request\",\"To\":\"M:Handler.Run\",\"Kind\":\"handler\",\"Scope\":\"server\"}]}");
|
||||
var index = new Index { BuildId = build };
|
||||
InspectCommand.AttachSnapshot(index, path);
|
||||
Assert.That(index.Relations.Count, Is.EqualTo(accepted ? 1 : 0));
|
||||
if (accepted) Assert.That(index.Relations[0].Evidence, Is.EqualTo("runtime-bound"));
|
||||
else Assert.That(index.Coverage, Is.Not.Empty);
|
||||
}
|
||||
finally { File.Delete(path); }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ChangedSourceInvalidatesRuntimeMerge()
|
||||
{
|
||||
var dir = Path.Combine(Path.GetTempPath(), "ShrinkInspectIdentity", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir);
|
||||
try
|
||||
{
|
||||
var source = Path.Combine(dir, "A.cs"); File.WriteAllText(source, "class A {}");
|
||||
var manifest = new CompilationManifest { Root = dir, BuildId = "a", Assemblies = [new() { Name = "A", Sources = [source] }], SourceTicks = new() { [source] = 0 } };
|
||||
var path = Path.Combine(dir, "compilation.json"); File.WriteAllText(path, JsonSerializer.Serialize(manifest));
|
||||
var index = new Index { Root = dir };
|
||||
await SemanticIndex.Build(index, path, null);
|
||||
Assert.That(index.BuildId, Is.Empty);
|
||||
Assert.That(index.Coverage.Any(c => c.Contains("Stale")));
|
||||
}
|
||||
finally { Directory.Delete(dir, true); }
|
||||
}
|
||||
[Test]
|
||||
public void MatchingSnapshotExposesBoundedStateItems()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
File.WriteAllText(path, """{"BuildId":"a","State":{"app":{"Fibers":[{"Name":"wait","Status":"Waiting"},{"Name":"ready","Status":"Active"}]}}} """);
|
||||
var index = new Index { BuildId = "a" }; InspectCommand.AttachSnapshot(index, path);
|
||||
Assert.That(index.RuntimeStates.Count, Is.EqualTo(2));
|
||||
Assert.That(index.RuntimeStates[0].Value.GetProperty("Name").GetString(), Is.EqualTo("wait"));
|
||||
}
|
||||
finally { File.Delete(path); }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using NUnit.Framework;
|
||||
using ShrinkSDK.Inspect;
|
||||
using Index = ShrinkSDK.Inspect.Index;
|
||||
|
||||
public class NavigationTests
|
||||
{
|
||||
private string _root = null!;
|
||||
[SetUp] public void SetUp() { _root = Path.Combine(Path.GetTempPath(), "ShrinkInspectTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_root); }
|
||||
[TearDown] public void TearDown() => Directory.Delete(_root, true);
|
||||
|
||||
[Test] public void ExactOverloadsAndVirtualCandidatesStayDistinct()
|
||||
{
|
||||
var index = Analyze("public class A { public void Start() { Call(1); } public void Call(int x) {} public void Call(string x) {} public virtual void Tick() {} public void Run() { Tick(); } }");
|
||||
Assert.That(index.Relations.Any(r => r.From == "M:A.Start" && r.To == "M:A.Call(System.Int32)" && r.Evidence == "static"));
|
||||
Assert.That(index.Relations.Any(r => r.To == "M:A.Call(System.String)"), Is.False);
|
||||
Assert.That(index.Relations.Any(r => r.To == "M:A.Tick" && r.Evidence == "candidate"));
|
||||
}
|
||||
|
||||
[Test] public void UnresolvedCallIsNotInventedAsStatic()
|
||||
{
|
||||
var index = Analyze("class A { void Run() { Missing.Go(); } }");
|
||||
Assert.That(index.Relations.Any(r => r.To == "Missing.Go" && r.Evidence == "unresolved"));
|
||||
Assert.That(index.Coverage, Is.Not.Empty);
|
||||
}
|
||||
|
||||
[Test] public void FrameworkEventEdgesBridgePublisherAndSubscriber()
|
||||
{
|
||||
var index = Analyze("""
|
||||
using System;
|
||||
namespace ShrinkEventBus { public class ShrinkSubscribeAttribute : Attribute {} public static class EventBus { public static void Post<T>(T value) {} } }
|
||||
public struct Killed {}
|
||||
public class Combat { public void Kill() { ShrinkEventBus.EventBus.Post(new Killed()); } }
|
||||
public class Rage { [ShrinkEventBus.ShrinkSubscribe] public void Gain(Killed value) {} }
|
||||
""");
|
||||
var edges = InspectCommand.Walk(index, "M:Combat.Kill", 2, null).ToArray();
|
||||
Assert.That(edges.Any(e => e.Kind == "publishes" && e.To == "type:Killed"));
|
||||
Assert.That(edges.Any(e => e.Kind == "subscribes" && e.To == "M:Rage.Gain(Killed)" && e.Evidence == "candidate"));
|
||||
}
|
||||
|
||||
[Test] public void OpcodeAndRouteUniquenessAreIndependent()
|
||||
{
|
||||
var index = Analyze("""
|
||||
using System;
|
||||
namespace ShrinkNetwork { public class ShrinkNetworkMessageAttribute : Attribute { public ShrinkNetworkMessageAttribute(int id, string route) {} } }
|
||||
[ShrinkNetwork.ShrinkNetworkMessage(1,"a")] class A {}
|
||||
[ShrinkNetwork.ShrinkNetworkMessage(1,"b")] class B {}
|
||||
[ShrinkNetwork.ShrinkNetworkMessage(2,"a")] class C {}
|
||||
""");
|
||||
SemanticIndex.CheckRelations(index);
|
||||
Assert.That(index.Findings.Any(f => f.Message.Contains("opcode:1")));
|
||||
Assert.That(index.Findings.Any(f => f.Message.Contains("route:a")));
|
||||
}
|
||||
|
||||
[Test] public void CapabilityMatchesDoNotImplyMissingAbilitiesAreAbsent()
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(_root, "Inventory"));
|
||||
File.WriteAllText(Path.Combine(_root, "Inventory", "inventory.asmdef"), "{\"name\":\"Inventory\"}");
|
||||
File.WriteAllText(Path.Combine(_root, "Inventory", "README.md"), """
|
||||
<!-- shrink:capabilities -->
|
||||
Capability: 配置物品容量
|
||||
Aliases: 背包 inventory 容量
|
||||
Limits: 不负责装备属性
|
||||
Extension: SlotOptions
|
||||
Evidence: Inventory.cs
|
||||
<!-- /shrink:capabilities -->
|
||||
""");
|
||||
Directory.CreateDirectory(Path.Combine(_root, "Combat"));
|
||||
File.WriteAllText(Path.Combine(_root, "Combat", "combat.asmdef"), "{\"name\":\"Combat\"}");
|
||||
var index = new Index { Root = _root }; ModuleCatalog.Load(index);
|
||||
Assert.That(ModuleCatalog.Search(index, "背包", null).Single().Limits, Is.EqualTo("不负责装备属性"));
|
||||
Assert.That(index.Modules.Single(m => m.Name == "Combat").SemanticDescriptionComplete, Is.False);
|
||||
Assert.That(ModuleCatalog.Search(index, "怒气", null), Is.Empty);
|
||||
}
|
||||
|
||||
private Index Analyze(string code)
|
||||
{
|
||||
var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!).Split(Path.PathSeparator).Select(p => MetadataReference.CreateFromFile(p));
|
||||
var compilation = CSharpCompilation.Create("Fixture", [CSharpSyntaxTree.ParseText(code, path: Path.Combine(_root, "Fixture.cs"))], references,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
var index = new Index { Root = _root }; SemanticIndex.Analyze(index, compilation); return index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><TargetFramework>net8.0</TargetFramework><IsPackable>false</IsPackable></PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Shrink.Inspect/Shrink.Inspect.csproj" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user