test(engines): add a shared engine test kit and contract tests
Each engine's tests carried its own copy of the job/shape helpers and validation, and they had drifted: Gpt6Astra linked NestValidator.cs as source (which stopped compiling once the host moved its checks into NestLayoutCheck), the others referenced OpenNest.Benchmark. The kit provides JobBuilder, Shapes, LayoutAssert (backed by the host's public NestLayoutCheck) and EngineContractTests<TEngine>: quadrants, overflow, oversize parts, lower-number priority, etch marks, sequential plate indices, determinism, cancellation and stop reasons, for every engine. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Engine.Jobs.Adapters;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenNest.Engine.Testing;
|
||||
|
||||
public static class LayoutAssert
|
||||
{
|
||||
public static void Valid(NestJob job, NestJobResult result)
|
||||
{
|
||||
var violations = NestLayoutCheck.Violations(job, result);
|
||||
Assert.True(violations.Count == 0, string.Join(Environment.NewLine, violations));
|
||||
Assert.Equal(Enumerable.Range(0, result.Plates.Count), result.Plates.Select(p => p.PlateIndex));
|
||||
foreach (var f in result.Fulfillment)
|
||||
Assert.Equal(f.Requested, f.Placed + f.Unplaced);
|
||||
foreach (var sheet in result.Plates)
|
||||
{
|
||||
var s = sheet.Stock;
|
||||
var work = s.WorkArea;
|
||||
foreach (var pose in sheet.Placements)
|
||||
{
|
||||
var part = job.Parts.Single(p => p.Id == pose.PartId);
|
||||
Assert.True(part.Rotation.Allows(pose.Rotation));
|
||||
var geometry = ConvertProgram.ToGeometry(DrawingJobMapper.ToProgram(part.Geometry))
|
||||
.Where(e => SpecialLayers.IsMaterial(e.Layer)).ToArray();
|
||||
foreach (var entity in geometry) { entity.Rotate(pose.Rotation); entity.Offset(pose.X, pose.Y); }
|
||||
var b = (L: geometry.Min(e => e.Left), B: geometry.Min(e => e.Bottom),
|
||||
R: geometry.Max(e => e.Right), T: geometry.Max(e => e.Top));
|
||||
Assert.True(b.L >= work.Left - 1e-7 && b.B >= work.Bottom - 1e-7
|
||||
&& b.R <= work.Right + 1e-7 && b.T <= work.Top + 1e-7);
|
||||
}
|
||||
}
|
||||
foreach (var part in job.Parts)
|
||||
{
|
||||
var placed = result.Plates.SelectMany(s => s.Placements).Where(p => p.PartId == part.Id).ToArray();
|
||||
Assert.Equal(Enumerable.Range(0, placed.Length), placed.Select(p => p.InstanceIndex).Order());
|
||||
var fulfillment = result.Fulfillment.Single(f => f.PartId == part.Id);
|
||||
Assert.Equal(placed.Length, fulfillment.Placed);
|
||||
Assert.Equal(part.Quantity, fulfillment.Placed + fulfillment.Unplaced);
|
||||
}
|
||||
foreach (var usage in result.StockUsage)
|
||||
{
|
||||
var stock = job.Plates.Single(s => s.Id == usage.StockId);
|
||||
Assert.Equal(result.Plates.Count(s => s.StockId == stock.Id), usage.Used);
|
||||
Assert.Equal(stock.Quantity - usage.Used, usage.Remaining);
|
||||
Assert.True(usage.Remaining is null or >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user