/** A valid pattern file to test the pattern segment **/
VALID_PATTERN_SEGMENT("test_xml_files/test_valid/test_pattern_segment.xml"),
/** A valid file for consuming fsm test */
- CONSUMING_FSM_TEST("test_xml_files/test_valid/test_consuming_fsm.xml");
+ CONSUMING_FSM_TEST("test_xml_files/test_valid/test_consuming_fsm.xml"),
+ /** A valid pattern file to test the initialState element */
+ INITIAL_STATE_ELEMENT_TEST_FILE_1("test_xml_files/test_valid/test_initialState_element1.xml"),
+ /** A valid pattern file to test the initialState element */
+ INITIAL_STATE_ELEMENT_TEST_FILE_2("test_xml_files/test_valid/test_initialState_element2.xml");
private final String fPath;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.eclipse.tracecompass.tmf.analysis.xml.core.tests.model;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model.TmfXmlPatternSegmentBuilder;
+import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.pattern.stateprovider.XmlPatternAnalysis;
+import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.segment.TmfXmlPatternSegment;
+import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.stateprovider.TmfXmlStrings;
+import org.eclipse.tracecompass.segmentstore.core.ISegment;
+import org.eclipse.tracecompass.segmentstore.core.ISegmentStore;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
+import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
+import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
+import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
+import org.eclipse.tracecompass.tmf.analysis.xml.core.tests.common.TmfXmlTestFiles;
+import org.eclipse.tracecompass.tmf.analysis.xml.core.tests.module.XmlUtilsTest;
+import org.eclipse.tracecompass.tmf.analysis.xml.core.tests.stateprovider.XmlModuleTestBase;
+import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * Test the pattern analysis fsm
+ *
+ * @author Jean-Christian Kouame
+ */
+public class FsmTest {
+ private static final int END_TIME = 7;
+ private static final @NonNull String TEST_TRACE = "test_traces/testTrace4.xml";
+ private static final String TEST_SEGMENT_NEW = TmfXmlPatternSegmentBuilder.PATTERN_SEGMENT_NAME_PREFIX + "NEW";
+ private static final TmfXmlTestFiles TEST_FILE_1 = TmfXmlTestFiles.INITIAL_STATE_ELEMENT_TEST_FILE_1;
+ private static final TmfXmlTestFiles TEST_FILE_2 = TmfXmlTestFiles.INITIAL_STATE_ELEMENT_TEST_FILE_2;
+ private static XmlPatternAnalysis fModule;
+ private static XmlPatternAnalysis fModule2;
+ private static ITmfTrace fTrace;
+
+ private static XmlPatternAnalysis createModule(@NonNull Element element, TmfXmlTestFiles file) {
+ XmlPatternAnalysis module = new XmlPatternAnalysis();
+ module.setXmlFile(new Path(file.getFile().getAbsolutePath()));
+ module.setName(XmlModuleTestBase.getName(element));
+ return module;
+ }
+
+ private static XmlPatternAnalysis initModule(TmfXmlTestFiles file) {
+ Document doc = file.getXmlDocument();
+ assertNotNull(doc);
+
+ /* get State Providers modules */
+ NodeList stateproviderNodes = doc.getElementsByTagName(TmfXmlStrings.PATTERN);
+
+ Element node = (Element) stateproviderNodes.item(0);
+ assertNotNull(node);
+
+ XmlPatternAnalysis module = createModule(node, file);
+
+ String moduleId = node.getAttribute(TmfXmlStrings.ID);
+ assertNotNull(moduleId);
+ module.setId(moduleId);
+
+ return module;
+ }
+
+ /**
+ * End the test suite
+ */
+ @AfterClass
+ public static void tearDown() {
+ fModule.dispose();
+ fModule2.dispose();
+ fTrace.dispose();
+ }
+
+ /**
+ * Before the test suite
+ */
+ @BeforeClass
+ public static void before() {
+ ITmfTrace trace = XmlUtilsTest.initializeTrace(TEST_TRACE);
+ //Create first module
+ fModule = initModule(TEST_FILE_1);
+ try {
+ fModule.setTrace(trace);
+ fModule.schedule();
+ assertTrue(fModule.waitForCompletion(new NullProgressMonitor()));
+ } catch (TmfAnalysisException e) {
+ fail("Cannot execute analyses " + e.getMessage());
+ }
+
+ //Create second module
+ fModule2 = initModule(TEST_FILE_2);
+ try {
+ fModule2.setTrace(trace);
+ fModule2.schedule();
+ assertTrue(fModule2.waitForCompletion(new NullProgressMonitor()));
+ } catch (TmfAnalysisException e) {
+ fail("Cannot execute analyses " + e.getMessage());
+ }
+ fTrace = trace;
+ }
+
+ /**
+ * Compare the execution of two state machines that do the same job, one
+ * using the initial element, the second one using the initialState element.
+ * The result should be the same for both state machines
+ */
+ @Test
+ public void testInitialStateDeclaration() {
+ ITmfStateSystem stateSystem = fModule.getStateSystem(fModule.getId());
+ assertNotNull("state system exist", stateSystem);
+ try {
+ int quark = stateSystem.getQuarkAbsolute("fsm1");
+ @NonNull ITmfStateInterval interval = stateSystem.querySingleState(END_TIME, quark);
+ long count1 = interval.getStateValue().unboxLong();
+
+ quark = stateSystem.getQuarkAbsolute("fsm2");
+ interval = stateSystem.querySingleState(END_TIME, quark);
+ long count2 = interval.getStateValue().unboxLong();
+ assertTrue("Test the count value", count1 == count2);
+ } catch (AttributeNotFoundException | StateSystemDisposedException e) {
+ fail("Failed to query the state system");
+ }
+ }
+
+ /**
+ * Compare the execution of two state machines doing the same job, the tid
+ * condition is ignored with the initial element and used with the
+ * initialState element. The result should be different.
+ */
+ @Test
+ public void testInitialStateWithCondition() {
+ ITmfStateSystem stateSystem = fModule.getStateSystem(fModule.getId());
+ assertNotNull("state system exist", stateSystem);
+ try {
+ int quark = stateSystem.getQuarkAbsolute("fsm1");
+ @NonNull ITmfStateInterval interval = stateSystem.querySingleState(END_TIME, quark);
+ long count1 = interval.getStateValue().unboxLong();
+
+ quark = stateSystem.getQuarkAbsolute("fsm3");
+ interval = stateSystem.querySingleState(END_TIME, quark);
+ long count3 = interval.getStateValue().unboxLong();
+ assertTrue("Test the count value", count1 > count3);
+ } catch (AttributeNotFoundException | StateSystemDisposedException e) {
+ fail("Failed to query the state system");
+ }
+ }
+
+ /**
+ * Execute one pattern, with the two types of initial state initialization,
+ * then test that the new behavior is prioritized and that preconditions are
+ * ignored with initialState element
+ */
+ @Test
+ public void testTwoInitialStates() {
+ //Test segment store
+ @Nullable ISegmentStore<@NonNull ISegment> ss = fModule2.getSegmentStore();
+ assertNotNull("segment store exist", ss);
+ assertTrue("Segment store not empty", ss.size() == 1);
+ Object item = ss.toArray()[0];
+ assertTrue(item instanceof TmfXmlPatternSegment);
+ assertTrue(((TmfXmlPatternSegment) item).getName().equals(TEST_SEGMENT_NEW));
+
+ //Test state system
+ ITmfStateSystem stateSystem = fModule2.getStateSystem(fModule2.getId());
+ assertNotNull("state system exist", stateSystem);
+ int quark;
+ try {
+ quark = stateSystem.getQuarkAbsolute("count_new");
+ @NonNull ITmfStateInterval interval = stateSystem.querySingleState(END_TIME, quark);
+ int count = interval.getStateValue().unboxInt();
+ assertTrue("Test the count value", count > 0);
+ } catch (AttributeNotFoundException | StateSystemDisposedException e) {
+ fail("Failed to query the state system");
+ }
+
+ try {
+ quark = stateSystem.getQuarkAbsolute("precond");
+ } catch (AttributeNotFoundException e) {
+ return;
+ }
+ fail();
+ }
+}
* The analysis element
* @return The name
*/
- public @NonNull String getName(Element element) {
+ public static @NonNull String getName(Element element) {
String name = null;
List<Element> head = XmlUtils.getChildElements(element, TmfXmlStrings.HEAD);
if (head.size() == 1) {
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<tmfxml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="xmlDefinition.xsd">
+<!-- ***************************************************************************
+* Copyright (c) 2016 Ericsson
+*
+* All rights reserved. This program and the accompanying materials are
+* made available under the terms of the Eclipse Public License v1.0 which
+* accompanies this distribution, and is available at
+* http://www.eclipse.org/legal/epl-v10.html
+*************************************************************************** -->
+<pattern version="0" id="test.analysis.1">
+ <head>
+ <traceType id="org.eclipse.linuxtools.lttng2.kernel.tracetype" />
+ <label value="XML test analysis 1" />
+ </head>
+
+ <patternHandler>
+
+<!-- This condition check if the current running thread PID is 496 -->
+ <test id="curState">
+ <if>
+ <condition>
+ <field name="curState" />
+ <stateValue type="string" value="GOOD" />
+ </condition>
+ </if>
+ </test>
+
+ <action id="increment_fsm1_counter">
+ <stateChange>
+ <stateAttribute type="constant" value="fsm1" />
+ <stateValue type="long" value="1" increment="true" />
+ </stateChange>
+ </action>
+
+ <action id="increment_fsm2_counter">
+ <stateChange>
+ <stateAttribute type="constant" value="fsm2" />
+ <stateValue type="long" value="1" increment="true" />
+ </stateChange>
+ </action>
+
+ <action id="increment_fsm3_counter">
+ <stateChange>
+ <stateAttribute type="constant" value="fsm3" />
+ <stateValue type="long" value="1" increment="true" />
+ </stateChange>
+ </action>
+
+ <fsm id="fsm1">
+ <initial>
+ <transition cond="curState" target="state1"/>
+ </initial>
+ <state id="state1">
+ <transition event="exit" target="end" action="increment_fsm1_counter" />
+ </state>
+ <final id="end" />
+ </fsm>
+
+ <fsm id="fsm2">
+ <initialState>
+ <transition event="exit" target="end" action="increment_fsm2_counter" />
+ </initialState>
+ <final id="end" />
+ </fsm>
+
+ <fsm id="fsm3">
+ <initialState>
+ <transition event="exit" cond="curState" target="end" action="increment_fsm3_counter"/>
+ </initialState>
+ <final id="end" />
+ </fsm>
+ </patternHandler>
+</pattern>
+</tmfxml>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<tmfxml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="xmlDefinition.xsd">
+<!-- ***************************************************************************
+* Copyright (c) 2016 Ericsson
+*
+* All rights reserved. This program and the accompanying materials are
+* made available under the terms of the Eclipse Public License v1.0 which
+* accompanies this distribution, and is available at
+* http://www.eclipse.org/legal/epl-v10.html
+*************************************************************************** -->
+<pattern version="0" id="test.analysis.2">
+ <head>
+ <traceType id="org.eclipse.linuxtools.lttng2.kernel.tracetype" />
+ <label value="XML test analysis 2" />
+ </head>
+
+ <patternHandler>
+
+ <action id="increment_counter_new">
+ <stateChange>
+ <stateAttribute type="constant" value="count_new"/>
+ <stateValue type="int" value="1" increment="true" />
+ </stateChange>
+ </action>
+
+ <action id="increment_counter_precond">
+ <stateChange>
+ <stateAttribute type="constant" value="precond"/>
+ <stateValue type="int" value="1" increment="true" />
+ </stateChange>
+ </action>
+
+ <action id="generate_old_segment">
+ <segment>
+ <segType segName="OLD"/>
+ </segment>
+ </action>
+
+ <action id="generate_new_segment">
+ <segment>
+ <segType segName="NEW"/>
+ </segment>
+ </action>
+
+ <fsm id="test" initial="state_old" multiple="false">
+ <initial>
+ <transition target="state_old" />
+ </initial>
+ <initialState>
+ <transition target="state_new" />
+ </initialState>
+ <state id="state_old">
+ <transition target="end" action="generate_old_segment"/>
+ </state>
+ <state id="state_new">
+ <transition target="end" action="generate_new_segment"/>
+ </state>
+ <final id="end"/>
+ </fsm>
+
+ <fsm id="test1" multiple="false">
+ <precondition event="wrong_event" />
+ <initialState>
+ <transition target="state_new" action="increment_counter_new"/>
+ </initialState>
+ <state id="state_new">
+ <transition target="state_2" action="increment_counter_precond"/>
+ </state>
+ <state id="state_2">
+ <transition target="end" action="increment_counter_precond"/>
+ </state>
+
+ <final id="end"/>
+ </fsm>
+ </patternHandler>
+</pattern>
+</tmfxml>
\ No newline at end of file
import org.eclipse.osgi.util.NLS;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.Activator;
-import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model.TmfXmlScenarioHistoryBuilder.ScenarioStatusType;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.IXmlStateSystemContainer;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.stateprovider.TmfXmlStrings;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
private final boolean fConsuming;
private boolean fEventConsumed;
private int fTotalScenarios;
+ private @Nullable TmfXmlScenario fPendingScenario;
/**
* Factory to create a {@link TmfXmlFsm}
}
// Get the initial state and the preconditions
+ Map<@NonNull String, @NonNull TmfXmlState> statesMap = new HashMap<>();
String initialState = node.getAttribute(TmfXmlStrings.INITIAL);
- if (initialState.isEmpty()) {
- NodeList nodesInitialState = node.getElementsByTagName(TmfXmlStrings.INITIAL);
- if (nodesInitialState.getLength() == 1) {
- NodeList nodesTransition = ((Element) nodesInitialState.item(0)).getElementsByTagName(TmfXmlStrings.TRANSITION);
- if (nodesInitialState.getLength() != 1) {
- throw new IllegalArgumentException("initial state : there should be one and only one initial state."); //$NON-NLS-1$
- }
- initialState = ((Element) nodesTransition.item(0)).getAttribute(TmfXmlStrings.TARGET);
+ NodeList nodesInitialElement = node.getElementsByTagName(TmfXmlStrings.INITIAL);
+ NodeList nodesInitialStateElement = node.getElementsByTagName(TmfXmlStrings.INITIAL_STATE);
+ if (nodesInitialStateElement.getLength() > 0) {
+ if (!initialState.isEmpty() || nodesInitialElement.getLength() > 0) {
+ Activator.logWarning("Fsm " + id + ": the 'initial' attribute was set or an <initial> element was defined. Only one of the 3 should be used."); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ @NonNull TmfXmlState initial = modelFactory.createState((Element) nodesInitialStateElement.item(0), container, null);
+ statesMap.put(TmfXmlState.INITIAL_STATE_ID, initial);
+ initialState = TmfXmlState.INITIAL_STATE_ID;
+ } else {
+ if (!initialState.isEmpty() && nodesInitialElement.getLength() > 0) {
+ Activator.logWarning("Fsm " + id + " was declared with both 'initial' attribute and <initial> element. Only the 'initial' attribute will be used"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (initialState.isEmpty() && nodesInitialElement.getLength() > 0) {
+ NodeList nodesTransition = ((Element) nodesInitialElement.item(0)).getElementsByTagName(TmfXmlStrings.TRANSITION);
+ if (nodesInitialElement.getLength() != 1) {
+ throw new IllegalArgumentException("initial element : there should be one and only one initial state."); //$NON-NLS-1$
+ }
+ initialState = ((Element) nodesTransition.item(0)).getAttribute(TmfXmlStrings.TARGET);
}
}
- Map<@NonNull String, @NonNull TmfXmlState> statesMap = new HashMap<>();
+
// Get the FSM states
NodeList nodesState = node.getElementsByTagName(TmfXmlStrings.STATE);
for (int i = 0; i < nodesState.getLength(); i++) {
*
* @param event
* The current event
- * @param transitionMap
+ * @param testMap
* The transitions of the pattern
*/
- public void handleEvent(ITmfEvent event, Map<String, TmfXmlTransitionValidator> transitionMap) {
+ public void handleEvent(ITmfEvent event, Map<String, TmfXmlTransitionValidator> testMap) {
setEventConsumed(false);
- if (!validatePreconditions(event, transitionMap)) {
- return;
+ boolean isValidInput = handleActiveScenarios(event, testMap);
+ handlePendingScenario(event, isValidInput);
+ }
+
+ /**
+ * Process the active scenario with the ongoing event
+ *
+ * @param event
+ * The ongoing event
+ * @param testMap
+ * The map of transition
+ * @return True if the ongoing event validates the preconditions, false otherwise
+ */
+ private boolean handleActiveScenarios(ITmfEvent event, Map<String, TmfXmlTransitionValidator> testMap) {
+ if (!validatePreconditions(event, testMap)) {
+ return false;
}
+
+ // The event is valid, we can handle the active scenario
for (Iterator<TmfXmlScenario> currentItr = fActiveScenariosList.iterator(); currentItr.hasNext();) {
TmfXmlScenario scenario = currentItr.next();
// Remove inactive scenarios or handle the active ones.
} else {
handleScenario(scenario, event);
if (fConsuming && isEventConsumed()) {
- return;
+ return true;
}
}
}
+ // The event is valid but hasn't been consumed. We return true.
+ return true;
+ }
+
+ /**
+ * Handle the pending scenario.
+ *
+ * @param event
+ * The ongoing event
+ * @param isInputValid
+ * Either the ongoing event validated the preconditions or not
+ */
+ private void handlePendingScenario(ITmfEvent event, boolean isInputValid) {
+ if (fConsuming && isEventConsumed()) {
+ return;
+ }
+
+ TmfXmlScenario scenario = fPendingScenario;
+ if ((fInitialStateId.equals(TmfXmlState.INITIAL_STATE_ID) || isInputValid) && scenario != null) {
+ handleScenario(scenario, event);
+ if (!scenario.isPending()) {
+ addActiveScenario(scenario);
+ fPendingScenario = null;
+ }
+ }
}
/**
}
private static void handleScenario(TmfXmlScenario scenario, ITmfEvent event) {
- if (scenario.isActive()) {
+ if (scenario.isActive() || scenario.isPending()) {
scenario.handleEvent(event);
}
}
*/
public synchronized void createScenario(@Nullable ITmfEvent event, TmfXmlPatternEventHandler eventHandler, boolean force) {
if (force || isNewScenarioAllowed()) {
- TmfXmlScenario scenario = new TmfXmlScenario(event, eventHandler, fId, fContainer, fModelFactory);
+ fPendingScenario = new TmfXmlScenario(event, eventHandler, fId, fContainer, fModelFactory);
fTotalScenarios++;
- fActiveScenariosList.add(scenario);
}
}
+ /**
+ * Add a scenario to the active scenario list
+ *
+ * @param scenario
+ * The scenario
+ */
+ private void addActiveScenario(TmfXmlScenario scenario) {
+ fActiveScenariosList.add(scenario);
+ }
+
/**
* Check if we have the right to create a new scenario. A new scenario could
* be created if it is not the first scenario of an FSM and the FSM is not a
* @return True if the start of a new scenario is allowed, false otherwise
*/
public synchronized boolean isNewScenarioAllowed() {
- return fTotalScenarios > 0
- && !fActiveScenariosList.get(fActiveScenariosList.size() - 1).getScenarioInfos().getStatus().equals(ScenarioStatusType.PENDING)
- && fInstanceMultipleEnabled;
+ return fTotalScenarios > 0 && fInstanceMultipleEnabled
+ && fPendingScenario == null;
}
}
* @return True if the scenario is active, false otherwise
*/
public boolean isActive() {
- return fScenarioInfo.getStatus().equals(ScenarioStatusType.PENDING) || fScenarioInfo.getStatus().equals(ScenarioStatusType.IN_PROGRESS);
+ return fScenarioInfo.getStatus() == ScenarioStatusType.IN_PROGRESS;
+ }
+
+ /**
+ * Test if the scenario is pending or not
+ *
+ * @return True if the scenario is pending, false otherwise
+ */
+ public boolean isPending() {
+ return fScenarioInfo.getStatus() == ScenarioStatusType.PENDING;
}
/**
*/
public class TmfXmlState {
+ /** The initial state ID */
+ public static final String INITIAL_STATE_ID = "#initial"; //$NON-NLS-1$
private final String fId;
private final IXmlStateSystemContainer fContainer;
private final List<TmfXmlStateTransition> fTransitions;
<xs:documentation>Declares a precondition for this fsm. At least one of the preconditions needs to be validated before being able to activate process the fsm. A precondition is a special transition with no target or action. It should contains only conditions that needs to be validated. Only used for fsm.</xs:documentation></xs:annotation></xs:element>
<xs:element maxOccurs="1" minOccurs="0" name="initial" type="initialState">
<xs:annotation>
- <xs:documentation>Declares the default initial state of this complex state. Must not be specified for an atomic state</xs:documentation></xs:annotation></xs:element>
+ <xs:documentation>Declares the default initial state of this complex state. Must not be specified for an atomic state. When define, the scenario will start at the state declared in the target attribute of this initial state's transition.</xs:documentation></xs:annotation></xs:element>
+ <xs:element maxOccurs="1" minOccurs="0" name="initialState" type="initialState">
+ <xs:annotation>
+ <xs:documentation>Declares the default initial state of this complex state. Must not be specified for an atomic state. When define this initial state is the starting state of the scenario. The scenario will stay at this state until one of its transition is validated.</xs:documentation></xs:annotation></xs:element>
<xs:element maxOccurs="unbounded" minOccurs="0" name="state" type="state">
<xs:annotation>
<xs:documentation>Declares an state as children of this declared state state.</xs:documentation></xs:annotation></xs:element>
String CONSUMING = "consuming";
String MAPPING_GROUP = "mappingGroup";
String ENTRY = "entry";
+ String INITIAL_STATE = "initialState";
}
\ No newline at end of file