소스 검색

Flow optimizer for StateCase and HistoryEntry extended (YAKHMI-527)

benjamin.schwertfeger@gmail.com 13 년 전
부모
커밋
eb0afd3e99

+ 1 - 0
plugins/org.yakindu.sct.generator.core/library/CoreFeatureTypeLibrary.xmi

@@ -19,5 +19,6 @@
     <parameters name="inlineChoices" optional="true" parameterType="BOOLEAN"/>
     <parameters name="inlineEnterRegion" optional="true" parameterType="BOOLEAN"/>
     <parameters name="inlineExitRegion" optional="true" parameterType="BOOLEAN"/>
+    <parameters name="inlineEntries" optional="true" parameterType="BOOLEAN"/>
   </types>
 </sgen:FeatureTypeLibrary>

+ 1 - 0
plugins/org.yakindu.sct.generator.core/src/org/yakindu/sct/generator/core/features/ICoreFeatureConstants.java

@@ -35,6 +35,7 @@ public interface ICoreFeatureConstants {
 	public static final String FUNCTION_INLINING_FEATURE_INLINE_EXIT_ACTIONS = "inlineExitActions";
 	public static final String FUNCTION_INLINING_FEATURE_INLINE_EXIT_SEQUENCES = "inlineExitSequences";
 	public static final String FUNCTION_INLINING_FEATURE_INLINE_CHOICES = "inlineChoices";
+	public static final String FUNCTION_INLINING_FEATURE_INLINE_ENTRIES = "inlineEntries";
 	public static final String FUNCTION_INLINING_FEATURE_INLINE_ENTER_REGION = "inlineEnterRegion";
 	public static final String FUNCTION_INLINING_FEATURE_INLINE_EXIT_REGION = "inlineExitRegion";
 

+ 3 - 0
plugins/org.yakindu.sct.generator.core/src/org/yakindu/sct/generator/core/impl/AbstractSExecModelGenerator.java

@@ -16,6 +16,7 @@ import static org.yakindu.sct.generator.core.features.ICoreFeatureConstants.FUNC
 import static org.yakindu.sct.generator.core.features.ICoreFeatureConstants.FUNCTION_INLINING_FEATURE_INLINE_CHOICES;
 import static org.yakindu.sct.generator.core.features.ICoreFeatureConstants.FUNCTION_INLINING_FEATURE_INLINE_ENTER_SEQUENCES;
 import static org.yakindu.sct.generator.core.features.ICoreFeatureConstants.FUNCTION_INLINING_FEATURE_INLINE_ENTRY_ACTIONS;
+import static org.yakindu.sct.generator.core.features.ICoreFeatureConstants.FUNCTION_INLINING_FEATURE_INLINE_ENTRIES;
 import static org.yakindu.sct.generator.core.features.ICoreFeatureConstants.FUNCTION_INLINING_FEATURE_INLINE_EXIT_ACTIONS;
 import static org.yakindu.sct.generator.core.features.ICoreFeatureConstants.FUNCTION_INLINING_FEATURE_INLINE_EXIT_SEQUENCES;
 import static org.yakindu.sct.generator.core.features.ICoreFeatureConstants.FUNCTION_INLINING_FEATURE_INLINE_REACTIONS;
@@ -143,6 +144,8 @@ public abstract class AbstractSExecModelGenerator implements ISCTGenerator {
 				FUNCTION_INLINING_FEATURE_INLINE_EXIT_SEQUENCES, true));
 		optimizer.inlineChoices(getBoolValue(optimizeConfig,
 				FUNCTION_INLINING_FEATURE_INLINE_CHOICES, true));
