mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
- Introduce ExecutionTimer/ExecutionTimerConfig to allow limiting evaluating time. - To amortize time checking costs, checking interval can be configured via the notion of work units - A global fallback time limit can be set to universally limit all evaluation in addition to engine level limit setting. - Implement limnits in interpreter and RVM. In RVM, also handle suspend/resume so that time during pause is not counted. - Add engine-level APIs to set/clear per-engine timer configuration and apply global fallback defaults. - Surface execution-time limits through FFI and C# bindings - Add C# tests and example usage to validate engine overrides, global fallback behavior, and compiled policy enforcement. - Expand docs for execution-time limit - Add interpreter YAML cases and VM unit tests for time-limit behavior and deterministic time sources. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
132 lines
4.5 KiB
C#
132 lines
4.5 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using Regorus;
|
|
|
|
namespace Regorus.Tests;
|
|
|
|
[DoNotParallelize] // Uses global fallback config; must run sequentially.
|
|
[TestClass]
|
|
public class ExecutionTimerTests
|
|
{
|
|
private const string Policy = @"
|
|
package limits.timer
|
|
import rego.v1
|
|
|
|
triplet_count := count([1 |
|
|
x := data.values[_]
|
|
y := data.values[_]
|
|
z := data.values[_]
|
|
])
|
|
";
|
|
|
|
private const string Query = "data.limits.timer.triplet_count";
|
|
private const int ValueCount = 160;
|
|
|
|
[TestMethod]
|
|
public void Engine_limit_enforced()
|
|
{
|
|
Engine.ClearFallbackExecutionTimerConfig();
|
|
using var engine = CreateEngine(ValueCount);
|
|
var config = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
|
engine.SetExecutionTimerConfig(config);
|
|
|
|
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
|
|
StringAssert.Contains(ex.Message, "execution exceeded time limit");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Fallback_applies_to_new_engines()
|
|
{
|
|
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
|
Engine.SetFallbackExecutionTimerConfig(fallback);
|
|
try
|
|
{
|
|
using var engine = CreateEngine(ValueCount);
|
|
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
|
|
StringAssert.Contains(ex.Message, "execution exceeded time limit");
|
|
}
|
|
finally
|
|
{
|
|
Engine.ClearFallbackExecutionTimerConfig();
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Engine_override_relaxes_fallback()
|
|
{
|
|
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
|
Engine.SetFallbackExecutionTimerConfig(fallback);
|
|
try
|
|
{
|
|
using var engine = CreateEngine(ValueCount);
|
|
var relaxed = new ExecutionTimerConfig(TimeSpan.FromSeconds(12), checkInterval: 1);
|
|
engine.SetExecutionTimerConfig(relaxed);
|
|
|
|
var resultJson = engine.EvalRule(Query);
|
|
var result = JsonSerializer.Deserialize<int>(resultJson!);
|
|
Assert.IsTrue(result > 0, "Expected a positive triplet count when limit is relaxed.");
|
|
|
|
engine.ClearExecutionTimerConfig();
|
|
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
|
|
StringAssert.Contains(ex.Message, "execution exceeded time limit");
|
|
}
|
|
finally
|
|
{
|
|
Engine.ClearFallbackExecutionTimerConfig();
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CompiledPolicy_limit_enforced()
|
|
{
|
|
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
|
Engine.SetFallbackExecutionTimerConfig(fallback);
|
|
try
|
|
{
|
|
using var policy = CreateCompiledPolicy(ValueCount);
|
|
var ex = Assert.ThrowsException<InvalidOperationException>(() => policy.EvalWithInput("null"));
|
|
StringAssert.Contains(ex.Message, "execution exceeded time limit");
|
|
}
|
|
finally
|
|
{
|
|
Engine.ClearFallbackExecutionTimerConfig();
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CompiledPolicy_uses_engine_limits_only()
|
|
{
|
|
// Compiled policies no longer store per-policy execution timers; limits are managed by Engine.
|
|
Engine.ClearFallbackExecutionTimerConfig();
|
|
using var policy = CreateCompiledPolicy(ValueCount);
|
|
var resultJson = policy.EvalWithInput("null");
|
|
var result = JsonSerializer.Deserialize<int>(resultJson!);
|
|
Assert.IsTrue(result > 0, "CompiledPolicy should evaluate using engine defaults without its own timer");
|
|
}
|
|
|
|
private static Engine CreateEngine(int valueCount)
|
|
{
|
|
var engine = new Engine();
|
|
engine.AddPolicy("limits_timer.rego", Policy);
|
|
engine.AddDataJson(CreateData(valueCount));
|
|
return engine;
|
|
}
|
|
|
|
private static CompiledPolicy CreateCompiledPolicy(int valueCount)
|
|
{
|
|
var modules = new[] { new PolicyModule("limits_timer.rego", Policy) };
|
|
return Compiler.CompilePolicyWithEntrypoint(CreateData(valueCount), modules, Query);
|
|
}
|
|
|
|
private static string CreateData(int valueCount)
|
|
{
|
|
var payload = new { values = Enumerable.Range(0, valueCount).ToArray() };
|
|
return JsonSerializer.Serialize(payload);
|
|
}
|
|
}
|