fix(templates): override rows on a derived template are not second definitions in the collision check (ScadaBridge#119)
CollisionDetector counted a derived template's own override row (same name as a parent member, IsInherited=false) as a second definition alongside the parent's row, so AddAttribute/AddAlarm/AddScript for a fresh name failed with a false "Naming collision" once any override existed. The inheritance walk now keeps a (type, name) shadow set: a member defined at a nearer level of the chain shadows the same member further up (effective member set, by name), including overrides at mid levels. Real collisions (e.g. against a composed module) still report. Regression tests in TemplateEngine.Tests (detector + service, attribute/alarm/ script, mid-level override, positive control); 6 fail without the fix. The #118 importer's additions-before-overrides ordering is now unnecessary but harmless and was left in place. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -197,6 +197,62 @@ public class CollisionDetectorTests
|
||||
Assert.Contains(collisions, c => c.Contains("dup.Shared"));
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// ScadaBridge#119: an OVERRIDE row on a derived template (own, IsInherited=false,
|
||||
// same name as a parent member) is not a second definition of the member.
|
||||
// ========================================================================
|
||||
|
||||
private static (Template parent, Template derived) OverrideFixture()
|
||||
{
|
||||
var parent = new Template("Base") { Id = 1 };
|
||||
parent.Attributes.Add(new TemplateAttribute("A") { Id = 10, TemplateId = 1, DataType = DataType.Float });
|
||||
parent.Alarms.Add(new TemplateAlarm("Al") { Id = 11, TemplateId = 1, TriggerType = AlarmTriggerType.ValueMatch });
|
||||
parent.Scripts.Add(new TemplateScript("Sc", "return 1;") { Id = 12, TemplateId = 1 });
|
||||
|
||||
var derived = new Template("Slot") { Id = 2, ParentTemplateId = 1 };
|
||||
derived.Attributes.Add(new TemplateAttribute("A") { Id = 20, TemplateId = 2, DataType = DataType.Float, Value = "9" }); // override
|
||||
derived.Alarms.Add(new TemplateAlarm("Al") { Id = 21, TemplateId = 2, TriggerType = AlarmTriggerType.ValueMatch });
|
||||
derived.Scripts.Add(new TemplateScript("Sc", "return 2;") { Id = 22, TemplateId = 2 });
|
||||
return (parent, derived);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that override rows of every member kind are not collisions.</summary>
|
||||
[Fact]
|
||||
public void DetectCollisions_OverrideRowsOnDerivedTemplate_NoFalseCollision()
|
||||
{
|
||||
var (parent, derived) = OverrideFixture();
|
||||
derived.Attributes.Add(new TemplateAttribute("New") { TemplateId = 2, DataType = DataType.String });
|
||||
Assert.Empty(CollisionDetector.DetectCollisions(derived, new List<Template> { parent, derived }));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an override on a mid-level template does not collide with the grandparent.</summary>
|
||||
[Fact]
|
||||
public void DetectCollisions_OverrideAtMidLevel_NoFalseCollision()
|
||||
{
|
||||
var gp = new Template("Root") { Id = 1 };
|
||||
gp.Attributes.Add(new TemplateAttribute("A") { Id = 10, TemplateId = 1, DataType = DataType.Float });
|
||||
var mid = new Template("Mid") { Id = 2, ParentTemplateId = 1 };
|
||||
mid.Attributes.Add(new TemplateAttribute("A") { Id = 20, TemplateId = 2, DataType = DataType.Float });
|
||||
var leaf = new Template("Leaf") { Id = 3, ParentTemplateId = 2 };
|
||||
leaf.Attributes.Add(new TemplateAttribute("A") { Id = 30, TemplateId = 3, DataType = DataType.Float, IsInherited = true });
|
||||
Assert.Empty(CollisionDetector.DetectCollisions(leaf, new List<Template> { gp, mid, leaf }));
|
||||
}
|
||||
|
||||
/// <summary>Positive control: a real collision on a template that also has overrides is still reported.</summary>
|
||||
[Fact]
|
||||
public void DetectCollisions_OverrideRowPresent_RealCollisionStillDetected()
|
||||
{
|
||||
var (parent, derived) = OverrideFixture();
|
||||
var module = new Template("Module") { Id = 3 };
|
||||
module.Attributes.Add(new TemplateAttribute("Shared") { Id = 30, TemplateId = 3, DataType = DataType.Float });
|
||||
derived.Compositions.Add(new TemplateComposition("dup") { Id = 1, TemplateId = 2, ComposedTemplateId = 3 });
|
||||
derived.Attributes.Add(new TemplateAttribute("dup.Shared") { TemplateId = 2, DataType = DataType.Float });
|
||||
|
||||
var c = CollisionDetector.DetectCollisions(derived, new List<Template> { parent, derived, module });
|
||||
Assert.Single(c);
|
||||
Assert.Contains("dup.Shared", c[0]);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Repeated composition blindness. A template composed into TWO
|
||||
// different slots was added to the global visited-set on the first slot, so
|
||||
|
||||
Reference in New Issue
Block a user