review(Analyzers): add trip-coverage for async guarded-interface methods
Re-review at 7286d320. -008: 5 regression tests for Unsubscribe/UnsubscribeAlarms/
Acknowledge/ReadEvents trip + suppression paths (analyzer source already correct).
Surfaced cross-module: Runtime DriverInstanceActor.HandleWriteAsync calls WriteAsync
directly (tracked for Runtime).
This commit is contained in:
+123
@@ -833,6 +833,129 @@ namespace ZB.MOM.WW.OtOpcUa.Server {
|
||||
diags.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Analyzers-008 — missing trip + pass-clean tests for ISubscribable.UnsubscribeAsync,
|
||||
// IAlarmSource.UnsubscribeAlarmsAsync, IAlarmSource.AcknowledgeAsync, and the wrapped
|
||||
// (pass-clean) path for IHistoryProvider.ReadEventsAsync (DIM).
|
||||
// =======================================================================
|
||||
|
||||
/// <summary>Verifies that a direct UnsubscribeAsync call trips the diagnostic.</summary>
|
||||
[Fact]
|
||||
public async Task Direct_UnsubscribeAsync_Call_TripsDiagnostic()
|
||||
{
|
||||
const string userSrc = """
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Server {
|
||||
public sealed class BadUnsubscribe {
|
||||
public async Task DoIt(ISubscribable driver, ISubscriptionHandle handle) {
|
||||
await driver.UnsubscribeAsync(handle, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
var diags = await Compile(userSrc);
|
||||
diags.Length.ShouldBe(1);
|
||||
diags[0].GetMessage().ShouldContain("UnsubscribeAsync");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a wrapped UnsubscribeAsync call inside a CapabilityInvoker lambda passes cleanly.</summary>
|
||||
[Fact]
|
||||
public async Task Wrapped_UnsubscribeAsync_InsideCapabilityInvokerLambda_PassesCleanly()
|
||||
{
|
||||
const string userSrc = """
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Server {
|
||||
public sealed class GoodUnsubscribe {
|
||||
public async Task DoIt(ISubscribable driver, ISubscriptionHandle handle, CapabilityInvoker invoker) {
|
||||
await invoker.ExecuteAsync(DriverCapability.AlarmSubscribe, "h1",
|
||||
async ct => await driver.UnsubscribeAsync(handle, ct),
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
var diags = await Compile(userSrc);
|
||||
diags.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a direct UnsubscribeAlarmsAsync call trips the diagnostic.</summary>
|
||||
[Fact]
|
||||
public async Task Direct_UnsubscribeAlarmsAsync_Call_TripsDiagnostic()
|
||||
{
|
||||
const string userSrc = """
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Server {
|
||||
public sealed class BadAlarmUnsubscribe {
|
||||
public async Task DoIt(IAlarmSource source, IAlarmSubscriptionHandle handle) {
|
||||
await source.UnsubscribeAlarmsAsync(handle, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
var diags = await Compile(userSrc);
|
||||
diags.Length.ShouldBe(1);
|
||||
diags[0].GetMessage().ShouldContain("UnsubscribeAlarmsAsync");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a direct AcknowledgeAsync call trips the diagnostic.</summary>
|
||||
[Fact]
|
||||
public async Task Direct_AcknowledgeAsync_Call_TripsDiagnostic()
|
||||
{
|
||||
const string userSrc = """
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Server {
|
||||
public sealed class BadAcknowledge {
|
||||
public async Task DoIt(IAlarmSource source) {
|
||||
await source.AcknowledgeAsync(new List<string>(), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
var diags = await Compile(userSrc);
|
||||
diags.Length.ShouldBe(1);
|
||||
diags[0].GetMessage().ShouldContain("AcknowledgeAsync");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a wrapped ReadEventsAsync DIM call inside a CapabilityInvoker lambda passes cleanly.</summary>
|
||||
[Fact]
|
||||
public async Task Wrapped_ReadEventsAsync_DIM_InsideCapabilityInvokerLambda_PassesCleanly()
|
||||
{
|
||||
// ReadEventsAsync is a DIM — the wrapped path must not trip the diagnostic.
|
||||
const string userSrc = """
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Server {
|
||||
public sealed class GoodHistoryEvents {
|
||||
public async Task DoIt(IHistoryProvider provider, CapabilityInvoker invoker) {
|
||||
_ = await invoker.ExecuteAsync(DriverCapability.Read, "h1",
|
||||
async ct => await provider.ReadEventsAsync(null, DateTime.MinValue, DateTime.MaxValue, 0, ct),
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
var diags = await Compile(userSrc);
|
||||
diags.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
private static async Task<ImmutableArray<Diagnostic>> CompileWithoutStubs(string userSource)
|
||||
{
|
||||
var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(userSource) };
|
||||
|
||||
Reference in New Issue
Block a user