+		optimizer.inlineEntries(getBoolValue(optimizeConfig,
+				FUNCTION_INLINING_FEATURE_INLINE_ENTRIES, true));
 		optimizer.inlineEnterRegion(getBoolValue(optimizeConfig,
 				FUNCTION_INLINING_FEATURE_INLINE_ENTER_REGION, true));
 		optimizer.inlineExitRegion(getBoolValue(optimizeConfig,

+ 43 - 4
plugins/org.yakindu.sct.model.sexec/src/org/yakindu/sct/model/sexec/transformation/FlowOptimizer.xtend

@@ -23,6 +23,9 @@ import org.yakindu.sct.model.sexec.StateSwitch
 import org.yakindu.sct.model.sexec.StateCase
 import org.eclipse.xtext.EcoreUtil2
 import org.yakindu.sct.model.sexec.ExecutionRegion
+import org.yakindu.sct.model.sexec.HistoryEntry
+import org.yakindu.sct.model.sexec.ExecutionChoice
+import org.yakindu.sct.model.sexec.ExecutionEntry
  
 class FlowOptimizer {
 
@@ -37,6 +40,7 @@ class FlowOptimizer {
 	boolean _inlineExitRegion   def inlineExitRegion(boolean b) {_inlineExitRegion = b}
 	boolean _inlineExitSequences    def inlineExitSequences(boolean b)  {_inlineExitSequences = b}
 	boolean _inlineChoices          def inlineChoices(boolean b)        {_inlineChoices = b}
+	boolean _inlineEntries          def inlineEntries(boolean b)        {_inlineEntries = b}
 	
 	
 	
@@ -55,8 +59,12 @@ class FlowOptimizer {
 
 				
 		if (_inlineChoices) {
-			flow.nodes.forEach( node | { node.reactions.forEach( r | { r.check.inline r.effect.inline }) node })
-			flow.nodes.forEach( node | node.reactSequence.inline )
+			flow.nodes.filter(typeof(ExecutionChoice)).forEach( node | { node.reactions.forEach( r | { r.check.inline r.effect.inline }) node })
+			flow.nodes.filter(typeof(ExecutionChoice)).forEach( node | node.reactSequence.inline )
+		}
+		if (_inlineEntries) {
+			flow.nodes.filter(typeof(ExecutionEntry)).forEach( node | { node.reactions.forEach( r | { r.check.inline r.effect.inline }) node })
+			flow.nodes.filter(typeof(ExecutionEntry)).forEach( node | node.reactSequence.inline )
 		}
 
 		flow
@@ -135,7 +143,7 @@ class FlowOptimizer {
 				val clone = step.stepCopy
 				if ( caller.eContainer.substituteCall(caller, clone) )
 					caller.step = null
-				else System::out.println("Did not substitute '" + step + "'.");
+				else System::out.println("Did not substitute '" + step + "' call from '"+caller.eContainer+"'.");
 			}		
 		}
 		step
@@ -145,7 +153,27 @@ class FlowOptimizer {
 	
 	// CALL SUBSTITUTION
 	def dispatch boolean substituteCall(EObject owner, Call pre, Step post) { false }
-	
+
+	def dispatch boolean substituteCall(StateCase owner, Call pre, Step post) {
+		if (owner.step == pre) {
+			owner.step = post
+			return true
+		}
+		return false
+	}
+
+	def dispatch boolean substituteCall(HistoryEntry owner, Call pre, Step post) {
+		if (owner.initialStep == pre) {
+			owner.initialStep = post
+			return true
+		}
+		if (owner.historyStep == pre) {
+			owner.historyStep = post
+			return true
+		}
+		return false
+	}
+
 	def dispatch boolean substituteCall(Sequence owner, Call call, Step step) {
 		if ( owner.steps.contains(call) ) { 
 			owner.steps.set(owner.steps.indexOf(call), step)
@@ -211,6 +239,17 @@ class FlowOptimizer {
 
 		_copy
 	}
+	def dispatch Step stepCopy(HistoryEntry cref) {
+		val _copy = sexecFactory.createHistoryEntry
+		_copy.name = cref.name
+		_copy.comment = cref.comment
+		_copy.deep =  cref.deep
+		_copy.region =  cref.region
+		_copy.initialStep =  cref.initialStep.stepCopy
+		_copy.historyStep =  cref.historyStep.stepCopy
+
+		_copy
+	}
 	
 	def dispatch Step stepCopy(StateSwitch _switch) {
 		val _copy = sexecFactory.createStateSwitch

+ 2 - 2
plugins/org.yakindu.sct.model.sexec/src/org/yakindu/sct/model/sexec/transformation/SequenceBuilder.xtend

@@ -51,7 +51,7 @@ class SequenceBuilder {
 		for (s : r.vertices.filter(typeof(State))) {
 			s.defineDeepEnterSequence
 		}
-		if (!r.requireHistory) {
+		if (!r.requireDeepHistory) {
 			return
 		}
 		val execRegion = r.create
@@ -96,7 +96,7 @@ class SequenceBuilder {
 	
 	def void defineShallowEnterSequences(ExecutionFlow flow, Statechart sc) {
 		for ( r : sc.allContentsIterable.filter(typeof(Region))) {
-			if (r.requireHistory) {
+			if (r.requireShallowHistory) {
 				val execRegion = r.create
 				val seq = sexec.factory.createSequence
 				seq.name = "shallowEnterSequence"

+ 5 - 4
plugins/org.yakindu.sct.model.sexec/src/org/yakindu/sct/model/sexec/transformation/SgraphExtensions.xtend

@@ -90,12 +90,13 @@ class SgraphExtensions {
 	}
 	
 	def requireDeepHistory(Region r) {
-		r.vertices.filter(typeof(Entry)).exists(v|v.kind == EntryKind::DEEP_HISTORY)
+		 r.containers.filter(typeof(Region)).exists(p|p.vertices.filter(typeof(Entry)).exists(v|v.kind == EntryKind::DEEP_HISTORY))
+	}
+	def requireShallowHistory(Region r) {
+		r.vertices.filter(typeof(Entry)).exists(v|v.kind == EntryKind::SHALLOW_HISTORY)		
 	}
-
 	def requireHistory(Region r) {
-		r.vertices.filter(typeof(Entry)).exists(v|v.kind == EntryKind::SHALLOW_HISTORY)
-		|| r.containers.filter(typeof(Region)).exists(p|p.requireDeepHistory)
+		r.requireDeepHistory || r.requireShallowHistory
 	}
 	
 }

+ 3 - 3
test-plugins/org.yakindu.sct.model.sexec.test/src/org/yakindu/sct/model/sexec/transformation/test/AllTests.java

@@ -5,9 +5,9 @@ import org.junit.runners.Suite;
 import org.junit.runners.Suite.SuiteClasses;
 
 @RunWith(Suite.class)
-@SuiteClasses({ HistoryTest.class, ModelSequencerSCTest.class,
-		ModelSequencertDeclarationsTest.class, ModelSequencerStateTest.class,
-		ModelSequencerStateReactionTest.class,
+@SuiteClasses({ FlowOptimizer_ExecutionEntryTest.class, HistoryTest.class,
+		ModelSequencerSCTest.class, ModelSequencertDeclarationsTest.class,
+		ModelSequencerStateTest.class, ModelSequencerStateReactionTest.class,
 		ModelSequencerHierarchyTest.class,
 		ModelSequencerOrthogonalityTest.class,
 		ModelSequencerStateVectorTest.class, ModelSequencerHistoryTest.class,

+ 135 - 0
test-plugins/org.yakindu.sct.model.sexec.test/src/org/yakindu/sct/model/sexec/transformation/test/FlowOptimizer_ExecutionEntryTest.java

@@ -0,0 +1,135 @@
+package org.yakindu.sct.model.sexec.transformation.test;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.TYPE_INTEGER;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createEntry;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createEntryAssignment;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createInterfaceScope;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createRegion;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createState;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createStatechart;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createTransition;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createVariableDefinition;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.findState;
+
+import java.util.ArrayList;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.ecore.EObject;
+import org.junit.Test;
+import org.yakindu.sct.model.sexec.Call;
+import org.yakindu.sct.model.sexec.ExecutionEntry;
+import org.yakindu.sct.model.sexec.ExecutionFlow;
+import org.yakindu.sct.model.sexec.ExecutionNode;
+import org.yakindu.sct.model.sexec.ExecutionRegion;
+import org.yakindu.sct.model.sexec.ExecutionState;
+import org.yakindu.sct.model.sexec.HistoryEntry;
+import org.yakindu.sct.model.sexec.Reaction;
+import org.yakindu.sct.model.sexec.SaveHistory;
+import org.yakindu.sct.model.sexec.Sequence;
+import org.yakindu.sct.model.sexec.Step;
+import org.yakindu.sct.model.sexec.transformation.FlowOptimizer;
+import org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.MinimalTSC;
+import org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.InitializingTSC;
+import org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.OrthogonalFlatTSC;
+import org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.SimpleFlatTSC;
+import org.yakindu.sct.model.sgraph.Entry;
+import org.yakindu.sct.model.sgraph.EntryKind;
+import org.yakindu.sct.model.sgraph.Region;
+import org.yakindu.sct.model.sgraph.State;
+import org.yakindu.sct.model.sgraph.Statechart;
+import org.yakindu.sct.model.sgraph.Transition;
+import org.yakindu.sct.model.stext.stext.AssignmentOperator;
+import org.yakindu.sct.model.stext.stext.InterfaceScope;
+import org.yakindu.sct.model.stext.stext.VariableDefinition;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.inject.Inject;
+
+public class FlowOptimizer_ExecutionEntryTest extends ModelSequencerTest {
+
+	@Inject
+	FlowOptimizer optimizer;
+
+	@Test
+	public void testNoNullCall() {
+		Statechart sc = _createStatechart("sc");
+		{
+			InterfaceScope s_scope = _createInterfaceScope("Interface", sc);
+			VariableDefinition v1 = _createVariableDefinition("v1",
+					TYPE_INTEGER, s_scope);
+			Region r = _createRegion("r", sc);
+			{
+				Entry r_entry = _createEntry(EntryKind.INITIAL, null, r);
+				State s1 = _createState("s1", r);
+				State s2 = _createState("s2", r);
+				{
+					_createEntryAssignment(v1, s2, 3);
+					Region r2 = _createRegion("r2", s2);
+					{
+						Entry e = _createEntry(EntryKind.INITIAL, null, r2);
+						Entry history = _createEntry(EntryKind.SHALLOW_HISTORY,
+								"history", r2);
+
+						State s3 = _createState("s3", r2);
+						{
+							_createEntryAssignment(v1, s3, 4);
+						}
+						State s4 = _createState("s4", r2);
+						{
+							Region r4 = _createRegion("r4", s4);
+							{
+								Entry e4 = _createEntry(EntryKind.INITIAL,
+										null, r2);
+								State s5 = _createState("s5", r4);
+								_createTransition(e4, s5);
+								_createTransition(s5, s1);
+							}
+						}
+						_createTransition(e, s3);
+						_createTransition(history, s3);
+						_createTransition(s3, s4);
+						_createTransition(s1, history);
+					}
+				}
+				_createTransition(r_entry, s1);
+				_createTransition(s1, s2);
+			}
+		}
+
+		ExecutionFlow flow = sequencer.transform(sc);
+
+		optimizer.inlineChoices(true);
+		optimizer.inlineEnterSequences(true);
+		optimizer.inlineEntryActions(true);
+		optimizer.inlineExitActions(true);
+		optimizer.inlineExitRegion(true);
+		optimizer.inlineExitSequences(true);
+		optimizer.inlineReactions(true);
+		optimizer.inlineEntries(true);
+		optimizer.transform(flow);
+
+		TreeIterator<EObject> iter = flow.eAllContents();
+		while (iter.hasNext()) {
+			EObject child = iter.next();
+			if (child instanceof Call) {
+				Call childCall = (Call) child;
+				if (childCall.getStep() == null) {
+					if (childCall.eContainer() instanceof Sequence) {
+						Sequence sequence = (Sequence) childCall.eContainer();
+						fail(sequence.getName() + ": " + sequence.getComment());
+					} else {
+						fail("Call of null-Step in " + childCall.eContainer());
+					}
+				}
+			}
+		}
+	}
+}

+ 112 - 0
test-plugins/org.yakindu.sct.model.sexec.test/src/org/yakindu/sct/model/sexec/transformation/test/HistoryTest.java

@@ -277,9 +277,121 @@ public class HistoryTest extends ModelSequencerTest {
 		// _s2.getEnterSequence());
 	}
 
+	@Test
 	public void testNoHistory() {
 		SimpleFlatTSC sc = new SCTTestUtil.SimpleFlatTSC();
 		ExecutionFlow flow = sequencer.transform(sc.sc);
 		assertNull(flow.getHistoryVector());
 	}
+
+	@Test
+	public void testHistoryAsEntry() {
+		Statechart sc = _createStatechart("sc");
+		{
+			InterfaceScope s_scope = _createInterfaceScope("Interface", sc);
+			VariableDefinition v1 = _createVariableDefinition("v1",
+					TYPE_INTEGER, s_scope);
+			Region r = _createRegion("r", sc);
+			{
+				Entry r_entry = _createEntry(EntryKind.INITIAL, null, r);
+				State s1 = _createState("s1", r);
+				State s2 = _createState("s2", r);
+				{
+					_createEntryAssignment(v1, s2, 3);
+					Region r2 = _createRegion("r2", s2);
+					{
+						Entry e = _createEntry(EntryKind.DEEP_HISTORY, null, r2);
+
+						State s3 = _createState("s3", r2);
+						{
+							_createEntryAssignment(v1, s3, 4);
+						}
+						State s4 = _createState("s4", r2);
+						{
+							_createEntryAssignment(v1, s4, 6);
+						}
+						_createTransition(e, s3);
+						_createTransition(s3, s4);
+						_createTransition(s4, s3);
+					}
+				}
+				_createTransition(r_entry, s2);
+				_createTransition(s1, s2);
+				_createTransition(s2, s1);
+			}
+		}
+
+		ExecutionFlow flow = sequencer.transform(sc);
+
+		ExecutionState _s2 = flow.getStates().get(1);
+		assertEquals("sc.r.s2", _s2.getName());
+		ExecutionState _s3 = flow.getStates().get(2);
+		assertEquals("sc.r.s2.r2.s3", _s3.getName());
+		ExecutionNode e = flow.getNodes().get(1);
+		assertTrue(e.eClass().getName(), e instanceof ExecutionEntry);
+
+		Sequence reactSequence = e.getReactSequence();
+
+		assertEquals("Default react sequence for deep history entry ",
+				reactSequence.getComment());
+		Step historyEntryStep = reactSequence.getSteps().get(0);
+		assertTrue(historyEntryStep.eClass().getName(),
+				historyEntryStep instanceof HistoryEntry);
+
+		HistoryEntry historyEntry = (HistoryEntry) historyEntryStep;
+		assertCall(historyEntry.getInitialStep(), _s3.getEnterSequence());
+		ExecutionRegion _r2 = (ExecutionRegion) _s3.getSuperScope();
+		assertCall(historyEntry.getHistoryStep(), _r2.getDeepEnterSequence());
+
+		Step _s3EnterStep = assertedStateCase(
+				_r2.getDeepEnterSequence().getSteps().get(0), _s3).getStep();
+		Step _s3EnterCall = assertedSequence(_s3EnterStep).getSteps().get(0);
+		assertCall(_s3EnterCall, _s3.getEnterSequence());
+	}
+
+	@Test
+	public void testEnterStatechart() {
+		Statechart sc = _createStatechart("sc");
+		{
+			InterfaceScope s_scope = _createInterfaceScope("Interface", sc);
+			VariableDefinition v1 = _createVariableDefinition("v1",
+					TYPE_INTEGER, s_scope);
+			Region r = _createRegion("r", sc);
+			{
+				Entry r_entry = _createEntry(EntryKind.INITIAL, null, r);
+				State s1 = _createState("s1", r);
+				State s2 = _createState("s2", r);
+				{
+					_createEntryAssignment(v1, s2, 3);
+					Region r2 = _createRegion("r2", s2);
+					{
+						Entry e = _createEntry(EntryKind.DEEP_HISTORY, null, r2);
+
+						State s3 = _createState("s3", r2);
+						{
+							_createEntryAssignment(v1, s3, 4);
+						}
+						State s4 = _createState("s4", r2);
+						{
+							_createEntryAssignment(v1, s4, 6);
+						}
+						_createTransition(e, s3);
+						_createTransition(s3, s4);
+						_createTransition(s4, s3);
+					}
+				}
+				_createTransition(r_entry, s2);
+				_createTransition(s1, s2);
+				_createTransition(s2, s1);
+			}
+		}
+
+		ExecutionFlow flow = sequencer.transform(sc);
+		ExecutionState _s1 = flow.getStates().get(0);
+		assertEquals("sc.r.s1", _s1.getName());
+		Sequence enterSequence = flow.getEnterSequence();
+		ExecutionRegion _r = (ExecutionRegion) _s1.getSuperScope();
+
+		assertCall(enterSequence, 0, _r.getEnterSequence());
+	}
 }

+ 82 - 0
test-plugins/org.yakindu.sct.model.sexec.test/src/org/yakindu/sct/model/sexec/transformation/test/ModelSequencerSCTest.java

@@ -2,16 +2,36 @@ package org.yakindu.sct.model.sexec.transformation.test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.TYPE_INTEGER;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createEntry;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createEntryAssignment;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createInterfaceScope;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createRegion;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createState;
 import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createStatechart;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createTransition;
+import static org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil._createVariableDefinition;
 
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.ecore.EObject;
 import org.junit.Test;
+import org.yakindu.sct.model.sexec.Call;
 import org.yakindu.sct.model.sexec.ExecutionFlow;
+import org.yakindu.sct.model.sexec.Sequence;
+import org.yakindu.sct.model.sexec.Step;
 import org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.MinimalTSC;
 import org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.InitializingTSC;
 import org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.OrthogonalFlatTSC;
 import org.yakindu.sct.model.sexec.transformation.test.SCTTestUtil.SimpleFlatTSC;
+import org.yakindu.sct.model.sgraph.Entry;
+import org.yakindu.sct.model.sgraph.EntryKind;
+import org.yakindu.sct.model.sgraph.Region;
+import org.yakindu.sct.model.sgraph.State;
 import org.yakindu.sct.model.sgraph.Statechart;
 import org.yakindu.sct.model.stext.stext.AssignmentOperator;
+import org.yakindu.sct.model.stext.stext.InterfaceScope;
+import org.yakindu.sct.model.stext.stext.VariableDefinition;
 
 public class ModelSequencerSCTest extends ModelSequencerTest {
 
@@ -99,4 +119,66 @@ public class ModelSequencerSCTest extends ModelSequencerTest {
 		assertCall(flow.getExitSequence(), 0, flow.getSubScopes().get(0)
 				.getExitSequence());
 	}
+
+	@Test
+	public void testNoNullCall() {
+		Statechart sc = _createStatechart("sc");
+		{
+			InterfaceScope s_scope = _createInterfaceScope("Interface", sc);
+			VariableDefinition v1 = _createVariableDefinition("v1",
+					TYPE_INTEGER, s_scope);
+			Region r = _createRegion("r", sc);
+			{
+				Entry r_entry = _createEntry(EntryKind.INITIAL, null, r);
+				State s1 = _createState("s1", r);
+				State s2 = _createState("s2", r);
+				{
+					_createEntryAssignment(v1, s2, 3);
+					Region r2 = _createRegion("r2", s2);
+					{
+						Entry e = _createEntry(EntryKind.INITIAL, null, r2);
+						Entry history = _createEntry(EntryKind.SHALLOW_HISTORY,
+								"history", r2);
+
+						State s3 = _createState("s3", r2);
+						{
+							_createEntryAssignment(v1, s3, 4);
+						}
+						State s4 = _createState("s4", r2);
+						{
+							Region r4 = _createRegion("r4", s4);
+							{
+								Entry e4 = _createEntry(EntryKind.INITIAL,
+										null, r2);
+								State s5 = _createState("s5", r4);
+								_createTransition(e4, s5);
+								_createTransition(s5, s1);
+							}
+						}
+						_createTransition(e, s3);
+						_createTransition(history, s3);
+						_createTransition(s3, s4);
+						_createTransition(s1, history);
+					}
+				}
+				_createTransition(r_entry, s1);
+				_createTransition(s1, s2);
+			}
+		}
+
+		ExecutionFlow flow = sequencer.transform(sc);
+
+		TreeIterator<EObject> iter = flow.eAllContents();
+		while (iter.hasNext()) {
+			EObject child = iter.next();
+			if (child instanceof Call) {
+				Call childCall = (Call) child;
+				if (childCall.getStep() == null) {
+					Sequence sequence = assertedSequence((Step) childCall
+							.eContainer());
+					fail(sequence.getName() + ": " + sequence.getComment());
+				}
+			}
+		}
+	}
 }