mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Support for Verus verification. Also add a verus github workflow. Signed-off-by: Jay Lorch <jaylorch@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
329 lines
12 KiB
C#
329 lines
12 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using Regorus;
|
|
|
|
namespace Regorus.Tests;
|
|
|
|
[TestClass]
|
|
[DoNotParallelize]
|
|
public class MemoryGrowthTests
|
|
{
|
|
private static int Iterations =>
|
|
int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_ITERS"), out var value) ? value : 50_000;
|
|
|
|
private static int LogEvery =>
|
|
int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_LOG_EVERY"), out var value) ? value : 500;
|
|
|
|
private static int GcEvery
|
|
{
|
|
get
|
|
{
|
|
if (!int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_GC_EVERY"), out var value))
|
|
{
|
|
value = LogEvery;
|
|
}
|
|
|
|
return value <= 0 ? LogEvery : value;
|
|
}
|
|
}
|
|
|
|
private static long? MaxWorkingSetDeltaBytes
|
|
{
|
|
get
|
|
{
|
|
if (!long.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_MAX_DELTA_MB"), out var mb))
|
|
{
|
|
mb = 32;
|
|
}
|
|
|
|
if (mb <= 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return mb * 1024L * 1024L;
|
|
}
|
|
}
|
|
|
|
private static ulong? GlobalRegorusMemoryLimitBytes
|
|
{
|
|
get
|
|
{
|
|
if (!ulong.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_GLOBAL_REGORUS_LIMIT_MB"), out var mb))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (mb == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return mb * 1024UL * 1024UL;
|
|
}
|
|
}
|
|
|
|
private static void WithOptionalGlobalRegorusMemoryLimit(Action action)
|
|
{
|
|
var priorLimit = MemoryLimits.GetGlobalMemoryLimit();
|
|
try
|
|
{
|
|
if (GlobalRegorusMemoryLimitBytes is { } limit)
|
|
{
|
|
MemoryLimits.SetGlobalMemoryLimit(limit);
|
|
}
|
|
|
|
action();
|
|
}
|
|
finally
|
|
{
|
|
MemoryLimits.SetGlobalMemoryLimit(priorLimit);
|
|
}
|
|
}
|
|
|
|
private static void ForceFullGc()
|
|
{
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect();
|
|
}
|
|
|
|
|
|
[TestMethod]
|
|
public void Engine_create_eval_dispose_does_not_grow_working_set()
|
|
{
|
|
WithOptionalGlobalRegorusMemoryLimit(() =>
|
|
{
|
|
var process = Process.GetCurrentProcess();
|
|
process.Refresh();
|
|
var baseline = process.WorkingSet64;
|
|
var maxDelta = 0L;
|
|
var baselineManaged = GC.GetTotalMemory(false);
|
|
var maxManagedDelta = 0L;
|
|
|
|
for (var i = 1; i <= Iterations; i++)
|
|
{
|
|
using (var engine = new Engine())
|
|
{
|
|
engine.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`");
|
|
_ = engine.EvalRule("data.test.message");
|
|
}
|
|
|
|
if (i % LogEvery == 0)
|
|
{
|
|
// Collect transient managed garbage so the working-set delta reflects
|
|
// retained (leaked) memory rather than uncollected allocations. A real
|
|
// native leak from a missed Dispose() would survive GC and still be caught.
|
|
ForceFullGc();
|
|
process.Refresh();
|
|
var workingSet = process.WorkingSet64;
|
|
var managed = GC.GetTotalMemory(false);
|
|
var delta = workingSet - baseline;
|
|
var managedDelta = managed - baselineManaged;
|
|
if (delta > maxDelta)
|
|
{
|
|
maxDelta = delta;
|
|
}
|
|
if (managedDelta > maxManagedDelta)
|
|
{
|
|
maxManagedDelta = managedDelta;
|
|
}
|
|
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
|
|
}
|
|
}
|
|
|
|
if (MaxWorkingSetDeltaBytes is { } limit)
|
|
{
|
|
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
|
|
Assert.IsTrue(
|
|
maxDelta <= limit,
|
|
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
|
|
}
|
|
});
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Engine_create_eval_finalize_does_not_grow_working_set()
|
|
{
|
|
WithOptionalGlobalRegorusMemoryLimit(() =>
|
|
{
|
|
var process = Process.GetCurrentProcess();
|
|
process.Refresh();
|
|
var baseline = process.WorkingSet64;
|
|
var maxDelta = 0L;
|
|
var baselineManaged = GC.GetTotalMemory(false);
|
|
var maxManagedDelta = 0L;
|
|
|
|
for (var i = 1; i <= Iterations; i++)
|
|
{
|
|
var engine = new Engine();
|
|
engine.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`");
|
|
_ = engine.EvalRule("data.test.message");
|
|
|
|
if (i % GcEvery == 0)
|
|
{
|
|
ForceFullGc();
|
|
}
|
|
|
|
if (i % LogEvery == 0)
|
|
{
|
|
process.Refresh();
|
|
var workingSet = process.WorkingSet64;
|
|
var managed = GC.GetTotalMemory(false);
|
|
var delta = workingSet - baseline;
|
|
var managedDelta = managed - baselineManaged;
|
|
if (delta > maxDelta)
|
|
{
|
|
maxDelta = delta;
|
|
}
|
|
if (managedDelta > maxManagedDelta)
|
|
{
|
|
maxManagedDelta = managedDelta;
|
|
}
|
|
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
|
|
}
|
|
}
|
|
|
|
if (MaxWorkingSetDeltaBytes is { } limit)
|
|
{
|
|
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
|
|
Assert.IsTrue(
|
|
maxDelta <= limit,
|
|
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
[TestMethod]
|
|
public void Rvm_rehydrate_execute_dispose_does_not_grow_working_set()
|
|
{
|
|
WithOptionalGlobalRegorusMemoryLimit(() =>
|
|
{
|
|
var modules = new[]
|
|
{
|
|
new PolicyModule("test.rego", "package test\nallow = true"),
|
|
};
|
|
|
|
using var compiled = Program.CompileFromModules("{}", modules, new[] { "data.test.allow" });
|
|
var serialized = compiled.SerializeBinary();
|
|
|
|
var process = Process.GetCurrentProcess();
|
|
process.Refresh();
|
|
var baseline = process.WorkingSet64;
|
|
var maxDelta = 0L;
|
|
var baselineManaged = GC.GetTotalMemory(false);
|
|
var maxManagedDelta = 0L;
|
|
|
|
for (var i = 1; i <= Iterations; i++)
|
|
{
|
|
using (var vm = new Rvm())
|
|
using (var program = Program.DeserializeBinary(serialized, out _))
|
|
{
|
|
vm.LoadProgram(program);
|
|
vm.SetDataJson("{}");
|
|
vm.SetInputJson("{}");
|
|
_ = vm.ExecuteEntryPoint(0);
|
|
}
|
|
|
|
if (i % LogEvery == 0)
|
|
{
|
|
// Collect transient managed garbage so the working-set delta reflects
|
|
// retained (leaked) memory rather than uncollected allocations. A real
|
|
// native leak from a missed Dispose() would survive GC and still be caught.
|
|
ForceFullGc();
|
|
process.Refresh();
|
|
var workingSet = process.WorkingSet64;
|
|
var managed = GC.GetTotalMemory(false);
|
|
var delta = workingSet - baseline;
|
|
var managedDelta = managed - baselineManaged;
|
|
if (delta > maxDelta)
|
|
{
|
|
maxDelta = delta;
|
|
}
|
|
if (managedDelta > maxManagedDelta)
|
|
{
|
|
maxManagedDelta = managedDelta;
|
|
}
|
|
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
|
|
}
|
|
}
|
|
|
|
if (MaxWorkingSetDeltaBytes is { } limit)
|
|
{
|
|
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
|
|
Assert.IsTrue(
|
|
maxDelta <= limit,
|
|
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
|
|
}
|
|
});
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Rvm_rehydrate_execute_finalize_does_not_grow_working_set()
|
|
{
|
|
WithOptionalGlobalRegorusMemoryLimit(() =>
|
|
{
|
|
var modules = new[]
|
|
{
|
|
new PolicyModule("test.rego", "package test\nallow = true"),
|
|
};
|
|
|
|
using var compiled = Program.CompileFromModules("{}", modules, new[] { "data.test.allow" });
|
|
var serialized = compiled.SerializeBinary();
|
|
|
|
var process = Process.GetCurrentProcess();
|
|
process.Refresh();
|
|
var baseline = process.WorkingSet64;
|
|
var maxDelta = 0L;
|
|
var baselineManaged = GC.GetTotalMemory(false);
|
|
var maxManagedDelta = 0L;
|
|
|
|
for (var i = 1; i <= Iterations; i++)
|
|
{
|
|
var vm = new Rvm();
|
|
var program = Program.DeserializeBinary(serialized, out _);
|
|
vm.LoadProgram(program);
|
|
vm.SetDataJson("{}");
|
|
vm.SetInputJson("{}");
|
|
_ = vm.ExecuteEntryPoint(0);
|
|
|
|
if (i % GcEvery == 0)
|
|
{
|
|
ForceFullGc();
|
|
}
|
|
|
|
if (i % LogEvery == 0)
|
|
{
|
|
process.Refresh();
|
|
var workingSet = process.WorkingSet64;
|
|
var managed = GC.GetTotalMemory(false);
|
|
var delta = workingSet - baseline;
|
|
var managedDelta = managed - baselineManaged;
|
|
if (delta > maxDelta)
|
|
{
|
|
maxDelta = delta;
|
|
}
|
|
if (managedDelta > maxManagedDelta)
|
|
{
|
|
maxManagedDelta = managedDelta;
|
|
}
|
|
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
|
|
}
|
|
}
|
|
|
|
if (MaxWorkingSetDeltaBytes is { } limit)
|
|
{
|
|
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
|
|
Assert.IsTrue(
|
|
maxDelta <= limit,
|
|
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
|
|
}
|
|
});
|
|
}
|
|
}
|