diff --git a/.scalafmt.conf b/.scalafmt.conf index 2aa31b5..5d5d991 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1 +1 @@ -maxColumn = 160 \ No newline at end of file +maxColumn = 160 diff --git a/.travis.yml b/.travis.yml index 1e6e098..b9701a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,4 @@ script: - sbt clean coverage test coverageReport scalastyle doc assembly after_success: - - bash <(curl -s https://codecov.io/bash) \ No newline at end of file + - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index ab4b421..f4d1e94 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,24 @@ # GUI State Machine API -API for the creation and modification of nondeterministic finite automaton for the automatic generation of GUI tests with the help of a genetic algorithm. +API for the creation and modification of incomplete state machines which represent the exploration of a GUI application. +The states represent the GUI elements and the transitions represent the GUI actions. + This is a small code example of creating a new state machine, adding two states connected with a transition and saving the state machine: ```scala -import de.retest.guistatemachine.api.impl.GuiStateMachineApiImpl +import de.retest.guistatemachine.api.GuiStateMachineApi +import de.retest.guistatemachine.api.GuiStateMachineSerializer import de.retest.recheck.ui.descriptors.SutState import de.retest.surili.commons.actions.NavigateToAction -val guiStateMachineApi = new GuiStateMachineApiImpl -val stateMachineId = guiStateMachineApi.createStateMachine() -val stateMachine = guiStateMachineApi.getStateMachine(stateMachineId).get +val stateMachineId = GuiStateMachineApi().createStateMachine() +val stateMachine = GuiStateMachineApi().getStateMachine(stateMachineId).get val currentState = new SutState(currentDescriptors) val action = new NavigateToAction("http://google.com") val nextState = new SutState(nextDescriptors) stateMachine.executeAction(currentState, action, nextState) -stateMachine.saveGML("mystatemachine.gml") -stateMachine.save("mystatemachine.ser") + +GuiStateMachineSerializer.javaObjectStream(stateMachine).save("mystatemachine.ser") +GuiStateMachineSerializer.gml(stateMachine).save("mystatemachine.gml") ``` State machines can be saved as and loaded from files using Java object serialization/deserialization. diff --git a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala index 04297e5..a2e5222 100644 --- a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala +++ b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala @@ -30,7 +30,8 @@ trait GuiStateMachine { * @return The current state which the transition of a leads to. */ def executeAction(from: State, a: Action, to: State): State - def executeAction(fromSutState: SutState, a: Action, toSutState: SutState): State = executeAction(getState(fromSutState), a, getState(toSutState)) + def executeAction(fromSutState: SutState, a: Action, toSutState: SutState): State = + executeAction(getState(fromSutState), a, getState(toSutState)) def getAllStates: Map[SutState, State] @@ -55,26 +56,9 @@ trait GuiStateMachine { def clear(): Unit /** - * Stores the state machine on the disk. - * Persistence can be useful when the state machines become quite big and the generation/modification is interrupted - * and continued later. + * Clears the current states and assigns them from another state machine. * - * @param filePath The file which the state machine is stored into. + * @param other The other state machine. */ - def save(filePath: String): Unit - - /** - * Clears the state machine and loads it from the disk. - * - * @param filePath The file which the state machine is loaded from. - */ - def load(filePath: String): Unit - - /** - * Converts the state machines into GML which can be read by editors like yED. - * - * @param filePath The file which the GML data is stored into. - * @throws RuntimeException If a vertex or edge cannot be added, this exception is thrown. - */ - def saveGML(filePath: String): Unit + def assignFrom(other: GuiStateMachine): Unit } diff --git a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineApi.scala b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineApi.scala index 472873d..65f57d0 100644 --- a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineApi.scala +++ b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineApi.scala @@ -1,11 +1,9 @@ package de.retest.guistatemachine.api +import de.retest.guistatemachine.api.impl.GuiStateMachineApiImpl /** * This API allows the creation, modification and deletion of state machines ([[GuiStateMachine]]) which are created * during test generations with the help of Genetic Algorithms. - * To store the state machines permanently, you have to call [[GuiStateMachineApi.save]] manually. - * Otherwise, they will only be stored in the memory. - * [[GuiStateMachineApi.load]] allows loading state machines from a file. */ trait GuiStateMachineApi { @@ -36,20 +34,13 @@ trait GuiStateMachineApi { * Clears all state machines. */ def clear(): Unit +} - /** - * 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. - * - * @param filePath The file which the state machines are stored into. - */ - def save(filePath: String): Unit +object GuiStateMachineApi { + private val impl = new GuiStateMachineApiImpl /** - * Clears all current state machines and loads all state machines from the disk. - * - * @param filePath The file which the state machines are loaded from. + * @return The standard implementaiton of the API. */ - def load(filePath: String): Unit + def apply(): GuiStateMachineApi = impl } diff --git a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineSerializer.scala b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineSerializer.scala new file mode 100644 index 0000000..8eb76ea --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineSerializer.scala @@ -0,0 +1,13 @@ +package de.retest.guistatemachine.api + +import de.retest.guistatemachine.api.impl.serialization.{GuiStateMachinGMLSerializer, GuiStateMachineJavaObjectStreamSerializer} + +trait GuiStateMachineSerializer { + def save(filePath: String) + def load(filePath: String) +} + +object GuiStateMachineSerializer { + def javaObjectStream(guiStateMachine: GuiStateMachine): GuiStateMachineSerializer = new GuiStateMachineJavaObjectStreamSerializer(guiStateMachine) + def gml(guiStateMachine: GuiStateMachine): GuiStateMachineSerializer = new GuiStateMachinGMLSerializer(guiStateMachine) +} diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImpl.scala index fc8833f..74466d8 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImpl.scala @@ -1,11 +1,12 @@ package de.retest.guistatemachine.api.impl -import java.io.{FileInputStream, FileOutputStream, ObjectInputStream, ObjectOutputStream} - import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineApi, Id} +/** + * Thread-safe implementation of the API. It is thread-safe because it uses `IdMap`. + */ class GuiStateMachineApiImpl extends GuiStateMachineApi { - private var stateMachines = IdMap[GuiStateMachine]() + private val stateMachines = IdMap[GuiStateMachine]() override def createStateMachine(): Id = stateMachines.addNewElement(new GuiStateMachineImpl) @@ -14,19 +15,4 @@ class GuiStateMachineApiImpl extends GuiStateMachineApi { override def getStateMachine(id: Id): Option[GuiStateMachine] = stateMachines.getElement(id) override def clear(): Unit = stateMachines.clear() - - override def save(filePath: String): Unit = { - val oos = new ObjectOutputStream(new FileOutputStream(filePath)) - 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)) - val readStateMachines = ois.readObject.asInstanceOf[IdMap[GuiStateMachine]] - ois.close() - stateMachines = readStateMachines - } } 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 27b2488..5b30207 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala @@ -1,20 +1,18 @@ package de.retest.guistatemachine.api.impl -import java.io._ - -import com.github.systemdir.gml.YedGmlWriter 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 org.jgrapht.graph.DirectedPseudograph import scala.collection.immutable.{HashMap, HashSet} +/** + * Thread-safe implementation of a GUI state machine. + */ @SerialVersionUID(1L) class GuiStateMachineImpl extends GuiStateMachine with Serializable { @transient private val logger = Logger[GuiStateMachineImpl] - // Make it accessible from the impl package for unit tests. private var states = new HashMap[SutState, State] /** @@ -33,7 +31,7 @@ class GuiStateMachineImpl extends GuiStateMachine with Serializable { states(sutState) } else { logger.info(s"Create new state from SUT state with hash code ${sutState.hashCode()}") - val s = new StateImpl(sutState) + val s = StateImpl(sutState) states += (sutState -> s) s } @@ -62,69 +60,11 @@ class GuiStateMachineImpl extends GuiStateMachine with Serializable { actionExecutionTimes = new HashMap[Action, Int] } - 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 = this.synchronized { + override def assignFrom(other: GuiStateMachine): Unit = this.synchronized { clear() - val ois = new ObjectInputStream(new FileInputStream(filePath)) - val readStateMachine = ois.readObject.asInstanceOf[GuiStateMachineImpl] - ois.close() - states = readStateMachine.states - allExploredActions = readStateMachine.allExploredActions - actionExecutionTimes = readStateMachine.actionExecutionTimes - } - - type GraphType = DirectedPseudograph[SutState, GraphActionEdge] - - override def saveGML(filePath: String): Unit = this.synchronized { - // get graph from user - val toDraw = getGraph() - - // define the look and feel of the graph - val graphicsProvider = new GraphicsProvider - - // get the gml writer - val writer = - new YedGmlWriter.Builder[SutState, GraphActionEdge, AnyRef](graphicsProvider, YedGmlWriter.PRINT_LABELS: _*) - .setEdgeLabelProvider(_.toString) - .setVertexLabelProvider(sutState => "%s - hash code: %d".format(sutState.toString, sutState.hashCode())) - .build - - // write to file - val outputFile = new File(filePath) - 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 = { - val graph = new GraphType(classOf[GraphActionEdge]) - val allStatesSorted = getAllStates.toSeq.sortWith(hashCodeComparisonOfTuples) - allStatesSorted.foreach { x => - val vertex = x._1 - if (!graph.addVertex(vertex)) throw new RuntimeException(s"Failed to add vertex $vertex") - } - - allStatesSorted.foreach { x => - val fromVertex = x._1 - val allTransitionsSorted = x._2.getTransitions.toSeq.sortWith(hashCodeComparisonOfTuples) - - allTransitionsSorted foreach { transition => - val actionTransitions = transition._2 - val action = transition._1 - actionTransitions.to.foreach { toState => - val toVertex = toState.getSutState - val edge = GraphActionEdge(fromVertex, toVertex, action) - if (!graph.addEdge(fromVertex, toVertex, edge)) throw new RuntimeException(s"Failed to add edge $edge") - } - } - } - graph + val otherStateMachine = other.asInstanceOf[GuiStateMachineImpl] + states = otherStateMachine.states + allExploredActions = otherStateMachine.allExploredActions + actionExecutionTimes = otherStateMachine.actionExecutionTimes } - - private def hashCodeComparisonOfTuples[A, B](a: (A, B), b: (A, B)) = a._1.hashCode().compareTo(b._2.hashCode()) < 0 } diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/IdMap.scala b/src/main/scala/de/retest/guistatemachine/api/impl/IdMap.scala index 35e7fc5..deb969c 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/IdMap.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/IdMap.scala @@ -5,8 +5,8 @@ import de.retest.guistatemachine.api.Id import scala.collection.mutable.HashMap /** - * This custom type allows storing values using [[Id]] as key. - * We cannot extend immutable maps in Scala, so we have to keep it as field. + * This custom type allows storing values using [[Id]] as key. We cannot extend immutable maps in Scala, so we have to + * keep it as field. The implementation is thread-safe. */ @SerialVersionUID(1L) case class IdMap[T]() extends Serializable { @@ -48,6 +48,13 @@ case class IdMap[T]() extends Serializable { } object IdMap { + + /** + * Creates a new `IdMap` by a number of values. + * @param v The initial values of the `IdMap`. + * @tparam T The type of the values of the `IdMap`. + * @return A newly created `IdMap`. + */ def apply[T](v: T*): IdMap[T] = { val r = new IdMap[T]() for (e <- v) { r.addNewElement(e) } 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 661148c..2d226cf 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala @@ -10,7 +10,7 @@ import scala.collection.immutable.HashMap case class StateImpl(sutState: SutState) extends State with Serializable { /** - * TODO #4 Currently, there is no MultiMap trait for immutable maps in the Scala standard library. + * 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, ActionTransitions] diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/GraphActionEdge.scala b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GraphActionEdge.scala similarity index 79% rename from src/main/scala/de/retest/guistatemachine/api/impl/GraphActionEdge.scala rename to src/main/scala/de/retest/guistatemachine/api/impl/serialization/GraphActionEdge.scala index a9ba5d3..1f0ed86 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/GraphActionEdge.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GraphActionEdge.scala @@ -1,5 +1,4 @@ -package de.retest.guistatemachine.api.impl - +package de.retest.guistatemachine.api.impl.serialization import de.retest.recheck.ui.descriptors.SutState import de.retest.surili.commons.actions.Action diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/GraphicsProvider.scala b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GraphicsProvider.scala similarity index 94% rename from src/main/scala/de/retest/guistatemachine/api/impl/GraphicsProvider.scala rename to src/main/scala/de/retest/guistatemachine/api/impl/serialization/GraphicsProvider.scala index ec917a1..2e2381c 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/GraphicsProvider.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GraphicsProvider.scala @@ -1,4 +1,4 @@ -package de.retest.guistatemachine.api.impl +package de.retest.guistatemachine.api.impl.serialization import java.awt.Color diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachinGMLSerializer.scala b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachinGMLSerializer.scala new file mode 100644 index 0000000..752b61a --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachinGMLSerializer.scala @@ -0,0 +1,68 @@ +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 org.jgrapht.graph.DirectedPseudograph + +class GuiStateMachinGMLSerializer(guiStateMachine: GuiStateMachine) extends GuiStateMachineSerializer { + + type GraphType = DirectedPseudograph[SutState, GraphActionEdge] + + /** + * Converts the state machines into GML which can be read by editors like yED. + * + * @param filePath The file which the GML data is stored into. + * @throws RuntimeException If a vertex or edge cannot be added, this exception is thrown. + */ + override def save(filePath: String): Unit = { + // get graph from user + val toDraw = createGraph() + + // define the look and feel of the graph + val graphicsProvider = new GraphicsProvider + + // get the gml writer + val writer = + new YedGmlWriter.Builder[SutState, GraphActionEdge, AnyRef](graphicsProvider, YedGmlWriter.PRINT_LABELS: _*) + .setEdgeLabelProvider(_.toString) + .setVertexLabelProvider(sutState => "%s - hash code: %d".format(sutState.toString, sutState.hashCode())) + .build + + // write to file + val outputFile = new File(filePath) + val output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "utf-8")) + try { writer.export(output, toDraw) } finally { output.close() } + } + + override def load(filePath: String): Unit = throw new UnsupportedOperationException("Loading GML is not supported.") + + private def createGraph(): GraphType = { + val graph = new GraphType(classOf[GraphActionEdge]) + val allStatesSorted = guiStateMachine.getAllStates.toSeq.sortWith(hashCodeComparisonOfTuples) + allStatesSorted.foreach { x => + val vertex = x._1 + if (!graph.addVertex(vertex)) { throw new RuntimeException(s"Failed to add vertex $vertex") } + } + + allStatesSorted.foreach { x => + val fromVertex = x._1 + val allTransitionsSorted = x._2.getTransitions.toSeq.sortWith(hashCodeComparisonOfTuples) + + allTransitionsSorted foreach { transition => + val actionTransitions = transition._2 + val action = transition._1 + actionTransitions.to.foreach { toState => + val toVertex = toState.getSutState + val edge = GraphActionEdge(fromVertex, toVertex, action) + if (!graph.addEdge(fromVertex, toVertex, edge)) { throw new RuntimeException(s"Failed to add edge $edge") } + } + } + } + graph + } + + private def hashCodeComparisonOfTuples[A, B](a: (A, B), b: (A, B)) = a._1.hashCode().compareTo(b._2.hashCode()) < 0 + +} diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializer.scala b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializer.scala new file mode 100644 index 0000000..dc27536 --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializer.scala @@ -0,0 +1,39 @@ +package de.retest.guistatemachine.api.impl.serialization + +import java.io.{FileInputStream, FileOutputStream, ObjectInputStream, ObjectOutputStream} + +import de.retest.guistatemachine.api.impl.GuiStateMachineImpl +import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineSerializer} + +class GuiStateMachineJavaObjectStreamSerializer(guiStateMachine: GuiStateMachine) extends GuiStateMachineSerializer { + + /** + * Stores the state machine on the disk. Persistence can be useful when the state machines become quite big and the + * generation/modification is interrupted and continued later. + * + * @param filePath The file which the state machine is stored into. + */ + override def save(filePath: String): Unit = { + val oos = new ObjectOutputStream(new FileOutputStream(filePath)) + try { + oos.writeObject(guiStateMachine) + } finally { + oos.close() + } + } + + /** + * Clears the state machine and loads it from the disk. + * + * @param filePath The file which the state machine is loaded from. + */ + override def load(filePath: String): Unit = { + val ois = new ObjectInputStream(new FileInputStream(filePath)) + try { + val readStateMachine = ois.readObject.asInstanceOf[GuiStateMachineImpl] + guiStateMachine.assignFrom(readStateMachine) + } finally { + ois.close() + } + } +} diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImplSpec.scala index 1450f7c..31f1457 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImplSpec.scala @@ -1,91 +1,28 @@ package de.retest.guistatemachine.api.impl -import java.io.File -import java.util.Arrays - -import de.retest.guistatemachine.api.{AbstractApiSpec, Id} -import de.retest.recheck.ui.descriptors.SutState -import de.retest.surili.commons.actions.NavigateToAction +import de.retest.guistatemachine.api.{AbstractApiSpec, GuiStateMachineApi, Id} class GuiStateMachineApiImplSpec extends AbstractApiSpec { - val sut = new GuiStateMachineApiImpl - var stateMachineId = Id(-1) - "GuiStateMachineApi" should { "create, get and remove a new state machine" in { - stateMachineId = sut.createStateMachine() + val stateMachineId = GuiStateMachineApi().createStateMachine() stateMachineId shouldEqual Id(0) - val stateMachine = sut.getStateMachine(stateMachineId) + val stateMachine = GuiStateMachineApi().getStateMachine(stateMachineId) stateMachine.isDefined shouldBe true val fsm = stateMachine.get fsm.getActionExecutionTimes.size shouldEqual 0 fsm.getAllExploredActions.size shouldEqual 0 - sut.removeStateMachine(stateMachineId) shouldBe true + GuiStateMachineApi().removeStateMachine(stateMachineId) shouldBe true } "clear all state machines" in { - sut.createStateMachine shouldEqual Id(0) - sut.createStateMachine shouldEqual Id(1) - sut.createStateMachine shouldEqual Id(2) - sut.clear() - sut.getStateMachine(Id(2)).isEmpty shouldEqual true - } - - "save and load" in { - val filePath = "./target/test_state_machines" - val oldFile = new File(filePath) - - if (oldFile.exists()) oldFile.delete() shouldEqual true - - val rootElementA = getRootElement("a", 0) - val rootElementB = getRootElement("b", 0) - val rootElementC = getRootElement("c", 0) - val action0 = new NavigateToAction("http://google.com") - val action1 = new NavigateToAction("http://wikipedia.org") - - val initialSutState = new SutState(Arrays.asList(rootElementA, rootElementB, rootElementC)) - val finalSutState = new SutState(Arrays.asList(rootElementC)) - - // Create the whole state machine: - sut.clear() - stateMachineId = sut.createStateMachine() - val stateMachine = sut.getStateMachine(stateMachineId).get - val initialState = stateMachine.getState(initialSutState) - val finalState = stateMachine.getState(finalSutState) - stateMachine.executeAction(initialState, action0, finalState) - - // Save all state machines: - sut.save(filePath) - val f = new File(filePath) - f.exists() shouldEqual true - f.isDirectory shouldEqual false - - // Load all state machines: - sut.clear() - sut.load(filePath) - - // Verify all loaded state machines: - val loadedStateMachineOp = sut.getStateMachine(stateMachineId) - loadedStateMachineOp.isDefined shouldEqual true - val loadedStateMachine = loadedStateMachineOp.get.asInstanceOf[GuiStateMachineImpl] - - loadedStateMachine.getAllExploredActions.size shouldEqual 1 - loadedStateMachine.getActionExecutionTimes(action0) shouldEqual 1 - loadedStateMachine.getActionExecutionTimes.contains(action1) shouldEqual false - loadedStateMachine.getAllStates.size shouldEqual 2 - val loadedInitialState = loadedStateMachine.getAllStates(initialSutState) - val loadedFinalState = loadedStateMachine.getAllStates(finalSutState) - loadedInitialState.getSutState shouldEqual initialSutState - loadedInitialState.getTransitions.size shouldEqual 1 - loadedInitialState.getTransitions.contains(action0) shouldEqual true - val loadedTransition = loadedInitialState.getTransitions(action0) - loadedTransition.executionCounter shouldEqual 1 - loadedTransition.to.size shouldEqual 1 - loadedTransition.to.head shouldEqual loadedFinalState - loadedFinalState.getSutState shouldEqual finalSutState - loadedFinalState.getTransitions.isEmpty shouldEqual true + GuiStateMachineApi().createStateMachine shouldEqual Id(0) + GuiStateMachineApi().createStateMachine shouldEqual Id(1) + GuiStateMachineApi().createStateMachine shouldEqual Id(2) + GuiStateMachineApi().clear() + GuiStateMachineApi().getStateMachine(Id(2)).isEmpty shouldEqual true } } } 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 1992769..d620158 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala @@ -1,11 +1,10 @@ package de.retest.guistatemachine.api.impl -import java.io.File import java.util.Arrays import de.retest.guistatemachine.api.AbstractApiSpec import de.retest.recheck.ui.descriptors.SutState -import de.retest.surili.commons.actions.{Action, NavigateToAction} +import de.retest.surili.commons.actions.NavigateToAction import org.scalatest.BeforeAndAfterEach class GuiStateMachineImplSpec extends AbstractApiSpec with BeforeAndAfterEach { @@ -94,88 +93,7 @@ class GuiStateMachineImplSpec extends AbstractApiSpec with BeforeAndAfterEach { sut.getActionExecutionTimes.isEmpty shouldEqual true sut.getAllStates.isEmpty shouldEqual true } - - "save and load" in { - val filePath = "./target/test_state_machine" - val oldFile = new File(filePath) - - if (oldFile.exists()) oldFile.delete() shouldEqual true - - val rootElementA = getRootElement("a", 0) - val rootElementB = getRootElement("b", 0) - val rootElementC = getRootElement("c", 0) - val action0 = new NavigateToAction("http://google.com") - val action1 = new NavigateToAction("http://wikipedia.org") - - val initialSutState = new SutState(Arrays.asList(rootElementA, rootElementB, rootElementC)) - val finalSutState = new SutState(Arrays.asList(rootElementC)) - - // Create the whole state machine: - sut.executeAction(initialSutState, action0, finalSutState) - - // Save the state machine: - sut.save(filePath) - val f = new File(filePath) - f.exists() shouldEqual true - f.isDirectory shouldEqual false - - // Load the state machine: - sut.clear() - sut.load(filePath) - - // Verify the loaded state machine: - sut.getAllExploredActions.size shouldEqual 1 - sut.getActionExecutionTimes(action0) shouldEqual 1 - sut.getActionExecutionTimes.contains(action1) shouldEqual false - sut.getAllStates.size shouldEqual 2 - val loadedInitialState = sut.getAllStates(initialSutState) - val loadedFinalState = sut.getAllStates(finalSutState) - loadedInitialState.getSutState shouldEqual initialSutState - loadedInitialState.getTransitions.size shouldEqual 1 - loadedInitialState.getTransitions.contains(action0) shouldEqual true - val loadedTransition = loadedInitialState.getTransitions(action0) - loadedTransition.executionCounter shouldEqual 1 - loadedTransition.to.size shouldEqual 1 - loadedTransition.to.head shouldEqual loadedFinalState - loadedFinalState.getSutState shouldEqual finalSutState - loadedFinalState.getTransitions.isEmpty shouldEqual true - } - - "save GML " in { - val rootElementA = getRootElement("a", 0) - val rootElementB = getRootElement("b", 0) - val rootElementC = getRootElement("c", 0) - val action0 = new NavigateToAction("http://google.com") - val action1 = new NavigateToAction("http://wikipedia.org") - - val initialSutState = new SutState(Arrays.asList(rootElementA, rootElementB, rootElementC)) - val finalSutState = new SutState(Arrays.asList(rootElementC)) - - // Create the whole state machine: - sut.clear() - val initialState = sut.getState(initialSutState) - val finalState = sut.getState(finalSutState) - sut.executeAction(initialSutState, action0, finalSutState) - sut.executeAction(initialSutState, action1, finalSutState) - sut.executeAction(finalSutState, action0, initialSutState) - sut.executeAction(finalSutState, action1, initialSutState) - - initialState.getTransitions.size shouldEqual 2 - finalState.getTransitions.size shouldEqual 2 - - val filePath = "./target/test_state_machine.gml" - val oldFile = new File(filePath) - - if (oldFile.exists()) oldFile.delete() shouldEqual true - - sut.saveGML(filePath) - - val f = new File(filePath) - f.exists() shouldEqual true - f.isDirectory shouldEqual false - } } - def getSutState: SutState = new SutState(Arrays.asList(rootElementA, rootElementB, rootElementC)) - def getNeverExploredActions: Set[Action] = Set[Action](action0, action1) + private def getSutState: SutState = new SutState(Arrays.asList(rootElementA, rootElementB, rootElementC)) } diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/IdMapSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/IdMapSpec.scala index ae12446..e5f1187 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/IdMapSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/IdMapSpec.scala @@ -12,6 +12,7 @@ class IdMapSpec extends WordSpec with Matchers { map.hasElement(Id(0)) shouldEqual true map.getElement(Id(0)).get shouldEqual 1 map.removeElement(Id(0)) shouldEqual true + map.removeElement(Id(0)) shouldEqual false map.hasElement(Id(0)) shouldEqual false map.size shouldEqual 2 map.addNewElement(4) shouldEqual Id(0) 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 1af3dd8..3f25de6 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala @@ -13,22 +13,22 @@ class StateImplSpec extends AbstractApiSpec { "StateImpl" should { "not equal" in { - val s0 = new StateImpl(sutStateA) - val s1 = new StateImpl(sutStateB) + val s0 = StateImpl(sutStateA) + val s1 = StateImpl(sutStateB) s0.equals(s1) shouldEqual false s0.equals(10) shouldEqual false s0.hashCode() should not equal s1.hashCode() } "equal" in { - val s0 = new StateImpl(sutStateA) - val s1 = new StateImpl(sutStateA) + val s0 = StateImpl(sutStateA) + val s1 = StateImpl(sutStateA) s0.equals(s1) shouldEqual true s0.hashCode() shouldEqual s1.hashCode() } "be converted into a string" in { - val s0 = new StateImpl(sutStateA) + val s0 = StateImpl(sutStateA) s0.toString shouldEqual "sutState=State[descriptor=[]],transitions=Map()" } } 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 new file mode 100644 index 0000000..770d770 --- /dev/null +++ b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala @@ -0,0 +1,170 @@ +package de.retest.guistatemachine.api.impl.serialization + +import java.io.File +import java.util.Arrays + +import de.retest.guistatemachine.api.impl.GuiStateMachineImpl +import de.retest.guistatemachine.api.{AbstractApiSpec, GuiStateMachineSerializer} +import de.retest.recheck.ui.descriptors.SutState +import de.retest.surili.commons.actions.NavigateToAction +import org.scalatest.BeforeAndAfterEach + +class GuiStateMachineGMLSerializerSpec extends AbstractApiSpec with BeforeAndAfterEach { + private val guiStateMachine = new GuiStateMachineImpl + + override def beforeEach() { + guiStateMachine.clear() + } + + "GuiStateMachineGMLSerializer" should { + "save GML " in { + val rootElementA = getRootElement("a", 0) + val rootElementB = getRootElement("b", 0) + val rootElementC = getRootElement("c", 0) + val action0 = new NavigateToAction("http://google.com") + val action1 = new NavigateToAction("http://wikipedia.org") + + val initialSutState = new SutState(Arrays.asList(rootElementA, rootElementB, rootElementC)) + val finalSutState = new SutState(Arrays.asList(rootElementC)) + + // Create the whole state machine: + val initialState = guiStateMachine.getState(initialSutState) + val finalState = guiStateMachine.getState(finalSutState) + guiStateMachine.executeAction(initialSutState, action0, finalSutState) + guiStateMachine.executeAction(initialSutState, action1, finalSutState) + guiStateMachine.executeAction(finalSutState, action0, initialSutState) + guiStateMachine.executeAction(finalSutState, action1, initialSutState) + + initialState.getTransitions.size shouldEqual 2 + finalState.getTransitions.size shouldEqual 2 + + val filePath = "./target/test_state_machine.gml" + val oldFile = new File(filePath) + + if (oldFile.exists()) { oldFile.delete() } shouldEqual true + + GuiStateMachineSerializer.gml(guiStateMachine).save(filePath) + + val f = new File(filePath) + f.exists() shouldEqual true + f.isDirectory shouldEqual false + + val lines = scala.io.Source.fromFile(filePath).mkString + lines shouldEqual + """Creator "JGraphT GML Exporter - modified by Hayato Hess, Andreas Hofstadler" + |Version 1 + |graph + |[ + | label "" + | directed 1 + | node + | [ + | id 1 + | label "State[descriptor=[]] - hash code: 9132415" + | graphics + | [ + | type "rectangle" + | fill "#c0c0c0ff" + | line "#000000ff" + | ] + | LabelGraphics + | [ + | fontStyle "ITALIC" + | ] + | ] + | node + | [ + | id 2 + | label "State[descriptor=[, , ]] - hash code: 416617022" + | graphics + | [ + | type "rectangle" + | fill "#c0c0c0ff" + | line "#000000ff" + | ] + | LabelGraphics + | [ + | fontStyle "ITALIC" + | ] + | ] + | edge + | [ + | id 3 + | source 1 + | target 2 + | label "NavigateToAction(url=http://google.com)" + | LabelGraphics + | [ + | model "centered" + | position "center" + | ] + | graphics + | [ + | fill "#000000ff" + | style "DASHED" + | targetArrow "short" + | ] + | ] + | edge + | [ + | id 4 + | source 1 + | target 2 + | label "NavigateToAction(url=http://wikipedia.org)" + | LabelGraphics + | [ + | model "centered" + | position "center" + | ] + | graphics + | [ + | fill "#000000ff" + | style "DASHED" + | targetArrow "short" + | ] + | ] + | edge + | [ + | id 5 + | source 2 + | target 1 + | label "NavigateToAction(url=http://google.com)" + | LabelGraphics + | [ + | model "centered" + | position "center" + | ] + | graphics + | [ + | fill "#000000ff" + | style "DASHED" + | targetArrow "short" + | ] + | ] + | edge + | [ + | id 6 + | source 2 + | target 1 + | label "NavigateToAction(url=http://wikipedia.org)" + | LabelGraphics + | [ + | model "centered" + | position "center" + | ] + | graphics + | [ + | fill "#000000ff" + | style "DASHED" + | targetArrow "short" + | ] + | ] + |] + |""".stripMargin + } + + "load GML " in { + 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 new file mode 100644 index 0000000..0853c21 --- /dev/null +++ b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala @@ -0,0 +1,66 @@ +package de.retest.guistatemachine.api.impl.serialization + +import java.io.File +import java.util.Arrays + +import de.retest.guistatemachine.api.impl.GuiStateMachineImpl +import de.retest.guistatemachine.api.{AbstractApiSpec, GuiStateMachineSerializer} +import de.retest.recheck.ui.descriptors.SutState +import de.retest.surili.commons.actions.NavigateToAction +import org.scalatest.BeforeAndAfterEach + +class GuiStateMachineJavaObjectStreamSerializerSpec extends AbstractApiSpec with BeforeAndAfterEach { + private val guiStateMachine = new GuiStateMachineImpl + + override def beforeEach() { + guiStateMachine.clear() + } + + "GuiStateMachineJavaObjectStreamSerializer" should { + "save and load" in { + val filePath = "./target/test_state_machine" + val oldFile = new File(filePath) + + if (oldFile.exists()) { oldFile.delete() } shouldEqual true + + val rootElementA = getRootElement("a", 0) + val rootElementB = getRootElement("b", 0) + val rootElementC = getRootElement("c", 0) + val action0 = new NavigateToAction("http://google.com") + val action1 = new NavigateToAction("http://wikipedia.org") + + val initialSutState = new SutState(Arrays.asList(rootElementA, rootElementB, rootElementC)) + val finalSutState = new SutState(Arrays.asList(rootElementC)) + + // Create the whole state machine: + guiStateMachine.executeAction(initialSutState, action0, finalSutState) + + // Save the state machine: + GuiStateMachineSerializer.javaObjectStream(guiStateMachine).save(filePath) + val f = new File(filePath) + f.exists() shouldEqual true + f.isDirectory shouldEqual false + + // Load the state machine: + guiStateMachine.clear() + GuiStateMachineSerializer.javaObjectStream(guiStateMachine).load(filePath) + + // Verify the loaded state machine: + guiStateMachine.getAllExploredActions.size shouldEqual 1 + guiStateMachine.getActionExecutionTimes(action0) shouldEqual 1 + guiStateMachine.getActionExecutionTimes.contains(action1) shouldEqual false + guiStateMachine.getAllStates.size shouldEqual 2 + val loadedInitialState = guiStateMachine.getAllStates(initialSutState) + val loadedFinalState = guiStateMachine.getAllStates(finalSutState) + loadedInitialState.getSutState shouldEqual initialSutState + loadedInitialState.getTransitions.size shouldEqual 1 + loadedInitialState.getTransitions.contains(action0) shouldEqual true + val loadedTransition = loadedInitialState.getTransitions(action0) + loadedTransition.executionCounter shouldEqual 1 + loadedTransition.to.size shouldEqual 1 + loadedTransition.to.head shouldEqual loadedFinalState + loadedFinalState.getSutState shouldEqual finalSutState + loadedFinalState.getTransitions.isEmpty shouldEqual true + } + } +}