513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
103 lines
5.6 KiB
C#
103 lines
5.6 KiB
C#
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using Google.Protobuf.Reflection;
|
|
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Grpc;
|
|
|
|
/// <summary>Compiles source descriptors and compares the complete vendored wire contract.</summary>
|
|
public class ProtoDescriptorContractTests
|
|
{
|
|
/// <summary>Verifies that the source descriptors match the vendored messages and services.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task XC4_SourceDescriptorsMatchVendoredMessagesAndServices()
|
|
{
|
|
var compiled = await CompileAsync();
|
|
foreach (var generated in Generated())
|
|
Assert.Equal(CanonicalGenerated(generated), compiled.File.Single(f => f.Name == generated.Name));
|
|
}
|
|
|
|
/// <summary>Verifies that the guard detects drift in names, types and services.</summary>
|
|
/// <param name="before">The descriptor text before the change.</param>
|
|
/// <param name="after">The descriptor text after the change.</param>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Theory]
|
|
[InlineData("string site_identifier = 1;", "string renamed_site = 1;")]
|
|
[InlineData("string site_identifier = 1;", "bytes site_identifier = 1;")]
|
|
[InlineData("rpc Heartbeat(HeartbeatDto)", "rpc RenamedHeartbeat(HeartbeatDto)")]
|
|
public async Task GuardDetectsNameTypeAndServiceDrift(string before, string after)
|
|
{
|
|
var compiled = await CompileAsync(before, after);
|
|
var generated = CentralControlReflection.Descriptor;
|
|
Assert.NotEqual(CanonicalGenerated(generated), compiled.File.Single(f => f.Name == generated.Name));
|
|
}
|
|
|
|
// C# embeds descriptors with implicit JSON names omitted; descriptor_set_out expands
|
|
// them. Compare effective names so explicit json_name drift remains detectable too.
|
|
private static FileDescriptorProto CanonicalGenerated(FileDescriptor file)
|
|
{
|
|
var proto = file.ToProto();
|
|
foreach (var message in proto.MessageType)
|
|
FillJsonNames(message, file.MessageTypes.Single(m => m.Name == message.Name));
|
|
return proto;
|
|
}
|
|
|
|
private static void FillJsonNames(DescriptorProto proto, MessageDescriptor descriptor)
|
|
{
|
|
foreach (var field in proto.Field)
|
|
field.JsonName = descriptor.FindFieldByNumber(field.Number).JsonName;
|
|
foreach (var nested in proto.NestedType)
|
|
FillJsonNames(nested, descriptor.NestedTypes.Single(m => m.Name == nested.Name));
|
|
}
|
|
|
|
private static FileDescriptor[] Generated() =>
|
|
[SitestreamReflection.Descriptor, SiteCommandReflection.Descriptor, CentralControlReflection.Descriptor];
|
|
|
|
private static async Task<FileDescriptorSet> CompileAsync(string? before = null, string? after = null)
|
|
{
|
|
var tools = typeof(ProtoDescriptorContractTests).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
|
|
.Single(a => a.Key == "GrpcToolsRoot").Value!;
|
|
var platform = OperatingSystem.IsMacOS() ? "macosx_x64"
|
|
: OperatingSystem.IsWindows() ? (RuntimeInformation.ProcessArchitecture == Architecture.X86 ? "windows_x86" : "windows_x64")
|
|
: RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "linux_arm64"
|
|
: RuntimeInformation.ProcessArchitecture == Architecture.X86 ? "linux_x86" : "linux_x64";
|
|
var executable = Path.Combine(tools, "tools", platform, OperatingSystem.IsWindows() ? "protoc.exe" : "protoc");
|
|
var directory = Path.Combine(Path.GetTempPath(), "scadabridge-proto-contract-" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(Path.Combine(directory, "Protos"));
|
|
try
|
|
{
|
|
foreach (var file in Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "ProtoSources", "Protos"), "*.proto"))
|
|
{
|
|
var source = await File.ReadAllTextAsync(file);
|
|
if (before is not null && Path.GetFileName(file) == "central_control.proto")
|
|
{
|
|
Assert.Contains(before, source);
|
|
source = source.Replace(before, after, StringComparison.Ordinal);
|
|
}
|
|
await File.WriteAllTextAsync(Path.Combine(directory, "Protos", Path.GetFileName(file)), source);
|
|
}
|
|
var output = Path.Combine(directory, "descriptor.bin");
|
|
var start = new ProcessStartInfo(executable)
|
|
{
|
|
WorkingDirectory = directory, RedirectStandardError = true, RedirectStandardOutput = true,
|
|
UseShellExecute = false, CreateNoWindow = true
|
|
};
|
|
start.ArgumentList.Add("--proto_path=" + directory);
|
|
start.ArgumentList.Add("--proto_path=" + Path.Combine(tools, "build", "native", "include"));
|
|
start.ArgumentList.Add("--descriptor_set_out=" + output);
|
|
foreach (var file in Directory.GetFiles(Path.Combine(directory, "Protos"), "*.proto").Order())
|
|
start.ArgumentList.Add("Protos/" + Path.GetFileName(file));
|
|
using var process = Process.Start(start)!;
|
|
var stderr = process.StandardError.ReadToEndAsync();
|
|
var stdout = process.StandardOutput.ReadToEndAsync();
|
|
try { await process.WaitForExitAsync().WaitAsync(TimeSpan.FromSeconds(30)); }
|
|
catch { process.Kill(entireProcessTree: true); await process.WaitForExitAsync(); throw; }
|
|
Assert.True(process.ExitCode == 0, await stderr + await stdout);
|
|
return FileDescriptorSet.Parser.ParseFrom(await File.ReadAllBytesAsync(output));
|
|
}
|
|
finally { Directory.Delete(directory, recursive: true); }
|
|
}
|
|
}
|