fix(IPC-36,IPC-37): guard secondary contracts
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using RetainedGalaxyReflection = ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy.GalaxyRepositoryReflection;
|
||||
using ServedGalaxyReflection = ZB.MOM.WW.GalaxyRepository.Grpc.GalaxyRepositoryReflection;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Tests.Contracts;
|
||||
|
||||
public sealed class GalaxyContractParityTests
|
||||
{
|
||||
/// <summary>Guards the retained client proto against drift from the descriptor served by the gateway.</summary>
|
||||
[Fact]
|
||||
public void RetainedClientDescriptor_MatchesServedGatewayDescriptor()
|
||||
{
|
||||
FileDescriptorProto retained = Normalize(RetainedGalaxyReflection.Descriptor);
|
||||
FileDescriptorProto served = Normalize(ServedGalaxyReflection.Descriptor);
|
||||
|
||||
AssertEquivalentDescriptors(retained, served);
|
||||
}
|
||||
|
||||
/// <summary>Proves descriptor drift diagnostics name the first differing protobuf message.</summary>
|
||||
[Fact]
|
||||
public void DescriptorComparison_WhenMessageDiffers_ReportsDifferingMessage()
|
||||
{
|
||||
FileDescriptorProto retained = Normalize(RetainedGalaxyReflection.Descriptor);
|
||||
FileDescriptorProto changed = retained.Clone();
|
||||
DescriptorProto changedMessage = changed.MessageType.First(message => message.Field.Count > 0);
|
||||
changedMessage.Field[0].Name += "_changed";
|
||||
|
||||
Xunit.Sdk.XunitException exception = Assert.Throws<Xunit.Sdk.XunitException>(
|
||||
() => AssertEquivalentDescriptors(retained, changed));
|
||||
|
||||
Assert.Contains(changedMessage.Name, exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static FileDescriptorProto Normalize(FileDescriptor descriptor)
|
||||
{
|
||||
FileDescriptorProto proto = descriptor.ToProto();
|
||||
proto.SourceCodeInfo = null;
|
||||
proto.Name = string.Empty;
|
||||
if (proto.Options is not null)
|
||||
{
|
||||
proto.Options.CsharpNamespace = string.Empty;
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
private static void AssertEquivalentDescriptors(FileDescriptorProto expected, FileDescriptorProto actual)
|
||||
{
|
||||
byte[] expectedBytes = SerializeDeterministically(expected);
|
||||
byte[] actualBytes = SerializeDeterministically(actual);
|
||||
if (expectedBytes.AsSpan().SequenceEqual(actualBytes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Galaxy Repository descriptors differ at {DescribeFirstDifference(expected, actual)}.");
|
||||
}
|
||||
|
||||
private static string DescribeFirstDifference(FileDescriptorProto expected, FileDescriptorProto actual)
|
||||
{
|
||||
int comparableMessageCount = Math.Min(expected.MessageType.Count, actual.MessageType.Count);
|
||||
for (int index = 0; index < comparableMessageCount; index++)
|
||||
{
|
||||
DescriptorProto expectedMessage = expected.MessageType[index];
|
||||
DescriptorProto actualMessage = actual.MessageType[index];
|
||||
if (!SerializeDeterministically(expectedMessage).AsSpan()
|
||||
.SequenceEqual(SerializeDeterministically(actualMessage)))
|
||||
{
|
||||
string messageName = string.IsNullOrEmpty(expectedMessage.Name)
|
||||
? actualMessage.Name
|
||||
: expectedMessage.Name;
|
||||
return $"message '{Qualify(expected.Package, messageName)}'";
|
||||
}
|
||||
}
|
||||
|
||||
if (expected.MessageType.Count != actual.MessageType.Count)
|
||||
{
|
||||
return "the top-level message list";
|
||||
}
|
||||
|
||||
return "the normalized file descriptor";
|
||||
}
|
||||
|
||||
private static string Qualify(string package, string symbol) =>
|
||||
string.IsNullOrEmpty(package) ? symbol : package + "." + symbol;
|
||||
|
||||
private static byte[] SerializeDeterministically(IMessage message)
|
||||
{
|
||||
using MemoryStream stream = new();
|
||||
using CodedOutputStream output = new(stream, leaveOpen: true)
|
||||
{
|
||||
Deterministic = true,
|
||||
};
|
||||
message.WriteTo(output);
|
||||
output.Flush();
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,16 @@ public sealed class WorkerProcessLauncherTests
|
||||
private const string PipeName = "mxaccess-gateway-123-session-1";
|
||||
private const string Nonce = "super-secret-nonce";
|
||||
|
||||
private static readonly string[] RequiredLaunchEnvironmentVariables =
|
||||
[
|
||||
WorkerLaunchEnvironment.WorkerNonce,
|
||||
WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds,
|
||||
WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds,
|
||||
WorkerLaunchEnvironment.EventQueueCapacity,
|
||||
WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds,
|
||||
WorkerLaunchEnvironment.MaxAlarmsPerFetch,
|
||||
];
|
||||
|
||||
/// <summary>Verifies that a valid worker executable starts with correct bootstrap arguments and nonce environment variable.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
@@ -38,33 +48,38 @@ public sealed class WorkerProcessLauncherTests
|
||||
Assert.Equal(
|
||||
["--session-id", SessionId, "--pipe-name", PipeName, "--protocol-version", "1"],
|
||||
processFactory.LastStartInfo.ArgumentList);
|
||||
Assert.Equal(Nonce, processFactory.LastStartInfo.Environment[WorkerProcessLauncher.WorkerNonceEnvironmentVariableName]);
|
||||
Assert.Equal(Nonce, processFactory.LastStartInfo.Environment[WorkerLaunchEnvironment.WorkerNonce]);
|
||||
Assert.Equal(
|
||||
"2000",
|
||||
processFactory.LastStartInfo.Environment[
|
||||
WorkerProcessLauncher.WorkerPipeConnectAttemptTimeoutEnvironmentVariableName]);
|
||||
WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds]);
|
||||
Assert.Equal(
|
||||
"1500",
|
||||
processFactory.LastStartInfo.Environment[
|
||||
WorkerProcessLauncher.WorkerWriteCompletionWaitEnvironmentVariableName]);
|
||||
WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds]);
|
||||
// The worker sizes its outbound event queue from the launch environment;
|
||||
// the queue has no drop policy, so this capacity is the session's burst
|
||||
// headroom rather than a throttle.
|
||||
Assert.Equal(
|
||||
"10000",
|
||||
processFactory.LastStartInfo.Environment[
|
||||
WorkerProcessLauncher.WorkerEventQueueCapacityEnvironmentVariableName]);
|
||||
WorkerLaunchEnvironment.EventQueueCapacity]);
|
||||
// MxGateway:Alarms defaults reach the worker's alarm poll loop and its
|
||||
// GetXmlCurrentAlarms2 cap (which is also its truncation threshold)
|
||||
// through the launch environment, not the command line.
|
||||
Assert.Equal(
|
||||
"500",
|
||||
processFactory.LastStartInfo.Environment[
|
||||
WorkerProcessLauncher.WorkerAlarmPollIntervalEnvironmentVariableName]);
|
||||
WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds]);
|
||||
Assert.Equal(
|
||||
"1024",
|
||||
processFactory.LastStartInfo.Environment[
|
||||
WorkerProcessLauncher.WorkerMaxAlarmsPerFetchEnvironmentVariableName]);
|
||||
WorkerLaunchEnvironment.MaxAlarmsPerFetch]);
|
||||
Assert.All(
|
||||
RequiredLaunchEnvironmentVariables,
|
||||
variable => Assert.True(
|
||||
processFactory.LastStartInfo.Environment.ContainsKey(variable),
|
||||
$"Worker launch environment is missing required variable '{variable}'."));
|
||||
Assert.DoesNotContain(Nonce, handle.CommandLine.ToString(), StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(Nonce, string.Join(" ", handle.CommandLine.Arguments), StringComparison.Ordinal);
|
||||
Assert.False(pipeReservation.DisposeCalled);
|
||||
@@ -94,7 +109,7 @@ public sealed class WorkerProcessLauncherTests
|
||||
Assert.Equal(
|
||||
"65536",
|
||||
processFactory.LastStartInfo.Environment[
|
||||
WorkerProcessLauncher.WorkerEventQueueCapacityEnvironmentVariableName]);
|
||||
WorkerLaunchEnvironment.EventQueueCapacity]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a failed startup probe kills and disposes the worker process.</summary>
|
||||
|
||||
Reference in New Issue
Block a user