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

Commit

Permalink
Merged in feature/hash-identifier (pull request #29) by ReBaZer
Browse files Browse the repository at this point in the history
Initial hash code identifier support #27
  • Loading branch information
rebazer authored Apr 4, 2019
2 parents 6047cc2 + 11c4112 commit 9622720
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.retest.guistatemachine.api

import de.retest.surili.commons.actions.Action

class ActionIdentifier(action: Action) extends HashIdentifier(action) {
val msg = s"ActionIdentifier[action=${action.toString}, hash=$hash]"
override def toString: String = msg
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import de.retest.surili.commons.actions.Action
*/
trait GuiStateMachine {

def getState(sutStateIdentifier: SutStateIdentifier): 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
def getState(sutState: SutState): State = getState(new SutStateIdentifier(sutState))

/**
* Executes an action from a state leading to the current state described by descriptors.
Expand All @@ -29,26 +31,27 @@ trait GuiStateMachine {
* @param to The state which the execution leads to.
* @return The current state which the transition of a leads to.
*/
def executeAction(from: State, a: Action, to: State): State
def executeAction(from: State, a: ActionIdentifier, to: State): State
def executeAction(from: State, a: Action, to: State): State = executeAction(from, new ActionIdentifier(a), to)
def executeAction(fromSutState: SutState, a: Action, toSutState: SutState): State =
executeAction(getState(fromSutState), a, getState(toSutState))

def getAllStates: Map[SutState, State]
def getAllStates: Map[SutStateIdentifier, State]

/**
* In the legacy code this was only used to show the number of actions which have been explored by Monkey Testing.
*
* @return All actions which have been explored and therefore have a corresponding transition.
*/
def getAllExploredActions: Set[Action]
def getAllExploredActions: Set[ActionIdentifier]

/**
* In the legacy code this was only used to calculate all never explored actions.
* It could be used for the visualization of the NFA to see how often actions are executed.
*
* @return The number of times every explored action has been executed in the NFA. Never explored actions are not part of it.
*/
def getActionExecutionTimes: Map[Action, Int]
def getActionExecutionTimes: Map[ActionIdentifier, Int]

/**
* Clears all states, transitions and never explored actions etc.
Expand Down
44 changes: 44 additions & 0 deletions src/main/scala/de/retest/guistatemachine/api/HashIdentifier.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package de.retest.guistatemachine.api

import java.io.{ByteArrayOutputStream, ObjectOutputStream, Serializable}

/**
* Storing the whole states and actions as XML takes too much space. Therefore, we only store a hash value. The hash
* should have no collisions to uniquely identify states and actions. Since we use SHA-256 the probability is very low
* that any collisions will occur.
*
* @param serializable The serializable from which a SHA-256 is generated.
*/
@SerialVersionUID(1L)
class HashIdentifier(serializable: Serializable) extends scala.Serializable {
val hash: String = HashIdentifier.sha256Hash(HashIdentifier.serializableToString(serializable))

override def equals(obj: Any): Boolean =
if (!obj.isInstanceOf[HashIdentifier]) {
false
} else {
val other = obj.asInstanceOf[HashIdentifier]
hash.equals(other.hash)
}

override def hashCode(): Int = hash.hashCode

override def toString: String = s"HashIdentifier[hash=$hash]"

}

object HashIdentifier {
def sha256Hash(text: String): String =
String.format("%064x", new java.math.BigInteger(1, java.security.MessageDigest.getInstance("SHA-256").digest(text.getBytes("UTF-8"))))

def serializableToString(serializable: Serializable): String = {
val byteArrayOutputStream = new ByteArrayOutputStream()
val oos = new ObjectOutputStream(byteArrayOutputStream)
try {
oos.writeObject(serializable)
byteArrayOutputStream.toString("UTF-8")
} finally {
oos.close()
}
}
}
17 changes: 7 additions & 10 deletions src/main/scala/de/retest/guistatemachine/api/State.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package de.retest.guistatemachine.api

import de.retest.recheck.ui.descriptors.SutState
import de.retest.surili.commons.actions.Action

/**
* A state should be identified by its corresponding SutState.
* It consists of actions which have not been explored yet and transitions to states which build up the state machine.
Expand All @@ -12,19 +9,19 @@ trait State {
/**
* @return The SutState which identifies this state.
*/
def getSutState: SutState
def getSutStateIdentifier: SutStateIdentifier

/**
* NFA states can lead to different states by consuming the same symbol.
* Hence, we have a set of states per action.
* In the legacy code there was a type called `AmbigueState` but a multimap simplifies the implementation.
*/
def getOutgoingActionTransitions: Map[Action, ActionTransitions]
def getOutgoingActionTransitions: Map[ActionIdentifier, ActionTransitions]

/**
* Helper method to retrieve all incoming transitions.
*/
def getIncomingActionTransitions: Map[Action, ActionTransitions]
def getIncomingActionTransitions: Map[ActionIdentifier, ActionTransitions]

/**
* Overriding this method is required to allow the usage of a set of states.
Expand All @@ -34,15 +31,15 @@ trait State {
override def equals(obj: Any): Boolean = {
obj match {
case other: State =>
this.getSutState == other.getSutState
this.getSutStateIdentifier == other.getSutStateIdentifier
case _ =>
super.equals(obj)
}
}

override def hashCode(): Int = this.getSutState.hashCode()
override def hashCode(): Int = this.getSutStateIdentifier.hashCode()

override def toString: String = s"sutState=$getSutState,transitions=$getOutgoingActionTransitions"
override def toString: String = s"State[sutStateIdentifier=$getSutStateIdentifier]"

/**
* Adds a new transition to the state which is only allowed by calling the methods of [[GuiStateMachine]].
Expand All @@ -51,5 +48,5 @@ trait State {
* @param to The state which the transition leads t o.
* @return The number of times the action has been executed from this state. The target state does not matter for this number.
*/
private[api] def addTransition(a: Action, to: State): Int
private[api] def addTransition(a: ActionIdentifier, to: State): Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.retest.guistatemachine.api

import de.retest.recheck.ui.descriptors.SutState

class SutStateIdentifier(sutState: SutState) extends HashIdentifier(sutState) {
val msg = s"SutStateIdentifier[sutState=${sutState.toString}, hash=$hash]"
override def toString: String = msg
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package de.retest.guistatemachine.api.impl

import com.typesafe.scalalogging.Logger
import de.retest.guistatemachine.api.{GuiStateMachine, State}
import de.retest.recheck.ui.descriptors.SutState
import de.retest.surili.commons.actions.Action
import de.retest.guistatemachine.api.{ActionIdentifier, GuiStateMachine, State, SutStateIdentifier}

import scala.collection.immutable.{HashMap, HashSet}

Expand All @@ -13,31 +11,31 @@ import scala.collection.immutable.{HashMap, HashSet}
@SerialVersionUID(1L)
class GuiStateMachineImpl extends GuiStateMachine with Serializable {
@transient private val logger = Logger[GuiStateMachineImpl]
private var states = new HashMap[SutState, State]
private var states = new HashMap[SutStateIdentifier, State]

/**
* The legacy code stored execution counters for every action.
*/
private var allExploredActions = new HashSet[Action]
private var allExploredActions = new HashSet[ActionIdentifier]

/**
* `actionExecutionCounter` from the legacy code.
* Stores the total number of executions per action.
*/
private var actionExecutionTimes = new HashMap[Action, Int]
private var actionExecutionTimes = new HashMap[ActionIdentifier, Int]

override def getState(sutState: SutState): State = this.synchronized {
override def getState(sutState: SutStateIdentifier): State = this.synchronized {
if (states.contains(sutState)) {
states(sutState)
} else {
logger.info(s"Create new state from SUT state with hash code ${sutState.hashCode()}")
logger.info(s"Create new state from SUT state $sutState")
val s = StateImpl(sutState)
states += (sutState -> s)
s
}
}

override def executeAction(from: State, a: Action, to: State): State = this.synchronized {
override def executeAction(from: State, a: ActionIdentifier, to: State): State = this.synchronized {
allExploredActions += a
val old = actionExecutionTimes.get(a)
old match {
Expand All @@ -48,16 +46,16 @@ class GuiStateMachineImpl extends GuiStateMachine with Serializable {
to
}

override def getAllStates: Map[SutState, State] = this.synchronized { states }
override def getAllStates: Map[SutStateIdentifier, State] = this.synchronized { states }

override def getAllExploredActions: Set[Action] = this.synchronized { allExploredActions }
override def getAllExploredActions: Set[ActionIdentifier] = this.synchronized { allExploredActions }

override def getActionExecutionTimes: Map[Action, Int] = this.synchronized { actionExecutionTimes }
override def getActionExecutionTimes: Map[ActionIdentifier, Int] = this.synchronized { actionExecutionTimes }

override def clear(): Unit = this.synchronized {
states = new HashMap[SutState, State]
allExploredActions = new HashSet[Action]
actionExecutionTimes = new HashMap[Action, Int]
states = new HashMap[SutStateIdentifier, State]
allExploredActions = new HashSet[ActionIdentifier]
actionExecutionTimes = new HashMap[ActionIdentifier, Int]
}

override def assignFrom(other: GuiStateMachine): Unit = this.synchronized {
Expand Down
18 changes: 8 additions & 10 deletions src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
package de.retest.guistatemachine.api.impl

import de.retest.guistatemachine.api.{ActionTransitions, State}
import de.retest.recheck.ui.descriptors.SutState
import de.retest.surili.commons.actions.Action
import de.retest.guistatemachine.api.{ActionIdentifier, ActionTransitions, State, SutStateIdentifier}

import scala.collection.immutable.HashMap

@SerialVersionUID(1L)
case class StateImpl(sutState: SutState) extends State with Serializable {
case class StateImpl(sutState: SutStateIdentifier) extends State with Serializable {

/**
* Currently, there is no MultiMap trait for immutable maps in the Scala standard library.
* The legacy code used `AmbigueState` here which was more complicated than just a multi map.
*/
private var outgoingActionTransitions = HashMap[Action, ActionTransitions]()
private var outgoingActionTransitions = HashMap[ActionIdentifier, ActionTransitions]()

/**
* Redundant information but helpful to be retrieved.
*/
private var incomingActionTransitions = HashMap[Action, ActionTransitions]()
private var incomingActionTransitions = HashMap[ActionIdentifier, ActionTransitions]()

override def getSutState: SutState = this.synchronized { sutState }
override def getOutgoingActionTransitions: Map[Action, ActionTransitions] = this.synchronized { outgoingActionTransitions }
override def getIncomingActionTransitions: Map[Action, ActionTransitions] = this.synchronized { incomingActionTransitions }
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 }

private[api] override def addTransition(a: Action, to: State): Int = {
private[api] override def addTransition(a: ActionIdentifier, to: State): Int = {
val executionCounter = this.synchronized {
outgoingActionTransitions.get(a) match {
case Some(oldTransitions) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package de.retest.guistatemachine.api.impl.serialization
import de.retest.recheck.ui.descriptors.SutState
import de.retest.surili.commons.actions.Action
import de.retest.guistatemachine.api.{ActionIdentifier, SutStateIdentifier}

case class GraphActionEdge(from: SutState, to: SutState, action: Action) {
case class GraphActionEdge(from: SutStateIdentifier, to: SutStateIdentifier, action: ActionIdentifier) {
override def toString: String = action.toString
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package de.retest.guistatemachine.api.impl.serialization
import java.awt.Color

import com.github.systemdir.gml.model.{EdgeGraphicDefinition, GraphicDefinition, NodeGraphicDefinition, YedGmlGraphicsProvider}
import de.retest.recheck.ui.descriptors.SutState
import de.retest.guistatemachine.api.SutStateIdentifier

class GraphicsProvider extends YedGmlGraphicsProvider[SutState, GraphActionEdge, AnyRef] {
override def getVertexGraphics(vertex: SutState): NodeGraphicDefinition =
class GraphicsProvider extends YedGmlGraphicsProvider[SutStateIdentifier, GraphActionEdge, AnyRef] {
override def getVertexGraphics(vertex: SutStateIdentifier): NodeGraphicDefinition =
new NodeGraphicDefinition.Builder().setFill(Color.LIGHT_GRAY).setLineColor(Color.black).setFontStyle(GraphicDefinition.FontStyle.ITALIC).build
override def getEdgeGraphics(edge: GraphActionEdge, edgeSource: SutState, edgeTarget: SutState): EdgeGraphicDefinition =
override def getEdgeGraphics(edge: GraphActionEdge, edgeSource: SutStateIdentifier, edgeTarget: SutStateIdentifier): EdgeGraphicDefinition =
new EdgeGraphicDefinition.Builder()
.setTargetArrow(EdgeGraphicDefinition.ArrowType.SHORT_ARROW)
.setLineType(GraphicDefinition.LineType.DASHED)
.build
override def getGroupGraphics(group: AnyRef, groupElements: java.util.Set[SutState]): NodeGraphicDefinition = null
override def getGroupGraphics(group: AnyRef, groupElements: java.util.Set[SutStateIdentifier]): NodeGraphicDefinition = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package de.retest.guistatemachine.api.impl.serialization
import java.io.{BufferedWriter, File, FileOutputStream, OutputStreamWriter}

import com.github.systemdir.gml.YedGmlWriter
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineSerializer}
import de.retest.recheck.ui.descriptors.SutState
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineSerializer, SutStateIdentifier}
import org.jgrapht.graph.DirectedPseudograph

class GuiStateMachinGMLSerializer(guiStateMachine: GuiStateMachine) extends GuiStateMachineSerializer {

type GraphType = DirectedPseudograph[SutState, GraphActionEdge]
type GraphType = DirectedPseudograph[SutStateIdentifier, GraphActionEdge]

/**
* Converts the state machines into GML which can be read by editors like yED.
Expand All @@ -25,9 +24,9 @@ class GuiStateMachinGMLSerializer(guiStateMachine: GuiStateMachine) extends GuiS

// get the gml writer
val writer =
new YedGmlWriter.Builder[SutState, GraphActionEdge, AnyRef](graphicsProvider, YedGmlWriter.PRINT_LABELS: _*)
new YedGmlWriter.Builder[SutStateIdentifier, GraphActionEdge, AnyRef](graphicsProvider, YedGmlWriter.PRINT_LABELS: _*)
.setEdgeLabelProvider(_.toString)
.setVertexLabelProvider(sutState => "%s - hash code: %d".format(sutState.toString, sutState.hashCode()))
.setVertexLabelProvider(_.toString)
.build

// write to file
Expand All @@ -54,7 +53,7 @@ class GuiStateMachinGMLSerializer(guiStateMachine: GuiStateMachine) extends GuiS
val actionTransitions = transition._2
val action = transition._1
actionTransitions.states.foreach { toState =>
val toVertex = toState.getSutState
val toVertex = toState.getSutStateIdentifier
val edge = GraphActionEdge(fromVertex, toVertex, action)
if (!graph.addEdge(fromVertex, toVertex, edge)) { throw new RuntimeException(s"Failed to add edge $edge") }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.scalatest.{Matchers, WordSpec}

abstract trait AbstractApiSpec extends WordSpec with Matchers {

def createSutState(rootElements: RootElement*) : SutState = new SutState(Arrays.asList(rootElements: _*))
def createSutState(rootElements: RootElement*): SutState = new SutState(Arrays.asList(rootElements: _*))

/**
* Creates a new identifying attributes collection which should only match other identifying attributes with the same ID.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.retest.guistatemachine.api

import de.retest.surili.commons.actions.NavigateToAction

class HashIdentifierSpec extends AbstractApiSpec {
private val action0 = new NavigateToAction("http://google.com")
private val action0Identifier = new HashIdentifier(action0)
private val action1 = new NavigateToAction("http://wikipedia.org")
private val action1Identifier = new HashIdentifier(action1)

"HashIdentifier" should {
"generate SHA hashes" in {
action0Identifier.hash shouldEqual "fd00ea22cb50efd96c3ff59d8900685d0d64f2cee1e77873133e7e186afd2e7f"
action1Identifier.hash shouldEqual "240d08498736de4d893c146fd64b58b1ae1eda8c36a565919b035d86c6ee2084"
}

"not equal" in {
action0Identifier.equals(action1Identifier) shouldEqual false
action0Identifier.hashCode() should not equal action1Identifier.hashCode()
}

"equal" in {
action0Identifier.equals(action0Identifier) shouldEqual true
action0Identifier.hashCode() shouldEqual action0Identifier.hashCode()
}
}
}
Loading

0 comments on commit 9622720

Please sign in to comment.