a1186685a9
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.
93 lines
3.7 KiB
C#
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);
|
|
}
|
|
}
|