From 9a234e8b09dba6821428861090d9930018cea5a3 Mon Sep 17 00:00:00 2001 From: samatstarion Date: Sun, 21 Jun 2026 23:18:02 +0200 Subject: [PATCH 1/2] [Feature] implement EObject.EClass() to return the element meta class; fixes #10 Resolve the Ecore meta class for an EObject by looking up // + runtime type name in the resource's registered eCoreTypes (new internal Resource.GetMetaClass helper), matching the EMF eClass() contract. Guards unregistered types with a descriptive exception. --- .../EObjectMetaClassTestFixture.cs | 102 ++++++++++++++++++ ECoreNetto/ModelElement/EObject.cs | 10 +- ECoreNetto/Resource/Resource.cs | 20 ++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 ECoreNetto.Tests/ModelElement/EObjectMetaClassTestFixture.cs diff --git a/ECoreNetto.Tests/ModelElement/EObjectMetaClassTestFixture.cs b/ECoreNetto.Tests/ModelElement/EObjectMetaClassTestFixture.cs new file mode 100644 index 0000000..fe07e95 --- /dev/null +++ b/ECoreNetto.Tests/ModelElement/EObjectMetaClassTestFixture.cs @@ -0,0 +1,102 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2017-2025 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace ECoreNetto.Tests.ModelElement +{ + using System; + using System.IO; + using System.Linq; + + using ECoreNetto.Resource; + + using NUnit.Framework; + + /// + /// Suite of tests that verify returns the Ecore meta class that + /// describes the runtime type of the model element (see issue #10). + /// + [TestFixture] + public class EObjectMetaClassTestFixture + { + private Resource resource = null!; + + private EPackage rootPackage = null!; + + [SetUp] + public void SetUp() + { + var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "recipe.ecore"); + var uri = new Uri(Path.GetFullPath(path)); + + var resourceSet = new ResourceSet(); + this.resource = resourceSet.CreateResource(uri); + this.rootPackage = this.resource.Load(null); + } + + [Test] + public void Verify_that_EClass_returns_the_expected_meta_class_for_each_element_kind() + { + var eClass = this.rootPackage.EClassifiers.OfType().First(); + var attribute = this.rootPackage.EClassifiers.OfType() + .SelectMany(c => c.EStructuralFeatures).OfType().First(); + var reference = this.rootPackage.EClassifiers.OfType() + .SelectMany(c => c.EStructuralFeatures).OfType().First(); + var eEnum = this.rootPackage.EClassifiers.OfType().First(); + var literal = this.resource.GetEObject("recipe.ecore#//Unit/PIECE"); + + Assert.That(literal, Is.Not.Null); + + Assert.Multiple(() => + { + Assert.That(this.rootPackage.EClass().Name, Is.EqualTo("EPackage")); + Assert.That(eClass.EClass().Name, Is.EqualTo("EClass")); + Assert.That(attribute.EClass().Name, Is.EqualTo("EAttribute")); + Assert.That(reference.EClass().Name, Is.EqualTo("EReference")); + Assert.That(eEnum.EClass().Name, Is.EqualTo("EEnum")); + Assert.That(literal!.EClass().Name, Is.EqualTo("EEnumLiteral")); + }); + } + + [Test] + public void Verify_that_the_returned_meta_class_is_the_resource_registered_instance() + { + var eClass = this.rootPackage.EClassifiers.OfType().First(); + + var metaClass = eClass.EClass(); + + Assert.Multiple(() => + { + // the meta class is itself an EClass, and is the same instance the resource exposes for "//EClass" + Assert.That(metaClass, Is.InstanceOf()); + Assert.That(metaClass, Is.SameAs(this.resource.GetEObject("//EClass"))); + }); + } + + [Test] + public void Verify_that_the_meta_class_is_consistent_across_instances_of_the_same_type() + { + var attributes = this.rootPackage.EClassifiers.OfType() + .SelectMany(c => c.EStructuralFeatures).OfType().Take(2).ToList(); + + Assert.That(attributes, Has.Count.EqualTo(2)); + Assert.That(attributes[0].EClass(), Is.SameAs(attributes[1].EClass())); + } + } +} diff --git a/ECoreNetto/ModelElement/EObject.cs b/ECoreNetto/ModelElement/EObject.cs index 51c4309..69db4f5 100644 --- a/ECoreNetto/ModelElement/EObject.cs +++ b/ECoreNetto/ModelElement/EObject.cs @@ -87,7 +87,15 @@ protected EObject(Resource.Resource resource, ILoggerFactory? loggerFactory = nu /// public EClass EClass() { - throw new NotImplementedException(); + var metaClass = this.EResource.GetMetaClass(this.GetType().Name); + + if (metaClass == null) + { + throw new InvalidOperationException( + $"No Ecore meta class is registered for '{this.GetType().Name}'."); + } + + return metaClass; } /// diff --git a/ECoreNetto/Resource/Resource.cs b/ECoreNetto/Resource/Resource.cs index 4bbfd74..e5943fd 100644 --- a/ECoreNetto/Resource/Resource.cs +++ b/ECoreNetto/Resource/Resource.cs @@ -200,6 +200,26 @@ public IEnumerable AllContents() return this.Cache.Values; } + /// + /// Gets the Ecore meta class registered for the provided simple type name. + /// + /// + /// The simple name of the meta class (e.g. EClass, EAttribute), which matches the + /// runtime type name of the model elements. + /// + /// + /// The meta class, or null when no meta class is registered for . + /// + internal EClass? GetMetaClass(string name) + { + if (this.eCoreTypes.TryGetValue($"//{name}", out var metaClass) && metaClass is EClass eClass) + { + return eClass; + } + + return null; + } + /// /// Returns the URI fragment that, when passed to , will return the given object. /// From 1a84c0be24fb649b76ada1426ab75dc53c34ce4a Mon Sep 17 00:00:00 2001 From: samatstarion Date: Mon, 22 Jun 2026 09:18:59 +0200 Subject: [PATCH 2/2] [Test] cover EObject.EClass guard and Resource.GetMetaClass null paths Add tests for the no-registered-meta-class throw path of EObject.EClass() and the unknown-type-name null return of Resource.GetMetaClass(string), bringing new-code coverage on #10 to 100% (>= 80% quality gate). Also document the 80% new-code coverage requirement in CLAUDE.md. --- CLAUDE.md | 10 ++++++ .../EObjectMetaClassTestFixture.cs | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index a0769ed..479690c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,6 +71,16 @@ Logging throughout uses optional injected `ILoggerFactory`, falling back to `Nul Sample models live in `TestData/` (`ecore.ecore`, `recipe.ecore`, `wizardEcore.ecore`) and are linked into test projects' output under `Data/` via `` items with `CopyToOutputDirectory`. Test framework is **NUnit 4** with **Moq**; coverage via coverlet. `InternalsVisibleTo` exposes internals (e.g. `ECoreParser`) to each project's `.Tests` assembly. +## Code coverage requirement + +**All new code must reach at least 80% line/branch coverage** — this is enforced by the SonarCloud quality gate on new code. When implementing a feature or fix, add tests that exercise every new branch (happy path *and* guard/error paths) so coverage on the changed lines is ≥ 80%. Verify locally before opening/updating a PR: + +```powershell +dotnet test ECoreNetto.Tests/ECoreNetto.Tests.csproj --collect:"XPlat Code Coverage" --settings coverlet.runsettings +``` + +Then inspect the generated Cobertura report (or run ReportGenerator) and confirm the new members are covered. Do not consider an issue "done" until its new code clears the 80% bar. + ## Code style (from .github/CONTRIBUTING.md) These differ from default .NET conventions — match them: diff --git a/ECoreNetto.Tests/ModelElement/EObjectMetaClassTestFixture.cs b/ECoreNetto.Tests/ModelElement/EObjectMetaClassTestFixture.cs index fe07e95..7d802e8 100644 --- a/ECoreNetto.Tests/ModelElement/EObjectMetaClassTestFixture.cs +++ b/ECoreNetto.Tests/ModelElement/EObjectMetaClassTestFixture.cs @@ -98,5 +98,36 @@ public void Verify_that_the_meta_class_is_consistent_across_instances_of_the_sam Assert.That(attributes, Has.Count.EqualTo(2)); Assert.That(attributes[0].EClass(), Is.SameAs(attributes[1].EClass())); } + + [Test] + public void Verify_that_EClass_throws_when_no_meta_class_is_registered_for_the_runtime_type() + { + // a runtime type whose name is not present in the Ecore meta model registry + var unregistered = new UnregisteredEObject(this.resource); + + Assert.That(() => unregistered.EClass(), Throws.TypeOf()); + } + + [Test] + public void Verify_that_GetMetaClass_returns_null_for_an_unknown_type_name() + { + Assert.That(this.resource.GetMetaClass("ThisTypeDoesNotExist"), Is.Null); + } + + /// + /// A minimal subtype whose runtime type name has no corresponding Ecore + /// meta class, used to exercise the guard path of . + /// + private sealed class UnregisteredEObject : EObject + { + public UnregisteredEObject(Resource resource) + : base(resource) + { + } + + internal override void SetProperties() + { + } + } } }