Skip to content

Commit

Permalink
Merge pull request #1 from johannesduesing/feature/upgrade-opal
Browse files Browse the repository at this point in the history
Upgrade to OPAL 4.0.0
  • Loading branch information
johannesduesing authored Jun 18, 2021
2 parents e7b7ccf + d6f09a0 commit 3717948
Show file tree
Hide file tree
Showing 11 changed files with 63 additions and 70 deletions.
13 changes: 4 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,11 @@ libraryDependencies ++= Seq(

resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

val opalVersion = "1.0.0"
val opalVersion = "4.0.0"
libraryDependencies ++= Seq(
"de.opal-project" % "common_2.12" % opalVersion
exclude("com.fasterxml.jackson.datatype", "jackson-datatype-jsr310"),
//https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONDATATYPE-173759
"com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.8",

"de.opal-project" % "opal-developer-tools_2.12" % opalVersion
exclude("com.google.protobuf", "protobuf-java"),
"com.google.protobuf" % "protobuf-java" % "3.4.0"
"de.opal-project" % "common_2.12" % opalVersion,
"de.opal-project" % "framework_2.12" % opalVersion,
"de.opal-project" % "hermes_2.12" % opalVersion
)

val mavenVersion = "3.5.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package de.upb.cs.swt.delphi.crawler.processing


import java.io.FileNotFoundException

import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import akka.pattern.ask
import akka.stream._
Expand All @@ -16,7 +15,7 @@ import de.upb.cs.swt.delphi.crawler.discovery.maven.MavenIdentifier
import de.upb.cs.swt.delphi.crawler.preprocessing.{JarFile, MavenDownloader, PomFile}
import de.upb.cs.swt.delphi.crawler.processing.CallGraphStream.MappedEdge
import de.upb.cs.swt.delphi.crawler.storage.ElasticCallGraphActor
import org.opalj.ai.analyses.cg.UnresolvedMethodCall
import org.opalj.br.DeclaredMethod

import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
Expand Down Expand Up @@ -60,23 +59,23 @@ class CallGraphStream(configuration: Configuration) extends Actor with ActorLogg
val esPushActor: ActorRef = context.actorOf(ElasticCallGraphActor.props(esClient))

val fileGenFlow: Flow[MavenIdentifier, (PomFile, JarFile, MavenIdentifier), NotUsed] = Flow.fromFunction(fetchFiles)
val edgeSetFlow: Flow[(Set[MavenIdentifier], JarFile, MavenIdentifier), (Set[UnresolvedMethodCall], Set[MavenIdentifier], MavenIdentifier), NotUsed] =
val edgeSetFlow: Flow[(Set[MavenIdentifier], JarFile, MavenIdentifier), (Set[DeclaredMethod], Set[MavenIdentifier], MavenIdentifier), NotUsed] =
Flow[(Set[MavenIdentifier], JarFile, MavenIdentifier)].mapAsync(parallelism){ case (ix: Set[MavenIdentifier], jf: JarFile, i: MavenIdentifier) =>
(opalActor ? jf).mapTo[Set[UnresolvedMethodCall]].map(cx => (cx, ix, i))}
(opalActor ? jf).mapTo[Set[DeclaredMethod]].map(cx => (cx, ix, i))}
val dependencyConverter: Flow[(PomFile, JarFile, MavenIdentifier), (Set[MavenIdentifier], JarFile, MavenIdentifier), NotUsed] =
Flow[(PomFile, JarFile, MavenIdentifier)].mapAsync(parallelism){ case (pf, jf, id) => (mavenDependencyActor ? pf).mapTo[Set[MavenIdentifier]].map((_, jf, id))}

