164 lines
8.0 KiB
C#
164 lines
8.0 KiB
C#
#nullable enable
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Mono.Cecil;
|
|
using ShrinkSDK.CodeGen;
|
|
|
|
if (args.Length == 2 && args[0] == "--marker")
|
|
{
|
|
using var exported = AssemblyDefinition.ReadAssembly(args[1]);
|
|
var marker = exported.CustomAttributes.SingleOrDefault(attribute =>
|
|
attribute.AttributeType.FullName == "ShrinkSDK.Runtime.ShrinkCodeGenWovenAttribute");
|
|
Require(marker != null, "exported assembly has no ShrinkSDK CodeGen marker");
|
|
Require(exported.MainModule.Types.Any(type => type.FullName ==
|
|
"ShrinkEventBus.Generated.ShrinkGeneratedStaticBindings"),
|
|
"exported assembly has no static EventBus bootstrap");
|
|
Console.WriteLine($"PASS exported assembly woven by {marker!.ConstructorArguments[0].Value}");
|
|
return;
|
|
}
|
|
|
|
if ((args.Length == 3 || args.Length == 4) && args[0] == "--resign")
|
|
{
|
|
var signedAssembly = Path.GetFullPath(args[1]);
|
|
var keepOutput = args.Length == 4;
|
|
var output = keepOutput
|
|
? Path.GetFullPath(args[3])
|
|
: Path.Combine(Path.GetTempPath(), "ShrinkSDK.CodeGen.Resigned." + Guid.NewGuid().ToString("N") + ".dll");
|
|
var outputPdb = Path.ChangeExtension(output, ".pdb");
|
|
try
|
|
{
|
|
var result = ShrinkAssemblyWeaver.Weave(signedAssembly, Path.ChangeExtension(signedAssembly, ".pdb"),
|
|
BuildReferences(Path.GetDirectoryName(signedAssembly)!), output, outputPdb,
|
|
ShrinkCodeGenPlatform.EngineNeutral, args[2]);
|
|
Require(result.Succeeded && result.Changed,
|
|
"signed assembly re-weave failed: " + string.Join(" | ", result.Diagnostics.Select(item => item.Message)));
|
|
using var resigned = AssemblyDefinition.ReadAssembly(output);
|
|
Require(resigned.Name.HasPublicKey, "re-signed output has no public key");
|
|
Console.WriteLine("PASS signed assembly woven and re-signed with explicit key");
|
|
}
|
|
finally
|
|
{
|
|
if (!keepOutput)
|
|
{
|
|
if (File.Exists(output)) File.Delete(output);
|
|
if (File.Exists(outputPdb)) File.Delete(outputPdb);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (args.Length != 1 || !File.Exists(args[0]))
|
|
throw new ArgumentException("Pass the unwoven CodeGenFixture assembly path.");
|
|
|
|
var sourceAssembly = Path.GetFullPath(args[0]);
|
|
var sourceDirectory = Path.GetDirectoryName(sourceAssembly)!;
|
|
var sourcePdb = Path.ChangeExtension(sourceAssembly, ".pdb");
|
|
var references = BuildReferences(sourceDirectory);
|
|
var root = Path.Combine(Path.GetTempPath(), "ShrinkSDK.CodeGen.Validation", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(root);
|
|
try
|
|
{
|
|
var woven = Path.Combine(root, "fixture.woven.dll");
|
|
var wovenPdb = Path.Combine(root, "fixture.woven.pdb");
|
|
var first = ShrinkAssemblyWeaver.Weave(sourceAssembly, sourcePdb, references, woven, wovenPdb);
|
|
Require(first.Succeeded && first.Changed,
|
|
"single weave did not change the assembly: " + string.Join(" | ", first.Diagnostics.Select(item => item.Message)));
|
|
Require(first.InstanceSubscribers == 1 && first.StaticSubscribers == 1 && first.RegistryEntries == 5,
|
|
$"unexpected generated counts: {first.InstanceSubscribers}/{first.StaticSubscribers}/{first.RegistryEntries}");
|
|
VerifyStructure(woven);
|
|
|
|
var second = ShrinkAssemblyWeaver.Weave(woven, wovenPdb, references.Append(woven),
|
|
Path.Combine(root, "twice.dll"), Path.Combine(root, "twice.pdb"));
|
|
Require(second.Succeeded && !second.Changed, "repeated weave was not idempotent");
|
|
|
|
var corruptPdb = Path.Combine(root, "corrupt.pdb");
|
|
File.WriteAllText(corruptPdb, "not a portable pdb");
|
|
var corrupt = ShrinkAssemblyWeaver.Weave(sourceAssembly, corruptPdb, references,
|
|
Path.Combine(root, "corrupt.dll"), Path.Combine(root, "corrupt.out.pdb"));
|
|
Require(!corrupt.Succeeded && !File.Exists(Path.Combine(root, "corrupt.dll")),
|
|
"damaged PDB was not rejected atomically");
|
|
|
|
var missing = ShrinkAssemblyWeaver.Weave(sourceAssembly, null, Array.Empty<string>(),
|
|
Path.Combine(root, "missing.dll"), null);
|
|
Require(!missing.Succeeded, "missing dependencies were not rejected");
|
|
|
|
var signedInput = Path.Combine(root, "signed.dll");
|
|
using (var assembly = AssemblyDefinition.ReadAssembly(sourceAssembly))
|
|
{
|
|
assembly.Name.PublicKey = Enumerable.Range(0, 160).Select(value => (byte)(value + 1)).ToArray();
|
|
assembly.Name.Attributes |= AssemblyAttributes.PublicKey;
|
|
assembly.Write(signedInput);
|
|
}
|
|
var signed = ShrinkAssemblyWeaver.Weave(signedInput, null, references,
|
|
Path.Combine(root, "signed.out.dll"), null);
|
|
Require(!signed.Succeeded && signed.Diagnostics.Any(item => item.Message.Contains("Signed assemblies")),
|
|
"signed assembly was not rejected");
|
|
|
|
var parallel = Enumerable.Range(0, 4).Select(index => Task.Run(() =>
|
|
{
|
|
var output = Path.Combine(root, $"parallel-{index}.dll");
|
|
var outputPdb = Path.Combine(root, $"parallel-{index}.pdb");
|
|
return ShrinkAssemblyWeaver.Weave(sourceAssembly, sourcePdb, references, output, outputPdb);
|
|
})).ToArray();
|
|
await Task.WhenAll(parallel);
|
|
Require(parallel.All(task => task.Result.Succeeded && task.Result.Changed),
|
|
"parallel weaving of independent assemblies failed");
|
|
|
|
Console.WriteLine("PASS Cecil structure + single/repeat/parallel weave + corrupt PDB/signed/missing dependency guards");
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(root, true);
|
|
}
|
|
|
|
static void VerifyStructure(string path)
|
|
{
|
|
using var assembly = AssemblyDefinition.ReadAssembly(path);
|
|
var module = assembly.MainModule;
|
|
var instance = module.Types.Single(type => type.Name == "InstanceSubscriber");
|
|
Require(instance.Interfaces.Any(item => item.InterfaceType.FullName == "ShrinkEventBus.IShrinkGeneratedSubscriber"),
|
|
"generated subscriber interface is missing");
|
|
Require(instance.Methods.Any(method => method.Name == "ShrinkEventBus.IShrinkGeneratedSubscriber.AttachGenerated"),
|
|
"AttachGenerated is missing");
|
|
Require(module.Types.Any(type => type.FullName == "ShrinkEventBus.Generated.ShrinkGeneratedStaticBindings"),
|
|
"static binding bootstrap is missing");
|
|
Require(module.Types.Any(type => type.FullName ==
|
|
"ShrinkNetwork.Integration.Generated.ShrinkGeneratedNetworkEventBindings"),
|
|
"Network/EventBus generated bootstrap is missing");
|
|
var moduleInitializer = module.Types.Single(type => type.Name == "<Module>").Methods.Single(method => method.Name == ".cctor");
|
|
Require(moduleInitializer.Body.Instructions.Any(instruction => instruction.Operand is MethodReference method && method.Name == "Register"),
|
|
"module initializer does not call static registration");
|
|
var attributes = assembly.CustomAttributes.Select(attribute => attribute.AttributeType.FullName).ToArray();
|
|
foreach (var expected in new[]
|
|
{
|
|
"ShrinkSDK.Runtime.ShrinkCodeGenWovenAttribute",
|
|
"ShrinkCommand.ShrinkCommandStaticRegistryAttribute",
|
|
"ShrinkNetwork.ShrinkNetworkMessageRegistryAttribute",
|
|
"ShrinkNetwork.ShrinkNetworkStaticSubscriberRegistryAttribute"
|
|
})
|
|
Require(attributes.Contains(expected), $"assembly registry is missing: {expected}");
|
|
}
|
|
|
|
static void Require(bool condition, string message)
|
|
{
|
|
if (!condition) throw new InvalidOperationException(message);
|
|
}
|
|
|
|
static string[] BuildReferences(string sourceDirectory)
|
|
{
|
|
var referencePackRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
|
".nuget", "packages", "microsoft.netcore.app.ref");
|
|
var referencePack = Directory.GetDirectories(referencePackRoot)
|
|
.Select(path => Path.Combine(path, "ref", "net8.0"))
|
|
.Where(Directory.Exists)
|
|
.OrderByDescending(path => path, StringComparer.OrdinalIgnoreCase)
|
|
.First();
|
|
return Directory.GetFiles(sourceDirectory, "*.dll")
|
|
.Concat(Directory.GetFiles(referencePack, "*.dll"))
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToArray();
|
|
}
|