diff --git a/SysML2.NET.Tests/Extend/StateUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/StateUsageExtensionsTestFixture.cs index d064123c..02f6c48b 100644 --- a/SysML2.NET.Tests/Extend/StateUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/StateUsageExtensionsTestFixture.cs @@ -1,56 +1,258 @@ // ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 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 SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Kernel.Behaviors; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Core.POCO.Systems.Actions; using SysML2.NET.Core.POCO.Systems.States; + using SysML2.NET.Core.Systems.States; + using SysML2.NET.Extensions; [TestFixture] public class StateUsageExtensionsTestFixture { [Test] - public void ComputeDoAction_ThrowsNotSupportedException() + public void VerifyComputeDoAction() { - Assert.That(() => ((IStateUsage)null).ComputeDoAction(), Throws.TypeOf()); + Assert.That(() => ((IStateUsage)null).ComputeDoAction(), Throws.TypeOf()); + + // Empty: no StateSubactionMembership at all → null. + var emptyStateUsage = new StateUsage(); + + Assert.That(emptyStateUsage.ComputeDoAction(), Is.Null); + + // Wrong kind: one StateSubactionMembership of kind Entry only → null (action is never accessed). + var stateUsageWithEntry = new StateUsage(); + var entryAction = new ActionUsage(); + stateUsageWithEntry.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, entryAction); + + Assert.That(stateUsageWithEntry.ComputeDoAction(), Is.Null); + + // Matching kind: one StateSubactionMembership of kind Do → NotSupportedException until + // StateSubactionMembershipExtensions.ComputeAction stub is resolved. + // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub + var stateUsageWithDo = new StateUsage(); + var doAction = new ActionUsage(); + stateUsageWithDo.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, doAction); + + Assert.That(() => stateUsageWithDo.ComputeDoAction(), Throws.TypeOf()); + + // All three kinds present → first matching Do membership's action is accessed → NotSupportedException. + // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub + var stateUsageAllKinds = new StateUsage(); + var allKindsEntry = new ActionUsage(); + var allKindsDo = new ActionUsage(); + var allKindsExit = new ActionUsage(); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, allKindsEntry); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, allKindsDo); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Exit }, allKindsExit); + + Assert.That(() => stateUsageAllKinds.ComputeDoAction(), Throws.TypeOf()); } - + [Test] - public void ComputeEntryAction_ThrowsNotSupportedException() + public void VerifyComputeEntryAction() { - Assert.That(() => ((IStateUsage)null).ComputeEntryAction(), Throws.TypeOf()); + Assert.That(() => ((IStateUsage)null).ComputeEntryAction(), Throws.TypeOf()); + + // Empty: no StateSubactionMembership at all → null. + var emptyStateUsage = new StateUsage(); + + Assert.That(emptyStateUsage.ComputeEntryAction(), Is.Null); + + // Wrong kind: one StateSubactionMembership of kind Do only → null (action is never accessed). + var stateUsageWithDo = new StateUsage(); + var doAction = new ActionUsage(); + stateUsageWithDo.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, doAction); + + Assert.That(stateUsageWithDo.ComputeEntryAction(), Is.Null); + + // Matching kind: one StateSubactionMembership of kind Entry → NotSupportedException until + // StateSubactionMembershipExtensions.ComputeAction stub is resolved. + // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub + var stateUsageWithEntry = new StateUsage(); + var entryAction = new ActionUsage(); + stateUsageWithEntry.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, entryAction); + + Assert.That(() => stateUsageWithEntry.ComputeEntryAction(), Throws.TypeOf()); + + // All three kinds present → first matching Entry membership's action is accessed → NotSupportedException. + // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub + var stateUsageAllKinds = new StateUsage(); + var allKindsEntry = new ActionUsage(); + var allKindsDo = new ActionUsage(); + var allKindsExit = new ActionUsage(); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, allKindsEntry); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, allKindsDo); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Exit }, allKindsExit); + + Assert.That(() => stateUsageAllKinds.ComputeEntryAction(), Throws.TypeOf()); } - + [Test] - public void ComputeExitAction_ThrowsNotSupportedException() + public void VerifyComputeExitAction() { - Assert.That(() => ((IStateUsage)null).ComputeExitAction(), Throws.TypeOf()); + Assert.That(() => ((IStateUsage)null).ComputeExitAction(), Throws.TypeOf()); + + // Empty: no StateSubactionMembership at all → null. + var emptyStateUsage = new StateUsage(); + + Assert.That(emptyStateUsage.ComputeExitAction(), Is.Null); + + // Wrong kind: one StateSubactionMembership of kind Do only → null (action is never accessed). + var stateUsageWithDo = new StateUsage(); + var doAction = new ActionUsage(); + stateUsageWithDo.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, doAction); + + Assert.That(stateUsageWithDo.ComputeExitAction(), Is.Null); + + // Matching kind: one StateSubactionMembership of kind Exit → NotSupportedException until + // StateSubactionMembershipExtensions.ComputeAction stub is resolved. + // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub + var stateUsageWithExit = new StateUsage(); + var exitAction = new ActionUsage(); + stateUsageWithExit.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Exit }, exitAction); + + Assert.That(() => stateUsageWithExit.ComputeExitAction(), Throws.TypeOf()); + + // All three kinds present → first matching Exit membership's action is accessed → NotSupportedException. + // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub + var stateUsageAllKinds = new StateUsage(); + var allKindsEntry = new ActionUsage(); + var allKindsDo = new ActionUsage(); + var allKindsExit = new ActionUsage(); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, allKindsEntry); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, allKindsDo); + stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Exit }, allKindsExit); + + Assert.That(() => stateUsageAllKinds.ComputeExitAction(), Throws.TypeOf()); } - + + [Test] + public void VerifyComputeStateDefinition() + { + Assert.That(() => ((IStateUsage)null).ComputeStateDefinition(), Throws.TypeOf()); + + // Empty: no FeatureTyping in OwnedRelationship → empty list. + var emptyStateUsage = new StateUsage(); + + Assert.That(emptyStateUsage.ComputeStateDefinition(), Has.Count.EqualTo(0)); + + // One FeatureTyping whose Type is a StateDefinition (which implements IBehavior) → returned. + var stateUsageWithStateDefinition = new StateUsage(); + var stateDefinition = new StateDefinition(); + stateUsageWithStateDefinition.AssignOwnership(new FeatureTyping { Type = stateDefinition }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(stateUsageWithStateDefinition.ComputeStateDefinition(), Has.Count.EqualTo(1)); + Assert.That(stateUsageWithStateDefinition.ComputeStateDefinition(), Does.Contain(stateDefinition)); + } + + // One FeatureTyping whose Type is a plain Behavior (not IStateDefinition) → also returned (spec allows it). + var stateUsageWithBehavior = new StateUsage(); + var plainBehavior = new Behavior(); + stateUsageWithBehavior.AssignOwnership(new FeatureTyping { Type = plainBehavior }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(stateUsageWithBehavior.ComputeStateDefinition(), Has.Count.EqualTo(1)); + Assert.That(stateUsageWithBehavior.ComputeStateDefinition(), Does.Contain(plainBehavior)); + } + + // Mixed FeatureTypings: one IBehavior and one plain Classifier (not an IBehavior) → only the IBehavior returned. + var mixedStateUsage = new StateUsage(); + var mixedBehavior = new StateDefinition(); + var nonBehaviorType = new Classifier(); + mixedStateUsage.AssignOwnership(new FeatureTyping { Type = mixedBehavior }); + mixedStateUsage.AssignOwnership(new FeatureTyping { Type = nonBehaviorType }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(mixedStateUsage.ComputeStateDefinition(), Has.Count.EqualTo(1)); + Assert.That(mixedStateUsage.ComputeStateDefinition(), Does.Contain(mixedBehavior)); + Assert.That(mixedStateUsage.ComputeStateDefinition(), Does.Not.Contain(nonBehaviorType)); + } + } + [Test] - public void ComputeStateDefinition_ThrowsNotSupportedException() + public void VerifyComputeIsSubstateUsageOperation() { - Assert.That(() => ((IStateUsage)null).ComputeStateDefinition(), Throws.TypeOf()); + Assert.That(() => ((IStateUsage)null).ComputeIsSubstateUsageOperation(false), Throws.TypeOf()); + + // IsComposite = false → false regardless of other conditions. + var nonCompositeStateUsage = new StateUsage { IsComposite = false }; + + Assert.That(nonCompositeStateUsage.ComputeIsSubstateUsageOperation(false), Is.False); + + // IsComposite = true, owningType = null (no owner) → false. + var orphanStateUsage = new StateUsage { IsComposite = true }; + + Assert.That(orphanStateUsage.ComputeIsSubstateUsageOperation(false), Is.False); + + // owningType is a StateDefinition with IsParallel = true, isParallel = true, + // owned via plain FeatureMembership (not StateSubactionMembership) → true. + var parentStateDefinition = new StateDefinition { IsParallel = true }; + var substateUnderDefinition = new StateUsage { IsComposite = true }; + parentStateDefinition.AssignOwnership(new FeatureMembership(), substateUnderDefinition); + + Assert.That(substateUnderDefinition.ComputeIsSubstateUsageOperation(true), Is.True); + + // owningType is a StateDefinition with IsParallel = false, isParallel = true → false (mismatch). + var nonParallelDefinition = new StateDefinition { IsParallel = false }; + var substateParallelMismatch = new StateUsage { IsComposite = true }; + nonParallelDefinition.AssignOwnership(new FeatureMembership(), substateParallelMismatch); + + Assert.That(substateParallelMismatch.ComputeIsSubstateUsageOperation(true), Is.False); + + // owningType is a StateUsage with IsParallel = false, isParallel = false, + // owned via plain FeatureMembership → true. + var parentStateUsage = new StateUsage { IsParallel = false }; + var substateUnderUsage = new StateUsage { IsComposite = true }; + parentStateUsage.AssignOwnership(new FeatureMembership(), substateUnderUsage); + + Assert.That(substateUnderUsage.ComputeIsSubstateUsageOperation(false), Is.True); + + // All conditions met but owningFeatureMembership is a StateSubactionMembership → false. + var parallelDefinition = new StateDefinition { IsParallel = true }; + var actionSubstate = new StateUsage { IsComposite = true }; + var doMembership = new StateSubactionMembership { Kind = StateSubactionKind.Do }; + parallelDefinition.AssignOwnership(doMembership, actionSubstate); + + Assert.That(actionSubstate.ComputeIsSubstateUsageOperation(true), Is.False); + + // owningType is some other type (not StateDefinition/StateUsage) → false. + var otherParent = new ActionDefinition(); + var substateUnderOtherType = new StateUsage { IsComposite = true }; + otherParent.AssignOwnership(new FeatureMembership(), substateUnderOtherType); + + Assert.That(substateUnderOtherType.ComputeIsSubstateUsageOperation(false), Is.False); } } } diff --git a/SysML2.NET/Extend/StateUsageExtensions.cs b/SysML2.NET/Extend/StateUsageExtensions.cs index 02a71c0f..7c71586a 100644 --- a/SysML2.NET/Extend/StateUsageExtensions.cs +++ b/SysML2.NET/Extend/StateUsageExtensions.cs @@ -22,40 +22,12 @@ namespace SysML2.NET.Core.POCO.Systems.States { using System; using System.Collections.Generic; + using System.Linq; - using SysML2.NET.Core.Core.Types; - using SysML2.NET.Core.Root.Namespaces; - using SysML2.NET.Core.Systems.Occurrences; - using SysML2.NET.Core.POCO.Core.Classifiers; using SysML2.NET.Core.POCO.Core.Features; - using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Kernel.Behaviors; - using SysML2.NET.Core.POCO.Kernel.Classes; - using SysML2.NET.Core.POCO.Kernel.Functions; - using SysML2.NET.Core.POCO.Root.Annotations; - using SysML2.NET.Core.POCO.Root.Elements; - using SysML2.NET.Core.POCO.Root.Namespaces; using SysML2.NET.Core.POCO.Systems.Actions; - using SysML2.NET.Core.POCO.Systems.Allocations; - using SysML2.NET.Core.POCO.Systems.AnalysisCases; - using SysML2.NET.Core.POCO.Systems.Attributes; - using SysML2.NET.Core.POCO.Systems.Calculations; - using SysML2.NET.Core.POCO.Systems.Cases; - using SysML2.NET.Core.POCO.Systems.Connections; - using SysML2.NET.Core.POCO.Systems.Constraints; - using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; - using SysML2.NET.Core.POCO.Systems.Enumerations; - using SysML2.NET.Core.POCO.Systems.Flows; - using SysML2.NET.Core.POCO.Systems.Interfaces; - using SysML2.NET.Core.POCO.Systems.Items; - using SysML2.NET.Core.POCO.Systems.Metadata; - using SysML2.NET.Core.POCO.Systems.Occurrences; - using SysML2.NET.Core.POCO.Systems.Parts; - using SysML2.NET.Core.POCO.Systems.Ports; - using SysML2.NET.Core.POCO.Systems.Requirements; - using SysML2.NET.Core.POCO.Systems.UseCases; - using SysML2.NET.Core.POCO.Systems.VerificationCases; - using SysML2.NET.Core.POCO.Systems.Views; + using SysML2.NET.Core.Systems.States; /// /// The class provides extensions methods for @@ -85,10 +57,17 @@ internal static class StateUsageExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IActionUsage ComputeDoAction(this IStateUsage stateUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (stateUsageSubject == null) + { + throw new ArgumentNullException(nameof(stateUsageSubject)); + } + + return stateUsageSubject.ownedMembership + .OfType() + .FirstOrDefault(membership => membership.Kind == StateSubactionKind.Do) + ?.action; } /// @@ -113,10 +92,17 @@ internal static IActionUsage ComputeDoAction(this IStateUsage stateUsageSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IActionUsage ComputeEntryAction(this IStateUsage stateUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (stateUsageSubject == null) + { + throw new ArgumentNullException(nameof(stateUsageSubject)); + } + + return stateUsageSubject.ownedMembership + .OfType() + .FirstOrDefault(membership => membership.Kind == StateSubactionKind.Entry) + ?.action; } /// @@ -141,10 +127,17 @@ internal static IActionUsage ComputeEntryAction(this IStateUsage stateUsageSubje /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IActionUsage ComputeExitAction(this IStateUsage stateUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (stateUsageSubject == null) + { + throw new ArgumentNullException(nameof(stateUsageSubject)); + } + + return stateUsageSubject.ownedMembership + .OfType() + .FirstOrDefault(membership => membership.Kind == StateSubactionKind.Exit) + ?.action; } /// @@ -156,10 +149,15 @@ internal static IActionUsage ComputeExitAction(this IStateUsage stateUsageSubjec /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeStateDefinition(this IStateUsage stateUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return stateUsageSubject == null + ? throw new ArgumentNullException(nameof(stateUsageSubject)) + : [ + .. stateUsageSubject.OwnedRelationship.OfType() + .Select(featureTyping => featureTyping.Type) + .OfType() + ]; } /// @@ -188,10 +186,18 @@ internal static List ComputeStateDefinition(this IStateUsage stateUsa /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeIsSubstateUsageOperation(this IStateUsage stateUsageSubject, bool isParallel) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (stateUsageSubject == null) + { + throw new ArgumentNullException(nameof(stateUsageSubject)); + } + + return stateUsageSubject.IsComposite + && stateUsageSubject.owningType != null + && (stateUsageSubject.owningType is IStateDefinition stateDefinition && stateDefinition.IsParallel == isParallel + || stateUsageSubject.owningType is IStateUsage owningStateUsage && owningStateUsage.IsParallel == isParallel) + && stateUsageSubject.owningFeatureMembership is not IStateSubactionMembership; } } }