val esEdgeMatcher: Flow[(Set[UnresolvedMethodCall], Set[MavenIdentifier], MavenIdentifier), (Set[UnresolvedMethodCall], Set[MavenIdentifier], Set[MappedEdge], MavenIdentifier), NotUsed] =
Flow[(Set[UnresolvedMethodCall], Set[MavenIdentifier], MavenIdentifier)].mapAsync(parallelism){ case (mxIn, ixIn, i) => (esEdgeSearchActor ? (mxIn, ixIn))
.mapTo[(Set[UnresolvedMethodCall], Set[MavenIdentifier], Set[MappedEdge])].map{case (mxOut, ixOut, ex) => (mxOut, ixOut, ex, i)}}
val mavenEdgeMatcher: Flow[(Set[UnresolvedMethodCall], Set[MavenIdentifier], Set[MappedEdge], MavenIdentifier), (Set[MappedEdge], MavenIdentifier), NotUsed] =
Flow[(Set[UnresolvedMethodCall], Set[MavenIdentifier], Set[MappedEdge], MavenIdentifier)].mapAsync(parallelism){ case (mx, ix, ex, i)
val esEdgeMatcher: Flow[(Set[DeclaredMethod], Set[MavenIdentifier], MavenIdentifier), (Set[DeclaredMethod], Set[MavenIdentifier], Set[MappedEdge], MavenIdentifier), NotUsed] =
Flow[(Set[DeclaredMethod], Set[MavenIdentifier], MavenIdentifier)].mapAsync(parallelism){ case (mxIn, ixIn, i) => (esEdgeSearchActor ? (mxIn, ixIn))
.mapTo[(Set[DeclaredMethod], Set[MavenIdentifier], Set[MappedEdge])].map{case (mxOut, ixOut, ex) => (mxOut, ixOut, ex, i)}}
val mavenEdgeMatcher: Flow[(Set[DeclaredMethod], Set[MavenIdentifier], Set[MappedEdge], MavenIdentifier), (Set[MappedEdge], MavenIdentifier), NotUsed] =
Flow[(Set[DeclaredMethod], Set[MavenIdentifier], Set[MappedEdge], MavenIdentifier)].mapAsync(parallelism){ case (mx, ix, ex, i)
=> (mavenEdgeMapActor ? (mx, ix)).mapTo[Set[MappedEdge]].map(me => (me ++ ex, i))}

val esPusherSink: Sink[(Set[MappedEdge], MavenIdentifier), Future[Done]] =
Sink.foreach{ case (ex, i) => (esPushActor ! (i, ex))}

val edgeGeneratingGraph: Flow[MavenIdentifier, (Set[UnresolvedMethodCall], Set[MavenIdentifier], MavenIdentifier), NotUsed] =
val edgeGeneratingGraph: Flow[MavenIdentifier, (Set[DeclaredMethod], Set[MavenIdentifier], MavenIdentifier), NotUsed] =
Flow.fromGraph(GraphDSL.create() { implicit b =>

val fileGen = b.add(fileGenFlow)
Expand All @@ -89,7 +88,7 @@ class CallGraphStream(configuration: Configuration) extends Actor with ActorLogg
FlowShape(fileGen.in, edgeGen.out)
})

val edgeMatchingGraph: Flow[(Set[UnresolvedMethodCall], Set[MavenIdentifier], MavenIdentifier), (Set[MappedEdge], MavenIdentifier), NotUsed] =
val edgeMatchingGraph: Flow[(Set[DeclaredMethod], Set[MavenIdentifier], MavenIdentifier), (Set[MappedEdge], MavenIdentifier), NotUsed] =
Flow.fromGraph(GraphDSL.create() { implicit b =>
val esMatcher = b.add(esEdgeMatcher)
val mvMatcher = b.add(mavenEdgeMatcher)
Expand Down Expand Up @@ -130,5 +129,5 @@ object CallGraphStream{
def props(configuration: Configuration) = Props(new CallGraphStream(configuration))

case class MappedEdge(library: MavenIdentifier, method: String) //I'm not sure if this is the best place to put these
def unresMCtoStr(m: UnresolvedMethodCall): String = m.calleeClass.toJava + ": " + m.calleeDescriptor.toJava(m.calleeName)
def unresMCtoStr(m: DeclaredMethod): String = m.declaringClassType.toJava + ": " + m.toJava
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import com.sksamuel.elastic4s.http.ElasticDsl._
import com.sksamuel.elastic4s.http.search.MultiSearchResponse
import de.upb.cs.swt.delphi.crawler.discovery.maven.MavenIdentifier
import de.upb.cs.swt.delphi.crawler.processing.CallGraphStream.{MappedEdge, unresMCtoStr}
import org.opalj.ai.analyses.cg.UnresolvedMethodCall

import org.opalj.br.DeclaredMethod

import scala.util.Try

Expand All @@ -22,7 +23,7 @@ class ElasticEdgeSearchActor(client: ElasticClient) extends Actor with ActorLogg
val maxBatchSize = 100

override def receive: Receive = {
case (mx: Set[UnresolvedMethodCall], ix: Set[MavenIdentifier]) => {
case (mx: Set[DeclaredMethod], ix: Set[MavenIdentifier]) => {
Try(segmentFun(searchMethods, maxBatchSize)(mx, ix.toList)) match {
case util.Success((unmapped, mapped)) =>
sender() ! (unmapped, ix, mapped)
Expand All @@ -36,8 +37,8 @@ class ElasticEdgeSearchActor(client: ElasticClient) extends Actor with ActorLogg

//Splits the set of methods in "batch" sized chucks before passing them to the search function, to prevent
// the construction of a search too large to be run
private def segmentFun(fx: (Set[UnresolvedMethodCall], List[MavenIdentifier]) => (Set[UnresolvedMethodCall], Set[MappedEdge]), batch: Int)
(mx: Set[UnresolvedMethodCall], ix: List[MavenIdentifier]): (Set[UnresolvedMethodCall], Set[MappedEdge]) = {
private def segmentFun(fx: (Set[DeclaredMethod], List[MavenIdentifier]) => (Set[DeclaredMethod], Set[MappedEdge]), batch: Int)
(mx: Set[DeclaredMethod], ix: List[MavenIdentifier]): (Set[DeclaredMethod], Set[MappedEdge]) = {
if (mx.size > batch) {
mx.splitAt(batch) match { case (currSeg, restSeg) =>
val segmentResults = fx (currSeg, ix)
Expand All @@ -51,7 +52,7 @@ class ElasticEdgeSearchActor(client: ElasticClient) extends Actor with ActorLogg
}
}

def genSearchDef(call: UnresolvedMethodCall, id: MavenIdentifier) = {
def genSearchDef(call: DeclaredMethod, id: MavenIdentifier) = {
search("delphi").query {
nestedQuery("calls",
boolQuery().must(
Expand All @@ -62,7 +63,7 @@ class ElasticEdgeSearchActor(client: ElasticClient) extends Actor with ActorLogg
}
}

def searchEsDb(calls: List[UnresolvedMethodCall], id: MavenIdentifier): UnresolvedMethodCall => Boolean = {
def searchEsDb(calls: List[DeclaredMethod], id: MavenIdentifier): DeclaredMethod => Boolean = {
val resp: Response[MultiSearchResponse] = client.execute{
multi(
for(call <- calls) yield genSearchDef(call, id)
Expand All @@ -76,8 +77,8 @@ class ElasticEdgeSearchActor(client: ElasticClient) extends Actor with ActorLogg
hits.contains
}

private def searchMethods(calls: Set[UnresolvedMethodCall], ids: List[MavenIdentifier]): (Set[UnresolvedMethodCall], Set[MappedEdge]) = {
if (calls.isEmpty) (Set[UnresolvedMethodCall](), Set[MappedEdge]())
private def searchMethods(calls: Set[DeclaredMethod], ids: List[MavenIdentifier]): (Set[DeclaredMethod], Set[MappedEdge]) = {
if (calls.isEmpty) (Set[DeclaredMethod](), Set[MappedEdge]())

ids.headOption match {
case None => (calls, Set[MappedEdge]())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,31 @@ package de.upb.cs.swt.delphi.crawler.processing

import java.io._
import java.net.URL

import org.opalj.br.analyses.Project
import org.opalj.hermes._

import scala.collection.JavaConverters.asScalaIteratorConverter

/**
* Custom Hermes runner for Delphi
*
* @author Ben Hermann
*
*/
class HermesAnalyzer(project: Project[URL]) extends HermesCore {
initialize(HermesAnalyzer.temporaryConfigFile())


override def updateProjectData(f: => Unit): Unit = Hermes.synchronized {
override def updateProjectData(f: => Unit): Unit = HermesAnalyzer.synchronized {
f
}

override def reportProgress(f: => Double): Unit = Hermes.synchronized {
override def reportProgress(f: => Double): Unit = HermesAnalyzer.synchronized {
f
}

def analyzeProject(): Iterator[(FeatureQuery, TraversableOnce[Feature[URL]])] = {
for {
projectFeatures <- featureMatrix.iterator
projectFeatures <- featureMatrix.iterator().asScala
projectConfiguration = projectFeatures.projectConfiguration
(featureQuery, features) <- projectFeatures.featureGroups.par
} yield {
Expand All @@ -58,7 +59,7 @@ class HermesAnalyzer(project: Project[URL]) extends HermesCore {


override lazy val registeredQueries: List[Query] = {
queries.values.flatten.map { s => Query(s, true) }.toList
queries.values.flatten.map { s => new Query(s, true) }.toList
}

val VERY_SLOW = 'VERY_SLOW
Expand Down Expand Up @@ -116,11 +117,11 @@ object HermesAnalyzer extends HermesCore {
tempConfigFile
}

override def updateProjectData(f: => Unit): Unit = Hermes.synchronized {
override def updateProjectData(f: => Unit): Unit = HermesAnalyzer.synchronized {
f
}

override def reportProgress(f: => Double): Unit = Hermes.synchronized {
override def reportProgress(f: => Double): Unit = HermesAnalyzer.synchronized {
f
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ package de.upb.cs.swt.delphi.crawler.processing

import java.io.FileNotFoundException
import java.util.jar.JarInputStream

import akka.actor.{Actor, ActorLogging, Props}
import de.upb.cs.swt.delphi.crawler.Configuration
import de.upb.cs.swt.delphi.crawler.discovery.maven.MavenIdentifier
import de.upb.cs.swt.delphi.crawler.preprocessing.MavenDownloader
import de.upb.cs.swt.delphi.crawler.processing.CallGraphStream.{MappedEdge, unresMCtoStr}
import de.upb.cs.swt.delphi.crawler.tools.ClassStreamReader
import org.opalj.ai.analyses.cg.UnresolvedMethodCall
import org.opalj.br.DeclaredMethod

import scala.util.Try

Expand All @@ -22,7 +21,7 @@ import scala.util.Try

class MavenEdgeMappingActor(configuration: Configuration) extends Actor with ActorLogging{
override def receive: Receive = {
case (mx: Set[UnresolvedMethodCall], ix: Set[MavenIdentifier]) => {
case (mx: Set[DeclaredMethod], ix: Set[MavenIdentifier]) => {
Try(matchEdges(mx, ix)) match {
case util.Success(ex) => sender() ! ex
case util.Failure(e) => {
Expand All @@ -33,7 +32,7 @@ class MavenEdgeMappingActor(configuration: Configuration) extends Actor with Act
}
}

def edgeSearch(edgeSet: Set[UnresolvedMethodCall], mavenList: List[MavenIdentifier]): Set[MappedEdge] = {
def edgeSearch(edgeSet: Set[DeclaredMethod], mavenList: List[MavenIdentifier]): Set[MappedEdge] = {
if (edgeSet.isEmpty) {
Set[MappedEdge]()
} else {
Expand All @@ -46,7 +45,8 @@ class MavenEdgeMappingActor(configuration: Configuration) extends Actor with Act
case Some(id) => {
Try {
val library = loadProject(id)
edgeSet.partition(m => library.resolveMethodReference(m.calleeClass, m.calleeName, m.calleeDescriptor).isDefined) match {
edgeSet.partition(m => library.resolveMethodReference(m.declaringClassType,
m.name, m.descriptor).isDefined) match {
case (hits, misses) => {
val mappedEdges = hits.map(m => MappedEdge(id, unresMCtoStr(m)))
mappedEdges ++ edgeSearch(misses, mavenList.tail)
Expand Down Expand Up @@ -78,7 +78,7 @@ class MavenEdgeMappingActor(configuration: Configuration) extends Actor with Act
project
}

private def matchEdges(edgeSet: Set[UnresolvedMethodCall], mavenSet: Set[MavenIdentifier]): Set[MappedEdge] = {
private def matchEdges(edgeSet: Set[DeclaredMethod], mavenSet: Set[MavenIdentifier]): Set[MappedEdge] = {
edgeSearch(edgeSet, mavenSet.toList)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ package de.upb.cs.swt.delphi.crawler.processing
import java.io.{File, InputStream}
import java.net.URL
import java.util.jar.JarInputStream

import akka.actor.{Actor, ActorLogging, Props}
import de.upb.cs.swt.delphi.crawler.Configuration
import de.upb.cs.swt.delphi.crawler.preprocessing.JarFile
import de.upb.cs.swt.delphi.crawler.tools.ClassStreamReader
import org.opalj.AnalysisModes
import org.opalj.br.analyses.AnalysisModeConfigFactory
import org.opalj.ai.analyses.cg.{CallGraphFactory, ExtVTACallGraphAlgorithmConfiguration}
import org.opalj.ai.analyses.cg.CallGraphFactory.defaultEntryPointsForLibraries

import org.opalj.br.VirtualDeclaredMethod
import org.opalj.tac.cg.{CallGraph, XTACallGraphKey}

import scala.util.Try

Expand Down Expand Up @@ -39,12 +37,13 @@ class OpalActor(configuration: Configuration) extends Actor with ActorLogging{
private def findCalls(is: InputStream, url: URL) = {
val p = new ClassStreamReader {}.createProject(url, new JarInputStream(is))
is.close()
val cpaP = AnalysisModeConfigFactory.resetAnalysisMode(p, AnalysisModes.OPA)
val entryPoints = () => defaultEntryPointsForLibraries(cpaP)
val config = new ExtVTACallGraphAlgorithmConfiguration(cpaP)
val callGraph = CallGraphFactory.create(cpaP, entryPoints, config)

callGraph.unresolvedMethodCalls.toSet
val cg: CallGraph = p.get(XTACallGraphKey)
cg.reachableMethods().filter{ method =>
(method.isInstanceOf[VirtualDeclaredMethod] ||
!p.allProjectClassFiles.exists(_.thisType.equals(method.declaringClassType))) &&
cg.calleesOf(method).isEmpty
}.toSet
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ trait ClassStreamReader {
futures ::= Future[List[(ClassFile, String)]] {
val cfs = reader.ClassFile(new DataInputStream(new ByteArrayInputStream(entryBytes)))
cfs map { cf => (cf, entryName) }
}(org.opalj.concurrent.OPALExecutionContext)
}(org.opalj.concurrent.OPALUnboundedExecutionContext)
}
je = in.getNextJarEntry()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,11 @@ class MavenDownloadActorTest extends TestKit(ActorSystem("DownloadActor"))
with WordSpecLike
with Matchers
with BeforeAndAfterAll {
override def afterAll {
TestKit.shutdownActorSystem(system)
}


"The maven download actor" must {
"create a maven artifact with a jar and pom file" in {
val mavenIdentifier = new MavenIdentifier("http://central.maven.org/maven2/", "junit", "junit", "4.12")
val mavenIdentifier = new MavenIdentifier("https://repo1.maven.org/maven2/", "junit", "junit", "4.12")
val downloadActor = system.actorOf(MavenDownloadActor.props)

implicit val timeout = Timeout(10 seconds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import de.upb.cs.swt.delphi.crawler.preprocessing.Common._

class MavenDownloaderSpec extends FlatSpec with Matchers {
"MavenDownloader" should "save jar file" in {
val mavenIdentifier = new MavenIdentifier("http://central.maven.org/maven2/", "junit", "junit", "4.12")
val mavenIdentifier = new MavenIdentifier("https://repo1.maven.org/maven2/", "junit", "junit", "4.12")
val downloader = new MavenDownloader(mavenIdentifier)
val jarStream = downloader.downloadJar()
checkJar(jarStream.is)
}
"MavenDownloader" should "save pom file" in {
val mavenIdentifier = new MavenIdentifier("http://central.maven.org/maven2/", "junit", "junit", "4.12")
val mavenIdentifier = new MavenIdentifier("https://repo1.maven.org/maven2/", "junit", "junit", "4.12")
val downloader = new MavenDownloader(mavenIdentifier)
val pomStream = downloader.downloadPom()
checkPom(pomStream.is)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ import org.scalatest.{FlatSpec, Matchers}

class MavenURLConstructionCheck extends FlatSpec with Matchers {
"Regular identifiers" should "create regular urls" in {
val reg1 = MavenIdentifier("http://repo1.maven.org/maven2/", "log4j", "log4j", "1.2.9")
val reg2 = MavenIdentifier("http://repo1.maven.org/maven2/", "de.tu-darmstadt.stg", "sootkeeper", "1.0")
val reg3 = MavenIdentifier("http://repo1.maven.org/maven2/", "de.tuebingen.uni.sfs.germanet", "germanet-api", "13.1.0")
val reg1 = MavenIdentifier("https://repo1.maven.org/maven2/", "log4j", "log4j", "1.2.9")
val reg2 = MavenIdentifier("https://repo1.maven.org/maven2/", "de.tu-darmstadt.stg", "sootkeeper", "1.0")
val reg3 = MavenIdentifier("https://repo1.maven.org/maven2/", "de.tuebingen.uni.sfs.germanet", "germanet-api", "13.1.0")

reg1.toJarLocation.toString shouldBe "http://repo1.maven.org/maven2/log4j/log4j/1.2.9/log4j-1.2.9.jar"
reg2.toJarLocation.toString shouldBe "http://repo1.maven.org/maven2/de/tu-darmstadt/stg/sootkeeper/1.0/sootkeeper-1.0.jar"
reg3.toJarLocation.toString shouldBe "http://repo1.maven.org/maven2/de/tuebingen/uni/sfs/germanet/germanet-api/13.1.0/germanet-api-13.1.0.jar"
reg1.toJarLocation.toString shouldBe "https://repo1.maven.org/maven2/log4j/log4j/1.2.9/log4j-1.2.9.jar"
reg2.toJarLocation.toString shouldBe "https://repo1.maven.org/maven2/de/tu-darmstadt/stg/sootkeeper/1.0/sootkeeper-1.0.jar"
reg3.toJarLocation.toString shouldBe "https://repo1.maven.org/maven2/de/tuebingen/uni/sfs/germanet/germanet-api/13.1.0/germanet-api-13.1.0.jar"

reg1.toPomLocation.toString shouldBe "http://repo1.maven.org/maven2/log4j/log4j/1.2.9/log4j-1.2.9.pom"
reg2.toPomLocation.toString shouldBe "http://repo1.maven.org/maven2/de/tu-darmstadt/stg/sootkeeper/1.0/sootkeeper-1.0.pom"
reg3.toPomLocation.toString shouldBe "http://repo1.maven.org/maven2/de/tuebingen/uni/sfs/germanet/germanet-api/13.1.0/germanet-api-13.1.0.pom"
reg1.toPomLocation.toString shouldBe "https://repo1.maven.org/maven2/log4j/log4j/1.2.9/log4j-1.2.9.pom"
reg2.toPomLocation.toString shouldBe "https://repo1.maven.org/maven2/de/tu-darmstadt/stg/sootkeeper/1.0/sootkeeper-1.0.pom"
reg3.toPomLocation.toString shouldBe "https://repo1.maven.org/maven2/de/tuebingen/uni/sfs/germanet/germanet-api/13.1.0/germanet-api-13.1.0.pom"
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class HermesTest extends FlatSpec with Matchers with BeforeAndAfterAll {

"HermesVersion" should "be a valid version string" in {
HermesAnalyzer.HermesVersion shouldBe a [String]
HermesAnalyzer.HermesVersion shouldBe "1.0.0"
HermesAnalyzer.HermesVersion shouldBe "4.0.0"
}


Expand Down

0 comments on commit 3717948

Please sign in to comment.