using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;
///
/// lmxopcua#779 — the stack-guard rewrite () must not change what a script
/// means: every lambda / local-function shape still binds to the same overload and returns the same value, and
/// bounded recursion keeps working. The process-level proof that unbounded recursion no longer kills the host is
/// ; this class runs only scripts that terminate.
///
public sealed class ScriptStackGuardInjectionTests
{
private static object? Run(string source)
{
using var evaluator = ScriptEvaluator.Compile(source);
return evaluator.RunAsync(new FakeScriptContext(), TestContext.Current.CancellationToken).Result;
}
[Theory]
[InlineData("int F(int n) => n <= 0 ? 0 : F(n - 1) + 1; return F(500);", 500)]
[InlineData("int F(int n) { if (n <= 0) return 0; return F(n - 1) + 1; } return F(500);", 500)]
[InlineData("System.Func f = null!; f = n => n <= 0 ? 0 : f(n - 1) + 1; return f(500);", 500)]
[InlineData("System.Func f = null!; f = delegate (int n) { return n <= 0 ? 0 : f(n - 1) + 1; }; return f(500);", 500)]
public void Bounded_recursion_still_returns_its_value(string source, int expected) =>
Run(source).ShouldBe(expected);
[Theory]
// Expression lambdas through LINQ: Select / Where / Sum / Aggregate overloads resolve as before.
[InlineData("return System.Linq.Enumerable.Sum(System.Linq.Enumerable.Select(new[] { 1, 2, 3 }, x => x * 2));", 12)]
[InlineData("return System.Linq.Enumerable.Count(new[] { 1, 2, 3, 4 }, x => x % 2 == 0);", 2)]
[InlineData("return System.Linq.Enumerable.Aggregate(new[] { 1, 2, 3 }, 10, (acc, x) => acc + x);", 16)]
// An expression-bodied VOID lambda (List.ForEach takes an Action): rewritten to `{ Check(); e; }`.
[InlineData("var acc = new System.Collections.Generic.List(); new System.Collections.Generic.List { 1, 2 }.ForEach(x => acc.Add(x * 5)); return acc[1];", 10)]
// Expression-bodied void local function, and a local function returning a lambda (nested rewrite).
[InlineData("var box = new int[1]; void Set(int v) => box[0] = v; Set(7); return box[0];", 7)]
[InlineData("System.Func Make(int k) => x => x + k; return Make(3)(4);", 7)]
// Static lambda and a lambda capturing ctx.
[InlineData("System.Func f = static x => x - 1; return f(9);", 8)]
[InlineData("System.Func known = p => ctx.GetTag(p).StatusCode == 0u; return known(\"missing\") ? 1 : 0;", 0)]
public void Rewritten_lambdas_and_local_functions_keep_their_meaning(string source, int expected) =>
Run(source).ShouldBe(expected);
/// A lambda converted to an expression tree is left as it is (a block body is illegal there and an
/// expression tree runs no script frame); rewriting it would turn a compiling script into an emit failure.
[Fact]
public void An_expression_tree_lambda_is_not_rewritten()
{
Run("System.Linq.Expressions.Expression> e = x => x + 1; return (int)e.Body.NodeType;")
.ShouldBe((int)System.Linq.Expressions.ExpressionType.Add);
}
}