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

Commit

Permalink
Initial concurrency support #15
Browse files Browse the repository at this point in the history
Basic concurrency support with synchronized.
  • Loading branch information
tdauth committed Feb 26, 2019
1 parent 7e308cc commit 3c67139
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 48 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ stateMachine.saveGML("mystatemachine.gml")
stateMachine.save("mystatemachine.ser")
```

The state machine can be saved as [GML](https://en.wikipedia.org/wiki/Graph_Modelling_Language) file which can be visualized by editors like [yEd](https://www.yworks.com/products/yed).
State machines can be saved as on loaded from files.
Besides, they can be saved as [GML](https://en.wikipedia.org/wiki/Graph_Modelling_Language) files which can be visualized by editors like [yEd](https://www.yworks.com/products/yed).

## Automatic Build with TravisCI

[![Build Status](https://travis-ci.com/retest/gui-state-machine-api.svg?branch=master)](https://travis-ci.com/retest/gui-state-machine-api)
[![Code Coverage](https://img.shields.io/codecov/c/github/retest/gui-state-machine-api/master.svg)](https://codecov.io/github/retest/gui-state-machine-api?branch=master)

Expand All @@ -30,6 +32,7 @@ Define the Nexus password in the environment variable `TRAVIS_NEXUS_PW`.
Otherwise, the build will fail!

## SBT Commands

* `sbt compile` to build the project manually.
* `sbt assembly` to create a standalone JAR which includes all dependencies including the Scala libraries. The standalone JAR is generated as `target/scala-<scalaversion>/gui-state-machine-api-assembly-<version>.jar`.
* `sbt eclipse` to generate a project for Eclipse.
Expand All @@ -43,6 +46,7 @@ Otherwise, the build will fail!
* `sbt publish` publishes the artifacts in ReTest's Nexus. Requires a `$HOME/.sbt/.credentials` file with the correct credentials. This command can be useful to publish SNAPSHOT versions.

## NFA for the Representation of Tests

A nondeterministic finite automaton represents the states of the GUI during the test.
The actions executed by the user on the widgets are represented by transitions.
If an action has not been executed yet from a state, it leads to an unknown state.
Expand All @@ -52,4 +56,8 @@ Whenever an unknown state is replaced by a newly discovered state, the NFA has t

The NFA is used to generate test cases (sequence of UI actions) with the help of a genetic algorithm.
For example, whenever a random action is executed with the help of monkey testing, it adds a transition to the state machine.
After running the genetic algorithm, the state machine is then used to create a test suite.
After running the genetic algorithm, the state machine is then used to create a test suite.

## Concurrency

The creation and modification of state machines should be threadsafe.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ resolvers += "nexus-retest-maven-all" at " https://nexus.retest.org/repository/a
libraryDependencies += "de.retest" % "surili-model" % "0.1.0-SNAPSHOT" % "provided" withSources () withJavadoc ()
libraryDependencies += "de.retest" % "retest-sut-api" % "3.2.0" % "provided" withSources () withJavadoc ()

// Dependencies to write GraphML files for yEd:
// Dependencies to write GML files for yEd:
libraryDependencies += "com.github.systemdir.gml" % "GMLWriterForYed" % "2.1.0"
libraryDependencies += "org.jgrapht" % "jgrapht-core" % "1.0.1"

Expand Down
2 changes: 0 additions & 2 deletions src/main/scala/de/retest/guistatemachine/api/State.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package de.retest.guistatemachine.api
import de.retest.surili.model.actions.Action
import de.retest.ui.descriptors.SutState

import scala.util.Random

/**
* A state should be identified by its corresponding `de.retest.ui.descriptors.SutState`.
* It consists of actions which have not been explored yet and transitions to states which build up the state machine.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@ class GraphicsProvider extends YedGmlGraphicsProvider[SutState, GraphActionEdge,
.setTargetArrow(EdgeGraphicDefinition.ArrowType.SHORT_ARROW)
.setLineType(GraphicDefinition.LineType.DASHED)
.build
// we have no groups in this example
override def getGroupGraphics(group: AnyRef, groupElements: java.util.Set[SutState]): NodeGraphicDefinition = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ class GuiStateMachineApiImpl extends GuiStateMachineApi {

override def save(filePath: String): Unit = {
val oos = new ObjectOutputStream(new FileOutputStream(filePath))
oos.writeObject(stateMachines)
oos.writeObject(stateMachines) // TODO #15 Do we need to make a copy before to make it threadsafe?
oos.close()
}

// TODO #15 Make thread safe?
override def load(filePath: String): Unit = {
clear()
val ois = new ObjectInputStream(new FileInputStream(filePath))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,67 +20,67 @@ class GuiStateMachineImpl extends GuiStateMachine with Serializable {
/**
* The legacy code stored execution counters for every action.
*/
var allExploredActions = new HashSet[Action]
private var allExploredActions = new HashSet[Action]

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

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

override def executeAction(from: State, a: Action, to: State): State = {
allExploredActions = allExploredActions + a
override def executeAction(from: State, a: Action, to: State): State = this.synchronized {
allExploredActions += a
val old = actionExecutionTimes.get(a)
old match {
case Some(o) => actionExecutionTimes = actionExecutionTimes + (a -> (o + 1))
case None => actionExecutionTimes = actionExecutionTimes + (a -> 1)
case Some(o) => actionExecutionTimes += (a -> (o + 1))
case None => actionExecutionTimes += (a -> 1)
}
from.addTransition(a, to)
to
}

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

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

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

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

override def save(filePath: String): Unit = {
override def save(filePath: String): Unit = this.synchronized {
val oos = new ObjectOutputStream(new FileOutputStream(filePath))
oos.writeObject(this)
oos.close()
}

override def load(filePath: String): Unit = {
override def load(filePath: String): Unit = this.synchronized {
clear()
val ois = new ObjectInputStream(new FileInputStream(filePath))
val readStateMachine = ois.readObject.asInstanceOf[GuiStateMachineImpl]
ois.close()
this.states = readStateMachine.states
this.allExploredActions = readStateMachine.allExploredActions
this.actionExecutionTimes = readStateMachine.actionExecutionTimes
states = readStateMachine.states
allExploredActions = readStateMachine.allExploredActions
actionExecutionTimes = readStateMachine.actionExecutionTimes
}

type GraphType = DirectedPseudograph[SutState, GraphActionEdge]

override def saveGML(filePath: String): Unit = {
override def saveGML(filePath: String): Unit = this.synchronized {
// get graph from user
val toDraw = getGraph()

Expand All @@ -99,7 +99,6 @@ class GuiStateMachineImpl extends GuiStateMachine with Serializable {
val output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "utf-8"))
try writer.export(output, toDraw)
finally if (output != null) output.close()

}

private def getGraph(): GraphType = {
Expand Down
25 changes: 13 additions & 12 deletions src/main/scala/de/retest/guistatemachine/api/impl/IdMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package de.retest.guistatemachine.api.impl

import de.retest.guistatemachine.api.Id

import scala.collection.immutable.HashMap
import scala.collection.mutable.HashMap

/**
* This custom type allows storing values using [[Id]] as key.
Expand All @@ -12,34 +12,35 @@ import scala.collection.immutable.HashMap
case class IdMap[T]() extends Serializable {
private type HashMapType = HashMap[Id, T]

private var values = new HashMapType
private val values = new HashMapType

def addNewElement(v: T): Id = {
val generatedId = generateId
values = values + (generatedId -> v)
def addNewElement(v: T): Id = this.synchronized {
val generatedId = generateId()
values += (generatedId -> v)
generatedId
}

def removeElement(id: Id): Boolean =
def removeElement(id: Id): Boolean = this.synchronized {
if (values.contains(id)) {
values = values - id
values -= id
true
} else {
false
}
}

def getElement(id: Id): Option[T] = values.get(id)
def getElement(id: Id): Option[T] = this.synchronized { values.get(id) }

def hasElement(id: Id): Boolean = values.contains(id)
def hasElement(id: Id): Boolean = this.synchronized { values.contains(id) }

def clear(): Unit = values = new HashMap[Id, T]
def clear(): Unit = this.synchronized { values.clear() }

def size: Int = values.size
def size: Int = this.synchronized { values.size }

/**
* Generates a new ID based on the existing entries.
*/
private def generateId: Id = {
private def generateId(): Id = {
var id = Id(0L)
while (values.keySet.contains(id)) { id = Id(id.id + 1) }
id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ class StateImpl(sutState: SutState) extends State with Serializable {
*/
var transitions = new HashMap[Action, ActionTransitions]

override def getSutState: SutState = sutState
override def getTransitions: Map[Action, ActionTransitions] = transitions
override def getSutState: SutState = this.synchronized { sutState }
override def getTransitions: Map[Action, ActionTransitions] = this.synchronized { transitions }

private[api] override def addTransition(a: Action, to: State): Int = {
private[api] override def addTransition(a: Action, to: State): Int = this.synchronized {
val old = transitions.get(a)
old match {
case Some(o) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class GuiStateMachineImplSpec extends AbstractApiSpec with BeforeAndAfterEach {
"clear the state machine" in {
sut.clear()
sut.getAllExploredActions.isEmpty shouldEqual true
sut.actionExecutionTimes.isEmpty shouldEqual true
sut.getActionExecutionTimes.isEmpty shouldEqual true
sut.getAllStates.isEmpty shouldEqual true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ package de.retest.guistatemachine.api.impl
import java.util.Arrays

import de.retest.guistatemachine.api.AbstractApiSpec
import de.retest.surili.model.actions.NavigateToAction
import de.retest.ui.descriptors.SutState

class StateImplSpec extends AbstractApiSpec {
private val rootElementA = getRootElement("a", 0)
private val rootElementB = getRootElement("b", 0)
private val sutStateA = new SutState(Arrays.asList(rootElementA))
private val sutStateB = new SutState(Arrays.asList(rootElementB))
private val action0 = new NavigateToAction("http://google.com")
private val action1 = new NavigateToAction("http://wikipedia.org")

"StateImpl" should {
"not equal" in {
Expand Down

0 comments on commit 3c67139

Please sign in to comment.