Skip to content
This repository has been archived by the owner on Mar 12, 2020. It is now read-only.

Commit

Permalink
Provide createState with a counter for never explored action types #34
Browse files Browse the repository at this point in the history
- getState returns an Option
- Remove convenient method executeAction based on SutStates
- Store only a counter not a set of ActionIdentifiers
- Adapt unit tests
  • Loading branch information
tdauth authored and rebazer committed May 14, 2019
1 parent 9f102f4 commit 18168a6
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 56 deletions.
19 changes: 15 additions & 4 deletions src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@ 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.
*
* @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.
Expand All @@ -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]

Expand Down
10 changes: 2 additions & 8 deletions src/main/scala/de/retest/guistatemachine/api/State.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,39 @@ 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.
*/
@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -18,19 +20,14 @@ 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
}
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 {
Expand All @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]]"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 18168a6

Please sign in to comment.