Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.ScriptAnalysis.Tests/RoslynScriptCompilerTests.cs
T
Joseph Doherty a1186685a9 fix(scriptanalysis): mirror WaitAsync/WaitForAsync on CompileAttributeAccessor
Adds the four missing overloads (value + predicate × WaitAsync + WaitForAsync)
to CompileAttributeAccessor so template/call scripts that use Attributes.WaitAsync
or Attributes.WaitForAsync pass design-time Roslyn validation.  Covers both root
scope and composed/child scope (Children["x"].Attributes.WaitAsync) automatically
since CompileCompositionAccessor.Attributes already returns CompileAttributeAccessor.
2026-06-17 11:03:24 -04:00

93 lines
3.7 KiB
C#

using ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis.Tests;
/// <summary>
/// M3.1: parse + compile gate tests. The "representative real script" corpus is
/// the PRIMARY guard that <see cref="ScriptCompileSurface"/> faithfully mirrors
/// the runtime <c>ScriptGlobals</c> surface — if a member or signature drifts,
/// the corpus stops binding and this test fails.
/// </summary>
public class RoslynScriptCompilerTests
{
[Fact]
public void ParseDiagnostics_NonEmpty_ForSyntaxError()
{
Assert.NotEmpty(RoslynScriptCompiler.ParseDiagnostics("var x = ;"));
}
[Fact]
public void ParseDiagnostics_Empty_ForValidSyntax()
{
Assert.Empty(RoslynScriptCompiler.ParseDiagnostics("var x = 1;"));
}
[Fact]
public void Compile_NonEmpty_ForUndefinedSymbol()
{
var code = "var x = NoSuchThing.Foo();";
Assert.NotEmpty(RoslynScriptCompiler.Compile(code, typeof(ScriptCompileSurface)));
}
[Fact]
public void Compile_Empty_ForRepresentativeRealScript()
{
const string code = """
var temp = Attributes["Temperature"];
Attributes["Setpoint"] = 42;
var r = await ExternalSystem.Call("erp", "sync");
var op = await Database.CachedWrite("hist", "INSERT ...");
await Notify.To("ops").Send("subj", "msg");
var shared = await Scripts.CallShared("Helper");
var child = Children["Pump"].Attributes["Speed"];
// Widen coverage across the rest of the surface.
var attr = await Instance.GetAttribute("Temperature");
await Instance.SetAttribute("Setpoint", "43");
var track = await Instance.Tracking.Status(op);
var parentSpeed = Parent?.Attributes["Speed"];
var alarmName = Alarm?.Name;
var p = Parameters;
var ct = CancellationToken;
var status = await Notify.Status("notif-id");
var cachedCall = await ExternalSystem.CachedCall("erp", "ping");
var resolved = Attributes.Resolve("Temperature");
var conn = await Database.Connection("hist");
var scope = Scope;
""";
var diagnostics = RoslynScriptCompiler.Compile(code, typeof(ScriptCompileSurface));
Assert.Empty(diagnostics);
}
[Fact]
public void Compile_Empty_ForTriggerExpression()
{
const string expr =
"Attributes[\"Temp\"] != null && (int)(Children[\"P\"].Attributes[\"S\"] ?? 0) > 5";
var diagnostics = RoslynScriptCompiler.Compile(expr, typeof(TriggerCompileSurface));
Assert.Empty(diagnostics);
}
[Fact]
public void Compile_Empty_ForWaitAsyncAndWaitForAsync()
{
// Covers all four overloads: value + predicate for both WaitAsync and
// WaitForAsync, on both root scope and composed/child scope.
const string code = """
// Root scope — value overload
var matched = await Attributes.WaitAsync("Flag", true, System.TimeSpan.FromSeconds(5));
// Root scope — predicate overload with requireGoodQuality
var matched2 = await Attributes.WaitAsync("Flag", v => v != null, System.TimeSpan.FromSeconds(5), true);
// Root scope — WaitForAsync value overload
var r = await Attributes.WaitForAsync("Flag", true, System.TimeSpan.FromSeconds(5));
// Composed/child scope — value overload
var childMatched = await Children["LeftMESReceiver"].Attributes.WaitAsync("MoveInCompleteFlag", true, System.TimeSpan.FromSeconds(5));
""";
var diagnostics = RoslynScriptCompiler.Compile(code, typeof(ScriptCompileSurface));
Assert.Empty(diagnostics);
}
}