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

Commit

Permalink
Swagger support for REST, execution counters for actions, persistence…
Browse files Browse the repository at this point in the history
… methods #1 #7 #9

- Besides, update some scalastyle rules.
- Move all sbt plugins to one file.
- Do some refactoring, so we only used immutable data structures.
- Add type ActionTransitions which stores the execution times.
- Initial persistence methods.
- Add initial Swagger support for the REST service.
  • Loading branch information
tdauth committed Nov 15, 2018
1 parent 770af5b commit 04cd7a8
Show file tree
Hide file tree
Showing 42 changed files with 307 additions and 144 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,9 @@ Some suggestions how the REST API for the state machine could look like:
* `/state-machine/<long>/state/<long>/transition/<long>` GET queries a specific transition of a specific state.
* `/state-machine/<long>/execute` POST executes the passed action from the passed state which might lead to a new state and adds a transition to the state machine. The action must be part of all actions?

## Swagger Support
The Swagger support is based on [swagger-akka-http](https://github.com/swagger-akka-http/swagger-akka-http).
The URL `http://localhost:8888/api-docs/swagger.json` should show create Swagger JSON output which can be rendered by Swagger UI.

### Bash Scripts for REST Calls
The directory [scripts](./scripts) contains a number of Bash scripts which use `curl` to send REST calls to a running server.
19 changes: 8 additions & 11 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ organization := "retest"
scalaVersion := "2.12.7"

// Dependencies to represent the input of states and actions:
libraryDependencies += "de.retest" % "retest-model" % "5.0.0" withSources() withJavadoc()
libraryDependencies += "org.seleniumhq.selenium" % "selenium-java" % "2.35.0" withSources() withJavadoc()
libraryDependencies += "de.retest" % "retest-model" % "5.0.0" withSources () withJavadoc ()
libraryDependencies += "org.seleniumhq.selenium" % "selenium-java" % "2.35.0" withSources () withJavadoc ()

// Dependencies to provide a REST service:
libraryDependencies += "com.github.scopt" % "scopt_2.12" % "3.7.0"
Expand All @@ -20,6 +20,10 @@ libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.5"
libraryDependencies += "com.typesafe.akka" %% "akka-http-xml" % "10.1.5"
libraryDependencies += "com.typesafe.akka" %% "akka-http-testkit" % "10.1.5" % "test"

// Swagger:
libraryDependencies += "io.swagger" % "swagger-jaxrs" % "1.5.21"
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "1.0.0"

// Test frameworks:
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test"
libraryDependencies += "org.scalamock" %% "scalamock" % "4.1.0" % "test"
Expand All @@ -29,12 +33,5 @@ mainClass in (Compile, run) := Some("de.retest.guistatemachine.rest.WebServer")
// set the main class for packaging the main jar
mainClass in (Compile, packageBin) := Some("de.retest.guistatemachine.rest.WebServer")

publishTo := {
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
}

credentials += Credentials(Path.userHome / ".sbt" / ".credentials")
// format the code
scalafmtOnCompile := true
1 change: 0 additions & 1 deletion project/assembly.sbt

This file was deleted.

10 changes: 10 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// to create a standalone JAR file with all dependencies
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
// code formatting
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15")
// for signed releases
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.10")
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
1 change: 0 additions & 1 deletion project/sbteclipse.sbt

This file was deleted.

1 change: 0 additions & 1 deletion project/sbtrelease.sbt

This file was deleted.

1 change: 0 additions & 1 deletion project/scalafmt.sbt

This file was deleted.

1 change: 0 additions & 1 deletion project/scalastyle.sbt

This file was deleted.

1 change: 0 additions & 1 deletion project/scoverage.sbt

This file was deleted.

4 changes: 2 additions & 2 deletions scalastyle-config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,6 @@
</parameters>
</check>
<check level="warning" class="org.scalastyle.scalariform.PublicMethodsHaveTypeChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.file.NewLineAtEofChecker" enabled="false"></check>
<check level="warning" class="org.scalastyle.file.NoNewLineAtEofChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.file.NewLineAtEofChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.file.NoNewLineAtEofChecker" enabled="false"></check>
</scalastyle>
2 changes: 1 addition & 1 deletion src/main/scala/de/retest/guistatemachine/api/Action.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ case class Action(a: org.openqa.selenium.interactions.Action) {

// TODO #6 Convert abstract representation of actions into string.
override def toString: String = "Selenium Action"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.retest.guistatemachine.api

/**
* Represents transitions for one single symbol which is represented by an [[Action]] to a number of states.
* The corresponding symbol is not stored in this class but in the [[State]] from which the transitions are started.
*
* @param to The states which the transitions lead to. Since it is a NFA, there can be multiple states for the same symbol.
* @param executionCounter The number of times all transitions for the action have been executed from the corresponding state.
* It does not matter to which state. In the legacy code this was stored as `StateGraph.executionCounter`.
*/
case class ActionTransitions(to: Set[State], executionCounter: Int)
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import de.retest.ui.descriptors.RootElement
/**
* Set of root elements which identifies a state.
*/
case class Descriptors(rootElements: Set[RootElement])
case class Descriptors(rootElements: Set[RootElement])
21 changes: 17 additions & 4 deletions src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ package de.retest.guistatemachine.api
* API to create a NFA which represents the current state machine of an automatic GUI test generation with the help of a genetic algorithm.
* Simulated actions by the user are mapped to transitions in the state machine.
* States are identified by descriptors.
* There can be ambigious states which makes the finite state machine non-deterministic.
* There can be ambiguous states which makes the finite state machine non-deterministic.
* There can also be multiple start states for NFAs.
* Therefore, we do not provide any functionality to set or get the initial state.
*/
trait GuiStateMachine {

/**
* Gets a state identified by descriptors and with its initial never explored actions.
*
* @param descriptors The descriptors which identify the state.
* @param neverExploredActions All actions which have never been explored from the state.
* @return The state identified by the descriptors. If there has not been any state yet, it will be added.
Expand All @@ -29,13 +32,23 @@ trait GuiStateMachine {

/**
* Can be used by the GA to generate new test cases.
*
* @return All actions which have not been explored yet.
*/
def getAllNeverExploredActions: scala.collection.mutable.Set[Action]
def getAllNeverExploredActions: Set[Action]

/**
* 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: scala.collection.mutable.Set[Action]
}
def getAllExploredActions: Set[Action]

/**
* In the legacy code this was only used to calculate [[getAllNeverExploredActions]].
* 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.
*/
def getActionExecutionTimes: Map[Action, Int]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@ trait GuiStateMachineApi {

/**
* Creates a new [[GuiStateMachine]].
*
* @return The new GUI state machine.
*/
def createStateMachine: GuiStateMachine

/**
* Removes a persisted [[GuiStateMachine]].
* Removes an existing [[GuiStateMachine]].
*
* @param stateMachine The persisted GUI state machine.
* @return True if it has been persisted before and is no remove. Otherwise, false.
* @return True if it existed and was removed by this call. Otherwise, false.
*/
def removeStateMachine(stateMachine: GuiStateMachine): Boolean
}

/**
* Stores all state machines on the disk.
* Persistence can be useful when the state machines become quite big and the generation/modification is interrupted
* and continued later.
*/
def persist(): Unit

/**
* Loads all state machines from the disk.
*/
def load(): Unit
}
34 changes: 19 additions & 15 deletions src/main/scala/de/retest/guistatemachine/api/State.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ trait State {
* 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 getTransitions: Map[Action, Set[State]]
def getTransitions: Map[Action, ActionTransitions]

/**
* This was used in the legacy code for Monkey testing.
*
* @return Returns a random action or an empty value if there are none left.
*/
def getRandomAction(): Option[Action] = {
Expand All @@ -37,19 +38,6 @@ trait State {
}
}

/**
* Adds a new transition to the state which is only allowed by calling the methods of [[GuiStateMachine]].
* @param a The action which represents the transition's consumed symbol.
* @param to The state which the transition leads t o.
*/
private[api] def addTransition(a: Action, to: State): Unit

/**
* This was named `getRandomActions` in the legacy code but actually returned all actions.
* @return All actions (explored + unexplored).
*/
private def getAllActions(): Set[Action] = getNeverExploredActions ++ getTransitions.keySet

/**
* Overriding this method is required to allow the usage of a set of states.
* Comparing the descriptors should check for the equality of all root elements which compares the identifying attributes and the contained components
Expand All @@ -67,4 +55,20 @@ trait State {
override def hashCode(): Int = this.getDescriptors.hashCode()

override def toString: String = s"descriptors=${getDescriptors},neverExploredActions=${getNeverExploredActions},transitions=${getTransitions}"
}

/**
* Adds a new transition to the state which is only allowed by calling the methods of [[GuiStateMachine]].
*
* @param a The action which represents the transition's consumed symbol.
* @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

/**
* This was named `getRandomActions` in the legacy code but actually returned all actions.
*
* @return All actions (explored + unexplored).
*/
private def getAllActions(): Set[Action] = getNeverExploredActions ++ getTransitions.keySet
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,12 @@ object GuiStateMachineApiImpl extends GuiStateMachineApi {
}

override def removeStateMachine(stateMachine: GuiStateMachine): Boolean = stateMachines.remove(stateMachine)
}

override def persist(): Unit = {
// TODO #9 store on the disk
}

override def load(): Unit = {
// TODO #9 Load from the disk
}
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,55 @@
package de.retest.guistatemachine.api.impl

import de.retest.guistatemachine.api.{Action, Descriptors, GuiStateMachine, State}

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

class GuiStateMachineImpl extends GuiStateMachine {
val states = new HashMap[Descriptors, State]
var states = new HashMap[Descriptors, State]

/**
* In the legacy code we had `getAllNeverExploredActions` which had to collect them from all states and make sure they were never executed.
* Storing them directly in a set improves efficiency.
*/
val allNeverExploredActions = new HashSet[Action]
var allNeverExploredActions = new HashSet[Action]

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

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

override def getState(descriptors: Descriptors, neverExploredActions: Set[Action]): State = {
if (states.contains(descriptors)) {
states(descriptors)
} else {
allNeverExploredActions ++= (neverExploredActions -- allExploredActions)
allNeverExploredActions = allNeverExploredActions ++ (neverExploredActions -- allExploredActions)
val s = new StateImpl(descriptors, neverExploredActions)
states += (descriptors -> s)
states = states + (descriptors -> s)
s
}
}

override def executeAction(from: State, a: Action, descriptors: Descriptors, neverExploredActions: Set[Action]): State = {
val to = getState(descriptors, neverExploredActions)
allExploredActions += a
allNeverExploredActions -= a
allExploredActions = allExploredActions + a
allNeverExploredActions = allNeverExploredActions - a
val old = actionExecutionTimes.get(a)
old match {
case Some(o) => actionExecutionTimes = actionExecutionTimes + (a -> (o + 1))
case None => actionExecutionTimes = actionExecutionTimes + (a -> 1)
}
from.addTransition(a, to)
to
}

override def getAllNeverExploredActions: scala.collection.mutable.Set[Action] = allNeverExploredActions
override def getAllNeverExploredActions: Set[Action] = allNeverExploredActions

override def getAllExploredActions: Set[Action] = allExploredActions

override def getAllExploredActions: scala.collection.mutable.Set[Action] = allExploredActions
}
override def getActionExecutionTimes: Map[Action, Int] = actionExecutionTimes
}
34 changes: 21 additions & 13 deletions src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
package de.retest.guistatemachine.api.impl

import de.retest.guistatemachine.api.{Action, Descriptors, State}
import de.retest.guistatemachine.api.{Action, ActionTransitions, Descriptors, State}

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

class StateImpl(val descriptors: Descriptors, var neverExploredActions: Set[Action]) extends State {
class StateImpl(descriptors: Descriptors, var neverExploredActions: Set[Action]) extends State {

/**
* TODO #4 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.
*/
var transitions = new HashMap[Action, Set[State]]
var transitions = new HashMap[Action, ActionTransitions]

override def getDescriptors: Descriptors = descriptors
override def getNeverExploredActions: Set[Action] = neverExploredActions
override def getTransitions: Map[Action, Set[State]] = transitions
override def getTransitions: Map[Action, ActionTransitions] = transitions

private[api] override def addTransition(a: Action, to: State): Unit = {
if (!transitions.contains(a)) {
transitions = transitions + (a -> HashSet(to))
// In the legacy code this is done in `increaseTimesExecuted`.
neverExploredActions = neverExploredActions - a
} else {
transitions = transitions + (a -> (transitions(a) + to))
private[api] override def addTransition(a: Action, to: State): Int = {
val old = transitions.get(a)
old match {
case Some(o) => {
val updated = ActionTransitions(o.to + to, o.executionCounter + 1)
transitions = transitions + (a -> updated)
updated.executionCounter
}

case None => {
transitions += (a -> ActionTransitions(Set(to), 1))
// In the legacy code this is done in `increaseTimesExecuted`.
neverExploredActions = neverExploredActions - a
1
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ class Persistence {
def createStateMachine(): Id = stateMachines.stateMachines.addNewElement(StateMachine())

def deleteStateMachine(id: Id): Boolean = stateMachines.stateMachines.removeElement(id)
}
}
Loading

0 comments on commit 04cd7a8

Please sign in to comment.