From 15e43e7d8427d1981708d129cc4ce2003c6e6f8e Mon Sep 17 00:00:00 2001 From: Tamino Dauth Date: Tue, 14 May 2019 15:02:39 +0200 Subject: [PATCH] Provide createState with a counter for never explored action types #34 - getState returns an Option - Remove convenient method executeAction based on SutStates - Store only a counter not a set of ActionIdentifiers - Adapt unit tests --- .../guistatemachine/api/GuiStateMachine.scala | 19 ++++++++--- .../de/retest/guistatemachine/api/State.scala | 10 ++---- .../api/impl/GuiStateMachineImpl.scala | 34 +++++++++++-------- .../guistatemachine/api/impl/StateImpl.scala | 14 ++++---- .../api/impl/GuiStateMachineImplSpec.scala | 31 +++++++++++------ .../api/impl/StateImplSpec.scala | 10 +++--- .../GuiStateMachineGMLSerializerSpec.scala | 14 +++++--- ...achineJavaObjectStreamSerializerSpec.scala | 4 ++- 8 files changed, 80 insertions(+), 56 deletions(-) diff --git a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala index 514c931..b50a293 100644 --- a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala +++ b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala @@ -13,7 +13,20 @@ import de.retest.surili.commons.actions.Action */ trait GuiStateMachine { - def getState(sutStateIdentifier: SutStateIdentifier): State + /** + * Creates a new state in the state machine based on a SUT state. + * @param sutStateIdentifier The abstract representation of a SUT state which should uniquely identify the state. + * @param neverExploredActionTypesCounter The initial number of unexplored action types. This number is decremented with each unexplored action which is added as a new transition. + * @return The newly created state. + * @throws RuntimeException If the state does already exist. + * @group possibleactions + */ + def createState(sutStateIdentifier: SutStateIdentifier, neverExploredActionTypesCounter: Int): State + + def createState(sutState: SutState, neverExploredActionTypesCounter: Int): State = + createState(new SutStateIdentifier(sutState), neverExploredActionTypesCounter) + + def getState(sutStateIdentifier: SutStateIdentifier): Option[State] /** * Gets a state identified by the corresponding SUT state. @@ -21,7 +34,7 @@ trait GuiStateMachine { * @param sutState The SUT state which identifies the state. * @return The state identified by the descriptors. If there has not been any state yet, it will be added. */ - def getState(sutState: SutState): State = getState(new SutStateIdentifier(sutState)) + def getState(sutState: SutState): Option[State] = getState(new SutStateIdentifier(sutState)) /** * Executes an action from a state leading to the current state described by descriptors. @@ -33,8 +46,6 @@ trait GuiStateMachine { */ def executeAction(from: State, a: ActionIdentifier, to: State): Int = from.addTransition(a, to) def executeAction(from: State, a: Action, to: State): Int = executeAction(from, new ActionIdentifier(a), to) - def executeAction(fromSutState: SutState, a: Action, toSutState: SutState): Int = - executeAction(getState(fromSutState), a, getState(toSutState)) def getAllStates: Map[SutStateIdentifier, State] diff --git a/src/main/scala/de/retest/guistatemachine/api/State.scala b/src/main/scala/de/retest/guistatemachine/api/State.scala index 0483216..c95916f 100644 --- a/src/main/scala/de/retest/guistatemachine/api/State.scala +++ b/src/main/scala/de/retest/guistatemachine/api/State.scala @@ -27,16 +27,10 @@ trait State { * @groupname possibleactions Possible Actions * The set of possible actions has to be restricted for certain action types like ChangeValueAction. The set should always be the same for the same elements per state. It can be used for exploration strategies. * - * @param possibleActions The possible actions of the state. + * @return The number of unexplored action types in this state. * @group possibleactions */ - def setPossibleActions(possibleActions: Set[ActionIdentifier]) - - /** - * @return The possible actions of the state. - * @group possibleactions - */ - def getPossibleActions: Set[ActionIdentifier] + def getNeverExploredActionTypesCounter: Int /** * Overriding this method is required to allow the usage of a set of states. diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala index 2d9eb11..87bff87 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala @@ -3,7 +3,7 @@ package de.retest.guistatemachine.api.impl import com.typesafe.scalalogging.Logger import de.retest.guistatemachine.api.{GuiStateMachine, State, SutStateIdentifier} -import scala.collection.concurrent.TrieMap +import scala.collection.mutable.HashMap /** * Thread-safe implementation of a GUI state machine. @@ -11,25 +11,31 @@ import scala.collection.concurrent.TrieMap @SerialVersionUID(1L) class GuiStateMachineImpl extends GuiStateMachine with Serializable { @transient private val logger = Logger[GuiStateMachineImpl] - private var states = TrieMap[SutStateIdentifier, State]() - - override def getState(sutState: SutStateIdentifier): State = - if (states.contains(sutState)) { - states(sutState) - } else { - logger.info(s"Create new state from SUT state $sutState") - val s = StateImpl(sutState) - states += (sutState -> s) - s + private var states = HashMap[SutStateIdentifier, State]() + + override def createState(sutStateIdentifier: SutStateIdentifier, neverExploredActionTypesCounter: Int): State = + this.synchronized { + if (states.contains(sutStateIdentifier)) { + throw new RuntimeException(s"State from SUT state $sutStateIdentifier does already exist.") + } else { + logger.info(s"Create new state from SUT state $sutStateIdentifier") + val s = StateImpl(sutStateIdentifier, neverExploredActionTypesCounter) + states += (sutStateIdentifier -> s) + s + } } + override def getState(sutStateIdentifier: SutStateIdentifier): Option[State] = this.synchronized { + states.get(sutStateIdentifier) + } + override def getAllStates: Map[SutStateIdentifier, State] = states.toMap - override def clear(): Unit = { - states = TrieMap[SutStateIdentifier, State]() + override def clear(): Unit = this.synchronized { + states.clear() } - override def assignFrom(other: GuiStateMachine): Unit = { + override def assignFrom(other: GuiStateMachine): Unit = this.synchronized { clear() val otherStateMachine = other.asInstanceOf[GuiStateMachineImpl] states = otherStateMachine.states diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala index aba16f8..452e21a 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala @@ -2,10 +2,12 @@ package de.retest.guistatemachine.api.impl import de.retest.guistatemachine.api.{ActionIdentifier, ActionTransitions, State, SutStateIdentifier} -import scala.collection.immutable.{HashMap, HashSet} +import scala.collection.immutable.HashMap @SerialVersionUID(1L) -case class StateImpl(sutState: SutStateIdentifier) extends State with Serializable { +case class StateImpl(sutState: SutStateIdentifier, var neverExploredActionTypesCounter: Int) + extends State + with Serializable { /** * Currently, there is no MultiMap trait for immutable maps in the Scala standard library. @@ -18,8 +20,6 @@ case class StateImpl(sutState: SutStateIdentifier) extends State with Serializab */ private var incomingActionTransitions = HashMap[ActionIdentifier, ActionTransitions]() - private var possibleActions: Set[ActionIdentifier] = HashSet[ActionIdentifier]() - override def getSutStateIdentifier: SutStateIdentifier = this.synchronized { sutState } override def getOutgoingActionTransitions: Map[ActionIdentifier, ActionTransitions] = this.synchronized { outgoingActionTransitions @@ -27,10 +27,7 @@ case class StateImpl(sutState: SutStateIdentifier) extends State with Serializab override def getIncomingActionTransitions: Map[ActionIdentifier, ActionTransitions] = this.synchronized { incomingActionTransitions } - override def setPossibleActions(possibleActions: Set[ActionIdentifier]): Unit = this.synchronized { - this.possibleActions = possibleActions - } - override def getPossibleActions: Set[ActionIdentifier] = this.synchronized { possibleActions } + override def getNeverExploredActionTypesCounter: Int = this.synchronized { neverExploredActionTypesCounter } private[api] override def addTransition(a: ActionIdentifier, to: State): Int = { val executionCounter = this.synchronized { @@ -42,6 +39,7 @@ case class StateImpl(sutState: SutStateIdentifier) extends State with Serializab case None => outgoingActionTransitions += (a -> ActionTransitions(Set(to), 1)) + neverExploredActionTypesCounter = neverExploredActionTypesCounter - 1 1 } } diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala index c91ad90..bcc874d 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala @@ -28,69 +28,78 @@ class GuiStateMachineImplSpec extends AbstractApiSpec with BeforeAndAfterEach { differentState.equals(s0) shouldBe false differentState.hashCode() should not equal s0.hashCode() sut.getAllStates.size shouldEqual 0 - sut.getState(s0) + sut.createState(s0, 0) sut.getAllStates.size shouldEqual 1 - sut.getState(s0Equal) + the[RuntimeException] thrownBy sut.createState(s0Equal, 0) sut.getAllStates.size shouldEqual 1 - sut.getState(differentState) + sut.createState(differentState, 0) sut.getAllStates.size shouldEqual 2 } "add two transitions to two new states for the same action and two transitions for the same action to another state" in { val initialSutState = createSutState(rootElementA, rootElementB, rootElementC) - val initial = sut.getState(initialSutState) + val initial = sut.createState(initialSutState, 2) + initial.getNeverExploredActionTypesCounter shouldEqual 2 // execute action0 for the first time val s0SutState = createSutState(rootElementA) - val s0 = sut.getState(s0SutState) - sut.executeAction(initialSutState, action0, s0SutState) shouldEqual 1 + val s0 = sut.createState(s0SutState, 2) + sut.executeAction(initial, action0, s0) shouldEqual 1 initial.getOutgoingActionTransitions.size shouldEqual 1 initial.getOutgoingActionTransitions(action0Identifier).states.size shouldEqual 1 initial.getOutgoingActionTransitions(action0Identifier).executionCounter shouldEqual 1 initial.getIncomingActionTransitions.size shouldEqual 0 + initial.getNeverExploredActionTypesCounter shouldEqual 1 s0.getOutgoingActionTransitions.size shouldEqual 0 s0.getIncomingActionTransitions.size shouldEqual 1 s0.getIncomingActionTransitions(action0Identifier).states.size shouldEqual 1 s0.getIncomingActionTransitions(action0Identifier).executionCounter shouldEqual 1 + s0.getNeverExploredActionTypesCounter shouldEqual 2 // execute action0 for the second time val s1SutState = createSutState(rootElementB) - val s1 = sut.getState(s1SutState) - sut.executeAction(initialSutState, action0, s1SutState) shouldEqual 2 + val s1 = sut.createState(s1SutState, 2) + sut.executeAction(initial, action0, s1) shouldEqual 2 initial.getOutgoingActionTransitions.size shouldEqual 1 initial.getOutgoingActionTransitions(action0Identifier).states.size shouldEqual 2 initial.getOutgoingActionTransitions(action0Identifier).executionCounter shouldEqual 2 initial.getIncomingActionTransitions.size shouldEqual 0 + initial.getNeverExploredActionTypesCounter shouldEqual 1 s1.getOutgoingActionTransitions.size shouldEqual 0 s1.getIncomingActionTransitions.size shouldEqual 1 s1.getIncomingActionTransitions(action0Identifier).states.size shouldEqual 1 s1.getIncomingActionTransitions(action0Identifier).executionCounter shouldEqual 1 + s1.getNeverExploredActionTypesCounter shouldEqual 2 // execute action1 for the first time val s2SutState = createSutState(rootElementC) - val s2 = sut.getState(s2SutState) - sut.executeAction(initialSutState, action1, s2SutState) shouldEqual 1 + val s2 = sut.createState(s2SutState, 2) + sut.executeAction(initial, action1, s2) shouldEqual 1 initial.getOutgoingActionTransitions.size shouldEqual 2 initial.getOutgoingActionTransitions(action1Identifier).states.size shouldEqual 1 initial.getOutgoingActionTransitions(action1Identifier).executionCounter shouldEqual 1 initial.getIncomingActionTransitions.size shouldEqual 0 + initial.getNeverExploredActionTypesCounter shouldEqual 0 s2.getOutgoingActionTransitions.size shouldEqual 0 s2.getIncomingActionTransitions.size shouldEqual 1 s2.getIncomingActionTransitions(action1Identifier).states.size shouldEqual 1 s2.getIncomingActionTransitions(action1Identifier).executionCounter shouldEqual 1 + s2.getNeverExploredActionTypesCounter shouldEqual 2 // execute action1 for the second time but from s1SutState to create one incoming action from two different states - sut.executeAction(s1SutState, action1, s2SutState) shouldEqual 1 + sut.executeAction(s1, action1, s2) shouldEqual 1 s1.getOutgoingActionTransitions.size shouldEqual 1 s1.getOutgoingActionTransitions(action1Identifier).states.size shouldEqual 1 s1.getOutgoingActionTransitions(action1Identifier).executionCounter shouldEqual 1 s1.getIncomingActionTransitions.size shouldEqual 1 s1.getIncomingActionTransitions(action0Identifier).states.size shouldEqual 1 s1.getIncomingActionTransitions(action0Identifier).executionCounter shouldEqual 1 + s1.getNeverExploredActionTypesCounter shouldEqual 1 s2.getOutgoingActionTransitions.size shouldEqual 0 s2.getIncomingActionTransitions.size shouldEqual 1 s2.getIncomingActionTransitions(action1Identifier).states shouldEqual Set(initial, s1) s2.getIncomingActionTransitions(action1Identifier).executionCounter shouldEqual 2 + s2.getNeverExploredActionTypesCounter shouldEqual 2 } "store a state for the second access" in { diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala index 224bb28..1fcb208 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala @@ -12,22 +12,22 @@ class StateImplSpec extends AbstractApiSpec { "StateImpl" should { "not equal" in { - val s0 = StateImpl(sutStateAIdentifier) - val s1 = StateImpl(sutStateBIdentifier) + val s0 = StateImpl(sutStateAIdentifier, 1) + val s1 = StateImpl(sutStateBIdentifier, 1) s0.equals(s1) shouldEqual false s0.equals(10) shouldEqual false s0.hashCode() should not equal s1.hashCode() } "equal" in { - val s0 = StateImpl(sutStateAIdentifier) - val s1 = StateImpl(sutStateAIdentifier) + val s0 = StateImpl(sutStateAIdentifier, 1) + val s1 = StateImpl(sutStateAIdentifier, 2) s0.equals(s1) shouldEqual true s0.hashCode() shouldEqual s1.hashCode() } "be converted into a string" in { - val s0 = StateImpl(sutStateAIdentifier) + val s0 = StateImpl(sutStateAIdentifier, 2) s0.toString shouldEqual "State[sutStateIdentifier=SutStateIdentifier[sutState=State[descriptor=[]], hash=0e4fd44f14d365fae3a7f3579b7ef013e1167e0f4ef6de418367b81edc63450d]]" } } diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala index 4eb9e18..d11ebe4 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala @@ -26,10 +26,12 @@ class GuiStateMachineGMLSerializerSpec extends AbstractApiSpec with BeforeAndAft val finalSutState = createSutState(rootElementC) // Create the whole state machine: - guiStateMachine.executeAction(initialSutState, action0, finalSutState) - guiStateMachine.executeAction(initialSutState, action1, finalSutState) - guiStateMachine.executeAction(finalSutState, action0, initialSutState) - guiStateMachine.executeAction(finalSutState, action1, initialSutState) + val initialState = guiStateMachine.createState(initialSutState, 2) + val finalState = guiStateMachine.createState(finalSutState, 2) + guiStateMachine.executeAction(initialState, action0, finalState) + guiStateMachine.executeAction(initialState, action1, finalState) + guiStateMachine.executeAction(finalState, action0, initialState) + guiStateMachine.executeAction(finalState, action1, initialState) val filePath = "./target/test_state_machine.gml" val oldFile = new File(filePath) @@ -160,7 +162,9 @@ class GuiStateMachineGMLSerializerSpec extends AbstractApiSpec with BeforeAndAft } "load GML " in { - the[UnsupportedOperationException] thrownBy GuiStateMachineSerializer.gml(guiStateMachine).load("bla") should have message "Loading GML is not supported." + the[UnsupportedOperationException] thrownBy GuiStateMachineSerializer + .gml(guiStateMachine) + .load("bla") should have message "Loading GML is not supported." } } } diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala index 8cd5f31..e1c4e7a 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala @@ -35,7 +35,9 @@ class GuiStateMachineJavaObjectStreamSerializerSpec extends AbstractApiSpec with val finalSutStateIdentifier = new SutStateIdentifier(finalSutState) // Create the whole state machine: - guiStateMachine.executeAction(initialSutState, action0, finalSutState) + val initialState = guiStateMachine.createState(initialSutStateIdentifier, 1) + val finalState = guiStateMachine.createState(finalSutStateIdentifier, 1) + guiStateMachine.executeAction(initialState, action0, finalState) // Save the state machine: GuiStateMachineSerializer.javaObjectStream(guiStateMachine).save(filePath)