Compare commits

..

3 Commits

Author SHA1 Message Date
Joseph Doherty cd92048f4e Regenerate stale Java client protobuf code
The checked-in generated Java sources under clients/java/src/main/generated/
were out of sync with both the .proto contracts and the configured
protobuf 4.33.1 toolchain: they were missing the alarm command kinds
(MX_COMMAND_KIND_SUBSCRIBE_ALARMS..ACKNOWLEDGE_ALARM_BY_NAME, 25-29), the
alarm/galaxy message additions, and the protobuf 4.x generated-code layout.
Regenerated via `gradle generateProto`; `gradle test` passes against the
refreshed sources. No hand edits — pure protoc/protoc-gen-grpc-java output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:21:13 -04:00
Joseph Doherty 964b40dcbc Fix stale WorkerProjectReferenceTests MXAccess-interop assertion
MxAccessInteropReference_ExistsOnlyInWorkerProject asserted the MXAccess COM
interop was referenced only by MxGateway.Worker. The worker test project now
legitimately references ArchestrA.MxAccess and Interop.WNWRAPCONSUMERLib so it
can exercise the COM-facing worker code (WnWrapAlarmConsumer, the alarm
tests). Renamed to ..._ExistsOnlyInWorkerAndWorkerTestProjects, updated the
assertion to expect both projects, and made it order-independent. The
architecture invariant the test protects — the gateway/contracts never
reference MXAccess COM — still holds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:19:32 -04:00
Joseph Doherty bb5603b7ec Fix flaky GalaxyHierarchyRefreshServiceTests timing race
ExecuteAsync_WhenFirstRefreshThrowsNonCancellationException_DoesNotFault
BackgroundService cancelled the service immediately after StartAsync, so
under parallel load the first RefreshAsync could be skipped (RefreshCallCount
0) and `await executeTask` rethrew TaskCanceledException before the IsFaulted
assertion. The test now waits for a TaskCompletionSource signal that the
first refresh was attempted before cancelling, and uses Task.WhenAny so a
Canceled ExecuteTask does not rethrow. Confirmed stable across full-suite
runs (408/408).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:14:47 -04:00
5 changed files with 18005 additions and 402 deletions
@@ -139,6 +139,68 @@ public final class MxAccessGatewayGrpc {
return getStreamEventsMethod; return getStreamEventsMethod;
} }
private static volatile io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest,
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply> getAcknowledgeAlarmMethod;
@io.grpc.stub.annotations.RpcMethod(
fullMethodName = SERVICE_NAME + '/' + "AcknowledgeAlarm",
requestType = mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest.class,
responseType = mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply.class,
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
public static io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest,
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply> getAcknowledgeAlarmMethod() {
io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest, mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply> getAcknowledgeAlarmMethod;
if ((getAcknowledgeAlarmMethod = MxAccessGatewayGrpc.getAcknowledgeAlarmMethod) == null) {
synchronized (MxAccessGatewayGrpc.class) {
if ((getAcknowledgeAlarmMethod = MxAccessGatewayGrpc.getAcknowledgeAlarmMethod) == null) {
MxAccessGatewayGrpc.getAcknowledgeAlarmMethod = getAcknowledgeAlarmMethod =
io.grpc.MethodDescriptor.<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest, mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply>newBuilder()
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "AcknowledgeAlarm"))
.setSampledToLocalTracing(true)
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest.getDefaultInstance()))
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply.getDefaultInstance()))
.setSchemaDescriptor(new MxAccessGatewayMethodDescriptorSupplier("AcknowledgeAlarm"))
.build();
}
}
}
return getAcknowledgeAlarmMethod;
}
private static volatile io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest,
mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> getQueryActiveAlarmsMethod;
@io.grpc.stub.annotations.RpcMethod(
fullMethodName = SERVICE_NAME + '/' + "QueryActiveAlarms",
requestType = mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest.class,
responseType = mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot.class,
methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
public static io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest,
mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> getQueryActiveAlarmsMethod() {
io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest, mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> getQueryActiveAlarmsMethod;
if ((getQueryActiveAlarmsMethod = MxAccessGatewayGrpc.getQueryActiveAlarmsMethod) == null) {
synchronized (MxAccessGatewayGrpc.class) {
if ((getQueryActiveAlarmsMethod = MxAccessGatewayGrpc.getQueryActiveAlarmsMethod) == null) {
MxAccessGatewayGrpc.getQueryActiveAlarmsMethod = getQueryActiveAlarmsMethod =
io.grpc.MethodDescriptor.<mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest, mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot>newBuilder()
.setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "QueryActiveAlarms"))
.setSampledToLocalTracing(true)
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest.getDefaultInstance()))
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot.getDefaultInstance()))
.setSchemaDescriptor(new MxAccessGatewayMethodDescriptorSupplier("QueryActiveAlarms"))
.build();
}
}
}
return getQueryActiveAlarmsMethod;
}
/** /**
* Creates a new async stub that supports all call types for the service * Creates a new async stub that supports all call types for the service
*/ */
@@ -232,6 +294,20 @@ public final class MxAccessGatewayGrpc {
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxEvent> responseObserver) { io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxEvent> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getStreamEventsMethod(), responseObserver); io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getStreamEventsMethod(), responseObserver);
} }
/**
*/
default void acknowledgeAlarm(mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getAcknowledgeAlarmMethod(), responseObserver);
}
/**
*/
default void queryActiveAlarms(mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getQueryActiveAlarmsMethod(), responseObserver);
}
} }
/** /**
@@ -298,6 +374,22 @@ public final class MxAccessGatewayGrpc {
io.grpc.stub.ClientCalls.asyncServerStreamingCall( io.grpc.stub.ClientCalls.asyncServerStreamingCall(
getChannel().newCall(getStreamEventsMethod(), getCallOptions()), request, responseObserver); getChannel().newCall(getStreamEventsMethod(), getCallOptions()), request, responseObserver);
} }
/**
*/
public void acknowledgeAlarm(mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply> responseObserver) {
io.grpc.stub.ClientCalls.asyncUnaryCall(
getChannel().newCall(getAcknowledgeAlarmMethod(), getCallOptions()), request, responseObserver);
}
/**
*/
public void queryActiveAlarms(mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> responseObserver) {
io.grpc.stub.ClientCalls.asyncServerStreamingCall(
getChannel().newCall(getQueryActiveAlarmsMethod(), getCallOptions()), request, responseObserver);
}
} }
/** /**
@@ -348,6 +440,22 @@ public final class MxAccessGatewayGrpc {
return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall(
getChannel(), getStreamEventsMethod(), getCallOptions(), request); getChannel(), getStreamEventsMethod(), getCallOptions(), request);
} }
/**
*/
public mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply acknowledgeAlarm(mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest request) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
getChannel(), getAcknowledgeAlarmMethod(), getCallOptions(), request);
}
/**
*/
@io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918")
public io.grpc.stub.BlockingClientCall<?, mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot>
queryActiveAlarms(mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest request) {
return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall(
getChannel(), getQueryActiveAlarmsMethod(), getCallOptions(), request);
}
} }
/** /**
@@ -397,6 +505,21 @@ public final class MxAccessGatewayGrpc {
return io.grpc.stub.ClientCalls.blockingServerStreamingCall( return io.grpc.stub.ClientCalls.blockingServerStreamingCall(
getChannel(), getStreamEventsMethod(), getCallOptions(), request); getChannel(), getStreamEventsMethod(), getCallOptions(), request);
} }
/**
*/
public mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply acknowledgeAlarm(mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getAcknowledgeAlarmMethod(), getCallOptions(), request);
}
/**
*/
public java.util.Iterator<mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> queryActiveAlarms(
mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest request) {
return io.grpc.stub.ClientCalls.blockingServerStreamingCall(
getChannel(), getQueryActiveAlarmsMethod(), getCallOptions(), request);
}
} }
/** /**
@@ -441,12 +564,22 @@ public final class MxAccessGatewayGrpc {
return io.grpc.stub.ClientCalls.futureUnaryCall( return io.grpc.stub.ClientCalls.futureUnaryCall(
getChannel().newCall(getInvokeMethod(), getCallOptions()), request); getChannel().newCall(getInvokeMethod(), getCallOptions()), request);
} }
/**
*/
public com.google.common.util.concurrent.ListenableFuture<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply> acknowledgeAlarm(
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest request) {
return io.grpc.stub.ClientCalls.futureUnaryCall(
getChannel().newCall(getAcknowledgeAlarmMethod(), getCallOptions()), request);
}
} }
private static final int METHODID_OPEN_SESSION = 0; private static final int METHODID_OPEN_SESSION = 0;
private static final int METHODID_CLOSE_SESSION = 1; private static final int METHODID_CLOSE_SESSION = 1;
private static final int METHODID_INVOKE = 2; private static final int METHODID_INVOKE = 2;
private static final int METHODID_STREAM_EVENTS = 3; private static final int METHODID_STREAM_EVENTS = 3;
private static final int METHODID_ACKNOWLEDGE_ALARM = 4;
private static final int METHODID_QUERY_ACTIVE_ALARMS = 5;
private static final class MethodHandlers<Req, Resp> implements private static final class MethodHandlers<Req, Resp> implements
io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>, io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
@@ -481,6 +614,14 @@ public final class MxAccessGatewayGrpc {
serviceImpl.streamEvents((mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest) request, serviceImpl.streamEvents((mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxEvent>) responseObserver); (io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxEvent>) responseObserver);
break; break;
case METHODID_ACKNOWLEDGE_ALARM:
serviceImpl.acknowledgeAlarm((mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply>) responseObserver);
break;
case METHODID_QUERY_ACTIVE_ALARMS:
serviceImpl.queryActiveAlarms((mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot>) responseObserver);
break;
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -527,6 +668,20 @@ public final class MxAccessGatewayGrpc {
mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest, mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest,
mxaccess_gateway.v1.MxaccessGateway.MxEvent>( mxaccess_gateway.v1.MxaccessGateway.MxEvent>(
service, METHODID_STREAM_EVENTS))) service, METHODID_STREAM_EVENTS)))
.addMethod(
getAcknowledgeAlarmMethod(),
io.grpc.stub.ServerCalls.asyncUnaryCall(
new MethodHandlers<
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest,
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply>(
service, METHODID_ACKNOWLEDGE_ALARM)))
.addMethod(
getQueryActiveAlarmsMethod(),
io.grpc.stub.ServerCalls.asyncServerStreamingCall(
new MethodHandlers<
mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest,
mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot>(
service, METHODID_QUERY_ACTIVE_ALARMS)))
.build(); .build();
} }
@@ -579,6 +734,8 @@ public final class MxAccessGatewayGrpc {
.addMethod(getCloseSessionMethod()) .addMethod(getCloseSessionMethod())
.addMethod(getInvokeMethod()) .addMethod(getInvokeMethod())
.addMethod(getStreamEventsMethod()) .addMethod(getStreamEventsMethod())
.addMethod(getAcknowledgeAlarmMethod())
.addMethod(getQueryActiveAlarmsMethod())
.build(); .build();
} }
} }
File diff suppressed because it is too large Load Diff
@@ -22,13 +22,23 @@ public sealed class GalaxyHierarchyRefreshServiceTests
using CancellationTokenSource cts = new(); using CancellationTokenSource cts = new();
await service.StartAsync(cts.Token); await service.StartAsync(cts.Token);
// Wait until the first RefreshAsync has actually been attempted (and
// thrown) before cancelling, so cancellation cannot race ahead of the
// first-load path under test — this is what made the test flaky under
// parallel load.
await cache.FirstRefreshAttempted.WaitAsync(TimeSpan.FromSeconds(10));
await cts.CancelAsync(); await cts.CancelAsync();
// The background loop must have stopped cleanly: ExecuteTask completes // The background loop must have stopped cleanly: ExecuteTask reaches a
// (RanToCompletion or Canceled) rather than faulting on the first refresh. // terminal state that is not Faulted (RanToCompletion or Canceled)
// rather than faulting on the first refresh. WhenAny is used so a
// Canceled task does not rethrow before the IsFaulted assertion.
Task? executeTask = service.ExecuteTask; Task? executeTask = service.ExecuteTask;
Assert.NotNull(executeTask); Assert.NotNull(executeTask);
await executeTask; Task completed = await Task.WhenAny(executeTask, Task.Delay(TimeSpan.FromSeconds(10)));
Assert.Same(executeTask, completed);
Assert.False(executeTask.IsFaulted); Assert.False(executeTask.IsFaulted);
Assert.Equal(1, cache.RefreshCallCount); Assert.Equal(1, cache.RefreshCallCount);
@@ -49,13 +59,20 @@ public sealed class GalaxyHierarchyRefreshServiceTests
private sealed class ThrowingCache(Exception toThrow) : IGalaxyHierarchyCache private sealed class ThrowingCache(Exception toThrow) : IGalaxyHierarchyCache
{ {
private readonly TaskCompletionSource firstRefreshAttempted =
new(TaskCreationOptions.RunContinuationsAsynchronously);
public int RefreshCallCount { get; private set; } public int RefreshCallCount { get; private set; }
/// <summary>Completes once <see cref="RefreshAsync"/> has been invoked at least once.</summary>
public Task FirstRefreshAttempted => firstRefreshAttempted.Task;
public GalaxyHierarchyCacheEntry Current => GalaxyHierarchyCacheEntry.Empty; public GalaxyHierarchyCacheEntry Current => GalaxyHierarchyCacheEntry.Empty;
public Task RefreshAsync(CancellationToken cancellationToken) public Task RefreshAsync(CancellationToken cancellationToken)
{ {
RefreshCallCount++; RefreshCallCount++;
firstRefreshAttempted.TrySetResult();
throw toThrow; throw toThrow;
} }
@@ -29,9 +29,16 @@ public sealed class WorkerProjectReferenceTests
Assert.Equal("x86", ElementValue(project, "PlatformTarget")); Assert.Equal("x86", ElementValue(project, "PlatformTarget"));
} }
/// <summary>Verifies that MXAccess interop reference exists only in the worker project.</summary> /// <summary>
/// Verifies that the MXAccess COM interop is referenced only by the
/// worker project and its test project — never by the gateway server
/// or the contracts project. The gateway must never load MXAccess COM
/// directly (see <c>gateway.md</c>); the worker test project
/// legitimately references the interop so it can exercise the
/// COM-facing worker code (e.g. <c>WnWrapAlarmConsumer</c>).
/// </summary>
[Fact] [Fact]
public void MxAccessInteropReference_ExistsOnlyInWorkerProject() public void MxAccessInteropReference_ExistsOnlyInWorkerAndWorkerTestProjects()
{ {
DirectoryInfo repositoryRoot = FindRepositoryRoot(); DirectoryInfo repositoryRoot = FindRepositoryRoot();
string[] projectFiles = Directory.GetFiles(repositoryRoot.FullName, "*.csproj", SearchOption.AllDirectories) string[] projectFiles = Directory.GetFiles(repositoryRoot.FullName, "*.csproj", SearchOption.AllDirectories)
@@ -42,9 +49,12 @@ public sealed class WorkerProjectReferenceTests
IReadOnlyList<string> projectsWithMxAccessReference = projectFiles IReadOnlyList<string> projectsWithMxAccessReference = projectFiles
.Where(ProjectReferencesMxAccess) .Where(ProjectReferencesMxAccess)
.Select(path => Path.GetFileNameWithoutExtension(path)) .Select(path => Path.GetFileNameWithoutExtension(path))
.OrderBy(name => name, StringComparer.Ordinal)
.ToArray(); .ToArray();
Assert.Equal(["MxGateway.Worker"], projectsWithMxAccessReference); Assert.Equal(
["MxGateway.Worker", "MxGateway.Worker.Tests"],
projectsWithMxAccessReference);
} }
private static bool ProjectReferencesMxAccess(string projectPath) private static bool ProjectReferencesMxAccess(string projectPath)