From f4c84625c350b33be2899c332c72e89d10861cb7 Mon Sep 17 00:00:00 2001 From: Viktoras Veitas Date: Wed, 28 Mar 2018 13:18:20 +0200 Subject: [PATCH 01/17] script for starting all dse related docker containers with remote ports on local server --- network-backends/dse-docker/scripts/start-docker.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/network-backends/dse-docker/scripts/start-docker.sh b/network-backends/dse-docker/scripts/start-docker.sh index c9f809c..f4a8798 100644 --- a/network-backends/dse-docker/scripts/start-docker.sh +++ b/network-backends/dse-docker/scripts/start-docker.sh @@ -1,7 +1,14 @@ #!/bin/sh # deleting all containers (not very efficient, but needed in order to run a container with the same name) +docker stop $(docker ps -a -q) docker ps -q -a | xargs docker rm # start dse-server with graph enabled -docker run -e DS_LICENSE=accept --name my-dse -d store/datastax/dse-server:5.1.6 -g +docker run -e DS_LICENSE=accept -e LISTEN_ADDRESS=127.0.0.1 -e START_RPC=true --name dse -d -p 8182:8182 -p 9042:9042 store/datastax/dse-server:5.1.6 -g + +# start dse-studio +docker run -e DS_LICENSE=accept --name dse-studio -d -p 9091:9091 --link dse datastax/dse-studio + +# start dse-opscenter +docker run -e DS_LICENSE=accept --name opscenter -d -p 8888:8888 datastax/dse-opscenter From 4d2f17c464f51d586ccbbf0b8ce4749c4541184e Mon Sep 17 00:00:00 2001 From: kabir Date: Wed, 28 Mar 2018 13:32:05 +0200 Subject: [PATCH 02/17] scripts for accessing dse based graph from gremlin console --- network-backends/dse-docker/scripts/gremlin.sh | 6 ++++++ .../dse-docker/scripts}/offernet.gremlin | 0 2 files changed, 6 insertions(+) create mode 100644 network-backends/dse-docker/scripts/gremlin.sh rename {scripts => network-backends/dse-docker/scripts}/offernet.gremlin (100%) diff --git a/network-backends/dse-docker/scripts/gremlin.sh b/network-backends/dse-docker/scripts/gremlin.sh new file mode 100644 index 0000000..74cfb93 --- /dev/null +++ b/network-backends/dse-docker/scripts/gremlin.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +HOST=192.168.1.6 +PORT=8182 + +/opt/dse/bin/dse gremlin-console $HOST:$PORTe diff --git a/scripts/offernet.gremlin b/network-backends/dse-docker/scripts/offernet.gremlin similarity index 100% rename from scripts/offernet.gremlin rename to network-backends/dse-docker/scripts/offernet.gremlin From 9f5930525ba35f180869d4c136eae7c3bf1b2894 Mon Sep 17 00:00:00 2001 From: kabir Date: Wed, 28 Mar 2018 21:51:16 +0200 Subject: [PATCH 03/17] some tests finally started to pass -- the codebase resurected (more or less) --- .gitignore | 1 + build.gradle | 3 ++- scripts/compileGroovy.sh | 8 ++++---- src/main/groovy/Agent.groovy | 7 ++++--- src/main/groovy/OfferNet.groovy | 4 +++- src/main/groovy/Simulation.groovy | 4 +++- src/test/groovy/Tests.groovy | 2 +- 7 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index e3358f5..c555e6f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ logs/* build/ resources/ temp/ +src/main/java/ \ No newline at end of file diff --git a/build.gradle b/build.gradle index cd0c043..82c3899 100644 --- a/build.gradle +++ b/build.gradle @@ -16,11 +16,12 @@ repositories { dependencies { compile 'junit:junit:4.12' - compile 'org.codehaus.groovy:groovy-all:2.4.1' + compile 'org.codehaus.groovy:groovy-all:2.4.11' compile 'com.datastax.dse:dse-java-driver-graph:1.5.1' compile 'org.apache.tinkerpop:gremlin-core:3.3.1' compile 'log4j:log4j:1.2.17' compile 'org.slf4j:slf4j-log4j12:1.7.22' compile 'com.typesafe.akka:akka-actor_2.12:2.5.11' + compile 'com.typesafe.akka:akka-actor-typed_2.12:2.5.11' testCompile 'com.typesafe.akka:akka-testkit_2.12:2.5.11' } diff --git a/scripts/compileGroovy.sh b/scripts/compileGroovy.sh index 22b8448..5f281a4 100755 --- a/scripts/compileGroovy.sh +++ b/scripts/compileGroovy.sh @@ -1,8 +1,8 @@ #!/bin/bash -if [ ! -d src/java ]; then mkdir -p src/java; else rm -r src/java/*; fi; -cp src/groovy/* src/java/ -cd src/java +if [ ! -d src/main/java ]; then mkdir -p src/main/java; else rm -r src/main/java/*; fi; +cp src/main/groovy/* src/main/java/ +cd src/main/java #remove all @Grab annotations from the file -- cannot be run by java #sed -i '/@Grab/d' *.groovy @@ -10,5 +10,5 @@ cd src/java echo $GROOVY_HOME #compile -groovyc -cp "~/.groovy/gradle/*" -encoding utf-8 *.groovy +groovyc -cp "/home/kabir/.gradle/*" -encoding utf-8 --indy *.groovy rm *.groovy diff --git a/src/main/groovy/Agent.groovy b/src/main/groovy/Agent.groovy index 5b77f27..529e61c 100644 --- a/src/main/groovy/Agent.groovy +++ b/src/main/groovy/Agent.groovy @@ -1,5 +1,6 @@ -//@Grab(group='com.datastax.cassandra', module='dse-driver', version='1.1.1') +//@Grab(group='com.datastax.dse', module='dse-java-driver-graph', version='1.5.1') //@Grab(group='log4j', module='log4j', version='1.2.17') +//@Grab(group='com.typesafe.akka', module='akka-actor_2.12', version='2.5.11') package net.vveitas.offernet @@ -28,7 +29,7 @@ public class Agent extends UntypedActor { private DseSession session; private Logger logger; - public static Props props(DseSession session) { + static Props props(DseSession session) { return Props.create(new Creator() { @Override public Agent create() throws Exception { @@ -37,7 +38,7 @@ public class Agent extends UntypedActor { }); } - public static Props props(Object vertexId, DseSession session) { + static Props props(Object vertexId, DseSession session) { return Props.create(new Creator() { @Override public Agent create() throws Exception { diff --git a/src/main/groovy/OfferNet.groovy b/src/main/groovy/OfferNet.groovy index 62dfd28..444297e 100644 --- a/src/main/groovy/OfferNet.groovy +++ b/src/main/groovy/OfferNet.groovy @@ -47,13 +47,15 @@ public class OfferNet implements AutoCloseable { def start = System.currentTimeMillis() cluster = DseCluster.builder().addContactPoint("192.168.1.6").build(); cluster.connect().executeGraph("system.graph('offernet').ifNotExists().create()"); - + cluster = DseCluster.builder() .addContactPoint("192.168.1.6") .withGraphOptions(new GraphOptions().setGraphName("offernet")) .build(); session = cluster.connect(); + session.executeGraph(new SimpleGraphStatement("schema.config().option('graph.schema_mode').set('Development')")) + logger.info("Created OfferNet instance with session {}", session); logger.warn("Method {} took {} seconds to complete", Utils.getCurrentMethodName(), (System.currentTimeMillis()-start)/1000) diff --git a/src/main/groovy/Simulation.groovy b/src/main/groovy/Simulation.groovy index 119782d..ac46392 100644 --- a/src/main/groovy/Simulation.groovy +++ b/src/main/groovy/Simulation.groovy @@ -5,6 +5,8 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import com.datastax.driver.dse.graph.Vertex; +import com.datastax.driver.dse.DseSession; +import com.datastax.driver.dse.graph.GraphNode; import akka.actor.UntypedActor; import akka.actor.Props; @@ -15,7 +17,7 @@ class Simulation extends UntypedActor { Logger logger; List agentList; - private static Props props(DseSession session) { + public static Props props(DseSession session) { return Props.create(new Creator() { @Override public Simulation create() throws Exception { diff --git a/src/test/groovy/Tests.groovy b/src/test/groovy/Tests.groovy index f3c75cf..ccb062a 100644 --- a/src/test/groovy/Tests.groovy +++ b/src/test/groovy/Tests.groovy @@ -18,6 +18,7 @@ import akka.actor.Props import akka.actor.ActorSystem; import akka.actor.ActorRef; + import akka.testkit.TestActorRef import akka.testkit.JavaTestKit; @@ -151,7 +152,6 @@ public class Tests { } - @Test void createAgentNewVertexTest() { def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); From c8339af18482b0e35afd97632633b53c8020c37b Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 14:08:00 +0200 Subject: [PATCH 04/17] updated gradle/w to 4.6, generates jacoco coverage report on each test run --- build.gradle | 12 +++++++++++- gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 3 +-- gradlew | 6 +++--- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 82c3899..94be599 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,19 @@ group="offernet" version="0.0.3" task wrapper(type: Wrapper) { - gradleVersion = '3.3' + gradleVersion = '4.6' } +jacocoTestReport { + reports { + xml.enabled false + csv.enabled false + html.enabled true + } +} + +test { finalizedBy jacocoTestReport } + repositories { mavenCentral() maven { url 'https://jitpack.io' } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9a70adb9b691ce2689f951b3d8f9d5afad423484..f6b961fd5a86aa5fbfe90f707c3138408be7c718 100644 GIT binary patch delta 32243 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 H{_y^PHQUY6 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 932b040..bf3de21 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Thu Jan 12 11:26:58 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip diff --git a/gradlew b/gradlew index 4453cce..cccdd3d 100755 --- a/gradlew +++ b/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } From 0c61eccd302bbfebd03a3869635790e51b076fad Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 14:10:15 +0200 Subject: [PATCH 05/17] created separate test class for each main class -- for better testing granularity --- src/test/groovy/AgentTests.groovy | 345 +++++++++++++++++ src/test/groovy/MethodTests.groovy | 42 +++ src/test/groovy/OfferNetTests.groovy | 159 ++++++++ src/test/groovy/ParametersTests.groovy | 42 +++ src/test/groovy/Tests.groovy | 497 ------------------------- src/test/groovy/UtilsTests.groovy | 68 ++++ 6 files changed, 656 insertions(+), 497 deletions(-) create mode 100644 src/test/groovy/AgentTests.groovy create mode 100644 src/test/groovy/MethodTests.groovy create mode 100644 src/test/groovy/OfferNetTests.groovy create mode 100644 src/test/groovy/ParametersTests.groovy delete mode 100644 src/test/groovy/Tests.groovy create mode 100644 src/test/groovy/UtilsTests.groovy diff --git a/src/test/groovy/AgentTests.groovy b/src/test/groovy/AgentTests.groovy new file mode 100644 index 0000000..7aadd01 --- /dev/null +++ b/src/test/groovy/AgentTests.groovy @@ -0,0 +1,345 @@ +package net.vveitas.offernet + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import static org.junit.Assert.* +import static org.hamcrest.CoreMatchers.instanceOf; + +import org.junit.Test; +import org.junit.BeforeClass; +import org.junit.AfterClass; + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import akka.actor.Props +import akka.actor.ActorSystem; +import akka.actor.ActorRef; + +import akka.testkit.TestActorRef +import akka.testkit.JavaTestKit; + +import java.util.UUID; + +public class AgentTests { + static private OfferNet on = new OfferNet().flushVertices(); + static private Logger logger; + static ActorSystem system = ActorSystem.create(); + + @BeforeClass + static void initLogging() { + def config = new ConfigSlurper().parse(new File('configs/log4j-properties.groovy').toURL()) + PropertyConfigurator.configure(config.toProperties()) + logger = LoggerFactory.getLogger('Tests.class'); + } + + @Test + void idStaticTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotNull(agent1.id()) + logger.info("id of the actor via static interface is {}",agentId) + } + + @Test + void idMessageTest() { + new JavaTestKit(system) {{ + def agentRef = system.actorOf(Agent.props(on.session),"agent1"); + agentRef.tell(new Method("id",[]),getRef()) + def agentId = receiveN(1) + assertNotNull(agentId) + logger.info("id of the actor via message is {}",agentId) + }} + } + + @Test + void connectTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work1 = agent1.ownsWork(); + def work2 = agent2.ownsWork(); + def item1 = agent1.addItemToWork("demands",work1) + def item2 = agent2.addItemToWork("demands",work1) + + def similarity = Utils.calculateSimilarity(item1,item2); + def similarityEdge = agent1.connect(item1,item2, similarity); + assertNotNull(similarityEdge); + } + + @Test + void connectAllSimilarTest() { + on.flushVertices(); + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work1 = agent1.ownsWork('1111','1110'); + def work2 = agent2.ownsWork('1100','1000'); + def start = agent1.addItemToWork("demands",work2,'0000') + + def knownItemsList = on.getVertices('item') + List similarityEdges = agent1.connectAllSimilar(start, knownItemsList,2) + assertEquals(3,similarityEdges.size()) + } + + @Test + void reciprocalDistanceLinkTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work1 = agent1.ownsWork(); + def work2 = agent2.ownsWork(); + def item1 = agent1.addItemToWork("demands",work1) + def item2 = agent2.addItemToWork("demands",work1) + + def similarity = Utils.calculateSimilarity(item1,item2); + def similarityEdge = agent1.connect(item1, item2, similarity); + assertNotNull(similarityEdge); + + def d1 =agent1.existsSimilarity(item1,item2); + assertNotNull(d1) + def d2 = agent2.existsSimilarity(item2,item1); + assertNotNull(d2) + assertEquals(d1,d2) + } + + @Test + void existsSimilarityTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work1 = agent1.ownsWork(); + def work2 = agent2.ownsWork(); + def item1 = agent1.addItemToWork("demands",work1) + def item2 = agent2.addItemToWork("demands",work1) + + def similarity = Utils.calculateSimilarity(item1,item2); + def similarityEdge = agent1.connect(item1, item2, similarity); + assertNotNull(similarityEdge); + + def d2 = agent2.existsSimilarity(item1,item2); + assertNotNull(d2) + + } + + @Test + void connectIfSimilarTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work1 = agent1.ownsWork(); + def work2 = agent2.ownsWork(); + def item1 = agent1.addItemToWork("demands",work1) + def item2 = agent2.addItemToWork("demands",work1) + + def similarity = Utils.calculateSimilarity(item1,item2); + def connectedEdge = agent1.connectIfSimilar(item1, item2, similarity-1); + assertNotNull(connectedEdge); + assertEquals(similarity,Utils.edgePropertyValueAsInteger(connectedEdge,'value')) + + def item3 = agent1.addItemToWork("offers",work1) + def item4 = agent2.addItemToWork("offers",work1) + + similarity = Utils.calculateSimilarity(item3,item4); + + connectedEdge = agent2.connectIfSimilar(item3, item4, similarity+1); + assertNull(connectedEdge); + } + + @Test + void itemsOfKnownAgentsTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent3 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent4 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + + agent1.knowsAgent(agent2.vertex.getId()); + agent2.knowsAgent(agent3.vertex.getId()); + agent3.knowsAgent(agent4.vertex.getId()); + + def work1 = agent1.ownsWork(); + agent2.ownsWork() + agent3.ownsWork() + agent4.ownsWork() + + List items = agent1.itemsOfKnownAgents(2) + assertNotNull(items) + assertEquals(4,items.size()) + items.each{ item -> + assertEquals('item',item.getLabel()) + } + + } + + @Test + void createAgentNewVertexTest() { + String agentId = UUID.randomUUID().toString(); + def agent1 = TestActorRef.create(system, Agent.props(on.session,agentId)).underlyingActor(); + assertNotNull(agent1); + def agentIdFromVertex = agent1.id() + logger.info("Original agent id {} is of type {}",agentId, agentId.getClass().getSimpleName()) + logger.info("Agent id extracted from vertex {} is of type {}",agentIdFromVertex, agentIdFromVertex.getClass().getSimpleName()) + assertEquals(agentId,agentIdFromVertex) + } + + @Test + void createAgentExistingVertexTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotNull(agent1); + def id1 = agent1.id(); + + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotNull(agent2); + assertEquals(id1,agent2.id()); + } + + + @Test + void agentKnowsAgentTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotNull(agent1); + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotNull(agent2); + def edge = agent1.knowsAgent(agent2.vertex,getId()); + assertNotNull(edge); + } + + @Test + void agentKnowsAgentViaMessageTest() { + new JavaTestKit(system) {{ + def agent1Ref = system.actorOf(Agent.props(on.session),"agent1"); + assertNotNull(agent1Ref); + def agent2Ref = system.actorOf(Agent.props(on.session),"agent2"); + assertNotNull(agent2Ref); + agent2Ref.tell(new Method("id",[]),getRef()) + def agent2id = receiveN(1) + agent1Ref.tell(new Method("knowsAgent",[agent2id]),getRef()) + def edge = receiveN(1) + assertNotNull(edge); + }} + } + + @Test + void agentOwnsNewWorkTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotNull(agent1); + + def work = agent1.ownsWork(); + assertNotNull(work); + } + + @Test + void agentOwnsKnownWorkTest() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotNull(agent1); + + def work = agent1.ownsWork("00011","00001") + assertNotNull(work); + + def demand = agent1.getWorksItems(work,"demands")[0]; + assertNotNull(demand) + def offer = agent1.getWorksItems(work,"offers")[0]; + assertNotNull(offer) + + assertEquals("00011",demand.getProperty("value").getValue().asString()) + assertEquals("00001",offer.getProperty("value").getValue().asString()) + } + + @Test + void allItemsTest() { + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotNull(agent); + + def work = agent.ownsWork(); + assertNotNull(work); + + agent.addItemToWork("demands",work); + agent.addItemToWork("offers",work); + + assertEquals(4,agent.allItems().size()) + } + + @Test + void searchAndConnectTest() { + on.flushVertices("agent"); + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + agent1.ownsWork('111110','000000'); + def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + agent2.ownsWork('111100','110000'); + def agent3 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + agent3.ownsWork('100000','111100'); + def agent4 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + agent4.ownsWork('111110','000000'); + + agent1.knowsAgent(agent2.vertex.getId()); + agent2.knowsAgent(agent3.vertex.getId()); + agent3.knowsAgent(agent4.vertex.getId()); + + /* + The resulting graph has 4 agents, 4 works, 8 items and 6 reciprocal connections (12 links in total) + */ + assertEquals(3,agent1.searchAndConnect(5,2)) // this traverses part of the graph + assertEquals(3,agent1.searchAndConnect(4,3)) // traverses the whole graph, and finds the rest of connections with similarity gte(4) + + } + + @Test + void getWorksTest() { + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work1 = agent.ownsWork(); + def work2 = agent.ownsWork(); + def work3 = agent.ownsWork(); + + List worksVertexList = agent.getWorks(); + assertEquals(3,worksVertexList.size()); + assertTrue(worksVertexList.contains(work1)); + assertTrue(worksVertexList.contains(work2)); + assertTrue(worksVertexList.contains(work3)); + + } + + @Test + void addNewOfferTest() { + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work = agent.ownsWork() + def offer = agent.addItemToWork("offers",work) + assertNotNull(offer); + } + + @Test + void addKnownOfferTest() { + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work = agent.ownsWork() + def offer = agent.addItemToWork("offers",work,"00000") + assertEquals("00000",offer.getProperty("value").getValue().asString()); + } + + @Test + void addNewDemandTest() { + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work = agent.ownsWork() + def offer = agent.addItemToWork("demands",work) + assertNotNull(offer); + } + + @Test + void addKnownDemandTest() { + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work = agent.ownsWork() + def offer = agent.addItemToWork("demands",work,"00011") + assertEquals("00011",offer.getProperty("value").getValue().asString()); + } + + @Test + void getWorkItemsTest() { + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work = agent.ownsWork(); + def item1 = agent.addItemToWork("demands",work); + def item2 = agent.addItemToWork("demands",work); + + def demands = agent.getWorksItems(work,"demands"); + assertEquals(3,demands.size()); // tree because one is created by default in Work constructor + + def offers = agent.getWorksItems(work,"offers"); + assertEquals(1,offers.size()); + + } + +} diff --git a/src/test/groovy/MethodTests.groovy b/src/test/groovy/MethodTests.groovy new file mode 100644 index 0000000..c8bd125 --- /dev/null +++ b/src/test/groovy/MethodTests.groovy @@ -0,0 +1,42 @@ +package net.vveitas.offernet + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import static org.junit.Assert.* +import static org.hamcrest.CoreMatchers.instanceOf; + +import org.junit.Test; +import org.junit.BeforeClass; +import org.junit.AfterClass; + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import akka.actor.Props +import akka.actor.ActorSystem; +import akka.actor.ActorRef; + +import akka.testkit.TestActorRef +import akka.testkit.JavaTestKit; + +public class MethodTests { + static private OfferNet on = new OfferNet().flushVertices(); + static private Logger logger; + static ActorSystem system = ActorSystem.create(); + + @BeforeClass + static void initLogging() { + def config = new ConfigSlurper().parse(new File('configs/log4j-properties.groovy').toURL()) + PropertyConfigurator.configure(config.toProperties()) + logger = LoggerFactory.getLogger('Tests.class'); + } + + @Test + void sendMethod() { + def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + } + +} \ No newline at end of file diff --git a/src/test/groovy/OfferNetTests.groovy b/src/test/groovy/OfferNetTests.groovy new file mode 100644 index 0000000..797f97c --- /dev/null +++ b/src/test/groovy/OfferNetTests.groovy @@ -0,0 +1,159 @@ +package net.vveitas.offernet + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import static org.junit.Assert.* +import static org.hamcrest.CoreMatchers.instanceOf; + +import org.junit.Test; +import org.junit.BeforeClass; +import org.junit.AfterClass; + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import akka.actor.Props +import akka.actor.ActorSystem; +import akka.actor.ActorRef; + +import akka.testkit.TestActorRef +import akka.testkit.JavaTestKit; + +public class OfferNetTests { + static private OfferNet on = new OfferNet().flushVertices(); + static private Logger logger; + static ActorSystem system = ActorSystem.create(); + + @BeforeClass + static void initLogging() { + def config = new ConfigSlurper().parse(new File('configs/log4j-properties.groovy').toURL()) + PropertyConfigurator.configure(config.toProperties()) + logger = LoggerFactory.getLogger('Tests.class'); + } + + @Test + void createOfferNetworkTest() { + def on1 = new OfferNet() + assertNotNull(on1); + assertFalse(on1.session.isClosed()); + assertFalse(on1.cluster.isClosed()); + } + + @Test + void closeOfferNetworkTest() { + def on1 = new OfferNet() + assertNotNull(on1); + on1.close(); + assertTrue(on1.session.isClosed()); + assertTrue(on1.cluster.isClosed()); + } + + @Test + void flushAgentsTest() { + def on1 = new OfferNet() + assertNotNull(on1); + TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + assertNotEquals(0,on1.getIds('agent').size()) + on1.flushVertices("agent"); + assertEquals(0,on1.getIds('agent').size()) + } + + @Test + void flushVerticesTest() { + def on1 = new OfferNet(); + assertNotNull(on1); + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work = agent.ownsWork(); + def item1 = agent.addItemToWork("offers",work); + def item2 = agent.addItemToWork("demands",work); + assertNotEquals(0,on1.getIds('agent').size()) + assertNotEquals(0,on1.getIds('work').size()) + assertNotEquals(0,on1.getIds('item').size()) + on1.flushVertices(); + assertEquals(0,on1.getIds('agent').size()) + assertEquals(0,on1.getIds('work').size()) + assertEquals(0,on1.getIds('item').size()) + } + + @Test + void getIdsTest() { + def sim = TestActorRef.create(system, Simulation.props()).underlyingActor(); + sim.on.flushVertices('agent'); + sim.createAgentNetwork(10) + def agentIds = sim.on.getIds('agent'); + assertNotNull(agentIds); + assertEquals(10,agentIds.size()); + } + + @Test + void addRandomWorksToAgentsTest() { + def on1 = new OfferNet() + on1.flushVertices(); + + on1.createAgentNetwork(10) + on1.addRandomWorksToAgents(10) + assertEquals(20,on.getIds("item").size()); // creates two items (demand and offer) when creating a random work; + } + + @Test + void allWorkItemEdgesTest() { + def sim = TestActorRef.create(system, Simulation.props()).underlyingActor(); + + def chains = [Utils.createChain(4)] + logger.info("Created chains: {}",chains) + + sim.createAgentNetwork(4,0,chains); + + def demandEdges = on.allWorkItemEdges("demands"); + def offerEdges = on.allWorkItemEdges("offers") + + logger.info("demandEdges {} of class {}",demandEdges,demandEdges.getClass()) + + assertEquals(7,demandEdges.size()) + assertEquals(7,offerEdges.size()) + } + + @Test + void connectMatchingPairsTest() { + def sim = TestActorRef.create(system, Simulation.props()).underlyingActor(); + def chainLength = 4 + + def chains = [Utils.createChain(chainLength)] + logger.info("Created chains: {}",chains) + + sim.createAgentNetwork(chainLength,0,chains); + + def demandEdges = on.allWorkItemEdges("demands"); + def offerEdges = on.allWorkItemEdges("offers") + + logger.info("demandEdges {} of class {}",demandEdges,demandEdges.getClass()) + + def matchingOfferDemandPairs = Utils.getMatchingOfferDemandPairs(offerEdges,demandEdges) + logger.warn("Offer-Demand pairs found: {}",matchingOfferDemandPairs) + + def connectedPairsCount = sim.on.connectMatchingPairs(matchingOfferDemandPairs); + assertEquals((chainLength - 2).toInteger(),connectedPairsCount) + } + + @Test + void createAgentTest() { + def sim = TestActorRef.create(system, Simulation.props()).underlyingActor(); + def agent = sim.on.createAgent(); + assertNotNull(agent); + } + + @Test + void createEdgeTest() { + def sim = TestActorRef.create(system, Simulation.props()).underlyingActor(); + def agent1 = sim.on.createAgent(); + assertNotNull(agent1) + def agent2 = sim.on.createAgent(); + assertNotNull(agent2) + def edge = sim.on.knowsAgent(agent1,agent2); + assertNotNull(edge) + } +} \ No newline at end of file diff --git a/src/test/groovy/ParametersTests.groovy b/src/test/groovy/ParametersTests.groovy new file mode 100644 index 0000000..394e2e5 --- /dev/null +++ b/src/test/groovy/ParametersTests.groovy @@ -0,0 +1,42 @@ +package net.vveitas.offernet + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import static org.junit.Assert.* +import static org.hamcrest.CoreMatchers.instanceOf; + +import org.junit.Test; +import org.junit.BeforeClass; +import org.junit.AfterClass; + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import akka.actor.Props +import akka.actor.ActorSystem; +import akka.actor.ActorRef; + +import akka.testkit.TestActorRef +import akka.testkit.JavaTestKit; + +public class ParametersTests { + static private OfferNet on = new OfferNet().flushVertices(); + static private Logger logger; + static ActorSystem system = ActorSystem.create(); + + @BeforeClass + static void initLogging() { + def config = new ConfigSlurper().parse(new File('configs/log4j-properties.groovy').toURL()) + PropertyConfigurator.configure(config.toProperties()) + logger = LoggerFactory.getLogger('Tests.class'); + } + + @Test + void parametersTest() { + assertEquals(16,Parameters.parameters.binaryStringLength); + assertEquals(8,Parameters.parameters.similarityThreshold); + } +} diff --git a/src/test/groovy/Tests.groovy b/src/test/groovy/Tests.groovy deleted file mode 100644 index ccb062a..0000000 --- a/src/test/groovy/Tests.groovy +++ /dev/null @@ -1,497 +0,0 @@ -package net.vveitas.offernet - -import org.apache.log4j.PropertyConfigurator -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import static org.junit.Assert.* - -import org.junit.Test; -import org.junit.BeforeClass; -import org.junit.AfterClass; - -import org.apache.log4j.PropertyConfigurator -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import akka.actor.Props -import akka.actor.ActorSystem; -import akka.actor.ActorRef; - - -import akka.testkit.TestActorRef -import akka.testkit.JavaTestKit; - -public class Tests { - static private OfferNet on = new OfferNet().flushVertices(); - static private Logger logger; - static ActorSystem system = ActorSystem.create(); - - @BeforeClass - static void initLogging() { - def config = new ConfigSlurper().parse(new File('configs/log4j-properties.groovy').toURL()) - PropertyConfigurator.configure(config.toProperties()) - logger = LoggerFactory.getLogger('Tests.class'); - } - - /* - * Agent.groovy - */ - - @Test - void connectTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work1 = agent1.ownsWork(); - def work2 = agent2.ownsWork(); - def item1 = agent1.addItemToWork("demands",work1) - def item2 = agent2.addItemToWork("demands",work1) - - def similarity = Utils.calculateSimilarity(item1,item2); - def similarityEdge = agent1.connect(item1,item2, similarity); - assertNotNull(similarityEdge); - } - - @Test - void connectAllSimilarTest() { - on.flushVertices(); - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work1 = agent1.ownsWork('1111','1110'); - def work2 = agent2.ownsWork('1100','1000'); - def start = agent1.addItemToWork("demands",work2,'0000') - - def knownItemsList = on.getVertices('item') - List similarityEdges = agent1.connectAllSimilar(start, knownItemsList,2) - assertEquals(3,similarityEdges.size()) - } - - @Test - void reciprocalDistanceLinkTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work1 = agent1.ownsWork(); - def work2 = agent2.ownsWork(); - def item1 = agent1.addItemToWork("demands",work1) - def item2 = agent2.addItemToWork("demands",work1) - - def similarity = Utils.calculateSimilarity(item1,item2); - def similarityEdge = agent1.connect(item1, item2, similarity); - assertNotNull(similarityEdge); - - def d1 =agent1.existsSimilarity(item1,item2); - assertNotNull(d1) - def d2 = agent2.existsSimilarity(item2,item1); - assertNotNull(d2) - assertEquals(d1,d2) - } - - @Test - void existsSimilarityTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work1 = agent1.ownsWork(); - def work2 = agent2.ownsWork(); - def item1 = agent1.addItemToWork("demands",work1) - def item2 = agent2.addItemToWork("demands",work1) - - def similarity = Utils.calculateSimilarity(item1,item2); - def similarityEdge = agent1.connect(item1, item2, similarity); - assertNotNull(similarityEdge); - - def d2 = agent2.existsSimilarity(item1,item2); - assertNotNull(d2) - - } - - @Test - void connectIfSimilarTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work1 = agent1.ownsWork(); - def work2 = agent2.ownsWork(); - def item1 = agent1.addItemToWork("demands",work1) - def item2 = agent2.addItemToWork("demands",work1) - - def similarity = Utils.calculateSimilarity(item1,item2); - def connectedEdge = agent1.connectIfSimilar(item1, item2, similarity-1); - assertNotNull(connectedEdge); - assertEquals(similarity,Utils.edgePropertyValueAsInteger(connectedEdge,'value')) - - def item3 = agent1.addItemToWork("offers",work1) - def item4 = agent2.addItemToWork("offers",work1) - - similarity = Utils.calculateSimilarity(item3,item4); - - connectedEdge = agent2.connectIfSimilar(item3, item4, similarity+1); - assertNull(connectedEdge); - } - - @Test - void itemsOfKnownAgentsTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent3 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent4 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - - agent1.knowsAgent(agent2.vertex.getId()); - agent2.knowsAgent(agent3.vertex.getId()); - agent3.knowsAgent(agent4.vertex.getId()); - - def work1 = agent1.ownsWork(); - agent2.ownsWork() - agent3.ownsWork() - agent4.ownsWork() - - List items = agent1.itemsOfKnownAgents(2) - assertNotNull(items) - assertEquals(4,items.size()) - items.each{ item -> - assertEquals('item',item.getLabel()) - } - - } - - @Test - void createAgentNewVertexTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent1); - } - - @Test - void createAgentExistingVertexTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent1); - def id1 = agent1.id(); - - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent2); - assertEquals(id1,agent2.id()); - } - - - @Test - void agentKnowsAgentTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent1); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent2); - def edge = agent1.knowsAgent(agent2.vertex,getId()); - assertNotNull(edge); - } - - @Test - void agentKnowsAgentViaMessageTest() { - new JavaTestKit(system) {{ - def agent1Ref = system.actorOf(Agent.props(on.session),"agent1"); - assertNotNull(agent1Ref); - def agent2Ref = system.actorOf(Agent.props(on.session),"agent2"); - assertNotNull(agent2Ref); - agent2Ref.tell(new Method("id",[]),getRef()) - def agent2id = receiveN(1) - agent1Ref.tell(new Method("knowsAgent",[agent2id]),getRef()) - def edge = receiveN(1) - assertNotNull(edge); - }} - } - - @Test - void agentOwnsNewWorkTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent1); - - def work = agent1.ownsWork(); - assertNotNull(work); - } - - @Test - void agentOwnsKnownWorkTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent1); - - def work = agent1.ownsWork("00011","00001") - assertNotNull(work); - - def demand = agent1.getWorksItems(work,"demands")[0]; - assertNotNull(demand) - def offer = agent1.getWorksItems(work,"offers")[0]; - assertNotNull(offer) - - assertEquals("00011",demand.getProperty("value").getValue().asString()) - assertEquals("00001",offer.getProperty("value").getValue().asString()) - } - - @Test - void allItemsTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent); - - def work = agent.ownsWork(); - assertNotNull(work); - - agent.addItemToWork("demands",work); - agent.addItemToWork("offers",work); - - assertEquals(4,agent.allItems().size()) - } - - @Test - void searchAndConnectTest() { - on.flushVertices("agent"); - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - agent1.ownsWork('111110','000000'); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - agent2.ownsWork('111100','110000'); - def agent3 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - agent3.ownsWork('100000','111100'); - def agent4 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - agent4.ownsWork('111110','000000'); - - agent1.knowsAgent(agent2.vertex.getId()); - agent2.knowsAgent(agent3.vertex.getId()); - agent3.knowsAgent(agent4.vertex.getId()); - - /* - The resulting graph has 4 agents, 4 works, 8 items and 6 reciprocal connections (12 links in total) - */ - assertEquals(3,agent1.searchAndConnect(5,2)) // this traverses part of the graph - assertEquals(3,agent1.searchAndConnect(4,3)) // traverses the whole graph, and finds the rest of connections with similarity gte(4) - - } - - @Test - void getWorksTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work1 = agent.ownsWork(); - def work2 = agent.ownsWork(); - def work3 = agent.ownsWork(); - - List worksVertexList = agent.getWorks(); - assertEquals(3,worksVertexList.size()); - assertTrue(worksVertexList.contains(work1)); - assertTrue(worksVertexList.contains(work2)); - assertTrue(worksVertexList.contains(work3)); - - } - - @Test - void addNewOfferTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work = agent.ownsWork() - def offer = agent.addItemToWork("offers",work) - assertNotNull(offer); - } - - @Test - void addKnownOfferTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work = agent.ownsWork() - def offer = agent.addItemToWork("offers",work,"00000") - assertEquals("00000",offer.getProperty("value").getValue().asString()); - } - - @Test - void addNewDemandTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work = agent.ownsWork() - def offer = agent.addItemToWork("demands",work) - assertNotNull(offer); - } - - @Test - void addKnownDemandTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work = agent.ownsWork() - def offer = agent.addItemToWork("demands",work,"00011") - assertEquals("00011",offer.getProperty("value").getValue().asString()); - } - - @Test - void getWorkItemsTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work = agent.ownsWork(); - def item1 = agent.addItemToWork("demands",work); - def item2 = agent.addItemToWork("demands",work); - - def demands = agent.getWorksItems(work,"demands"); - assertEquals(3,demands.size()); // tree because one is created by default in Work constructor - - def offers = agent.getWorksItems(work,"offers"); - assertEquals(1,offers.size()); - - } - - /* - * OfferNet.class - */ - - @Test - void createOfferNetworkTest() { - def on1 = new OfferNet() - assertNotNull(on1); - assertFalse(on1.session.isClosed()); - assertFalse(on1.cluster.isClosed()); - } - - @Test - void closeOfferNetworkTest() { - def on1 = new OfferNet() - assertNotNull(on1); - on1.close(); - assertTrue(on1.session.isClosed()); - assertTrue(on1.cluster.isClosed()); - } - - @Test - void flushAgentsTest() { - def on1 = new OfferNet() - assertNotNull(on1); - TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotEquals(0,on1.getIds('agent').size()) - on1.flushVertices("agent"); - assertEquals(0,on1.getIds('agent').size()) - } - - @Test - void flushVerticesTest() { - def on1 = new OfferNet(); - assertNotNull(on1); - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work = agent.ownsWork(); - def item1 = agent.addItemToWork("offers",work); - def item2 = agent.addItemToWork("demands",work); - assertNotEquals(0,on1.getIds('agent').size()) - assertNotEquals(0,on1.getIds('work').size()) - assertNotEquals(0,on1.getIds('item').size()) - on1.flushVertices(); - assertEquals(0,on1.getIds('agent').size()) - assertEquals(0,on1.getIds('work').size()) - assertEquals(0,on1.getIds('item').size()) - } - - @Test - void getIdsTest() { - def on1 = new OfferNet() - on1.flushVertices('agent'); - on1.createAgentNetwork(10) - def agentIds = on1.getIds('agent'); - assertNotNull(agentIds); - assertEquals(10,agentIds.size()); - } - - - @Test - void addRandomWorksToAgentsTest() { - def on1 = new OfferNet() - on1.flushVertices(); - - on1.createAgentNetwork(10) - on1.addRandomWorksToAgents(10) - assertEquals(20,on.getIds("item").size()); // creates two items (demand and offer) when creating a random work; - } - - @Test - void allWorkItemEdgesTest() { - def sim = new Simulation(); - - def chains = [Utils.createChain(4)] - logger.info("Created chains: {}",chains) - - sim.createAgentNetwork(4,0,chains); - - def demandEdges = on.allWorkItemEdges("demands"); - def offerEdges = on.allWorkItemEdges("offers") - - logger.info("demandEdges {} of class {}",demandEdges,demandEdges.getClass()) - - assertEquals(7,demandEdges.size()) - assertEquals(7,offerEdges.size()) - } - - @Test - void connectMatchingPairsTest() { - def sim = new Simulation(); - def chainLength = 4 - - def chains = [Utils.createChain(chainLength)] - logger.info("Created chains: {}",chains) - - sim.createAgentNetwork(chainLength,0,chains); - - def demandEdges = on.allWorkItemEdges("demands"); - def offerEdges = on.allWorkItemEdges("offers") - - logger.info("demandEdges {} of class {}",demandEdges,demandEdges.getClass()) - - def matchingOfferDemandPairs = Utils.getMatchingOfferDemandPairs(offerEdges,demandEdges) - logger.warn("Offer-Demand pairs found: {}",matchingOfferDemandPairs) - - def connectedPairsCount = sim.on.connectMatchingPairs(matchingOfferDemandPairs); - assertEquals((chainLength - 2).toInteger(),connectedPairsCount) - } - - @Test - void createAgentTest() { - def sim = new Simulation(); - def agent = sim.on.createAgent(); - assertNotNull(agent); - } - - @Test - void createEdgeTest() { - def sim = new Simulation(); - def agent1 = sim.on.createAgent(); - assertNotNull(agent1) - def agent2 = sim.on.createAgent(); - assertNotNull(agent2) - def edge = sim.on.knowsAgent(agent1,agent2); - assertNotNull(edge) - } - - /* - * Utils.class - */ - - @Test - void generateBinaryStringTest() { - String string = Utils.generateBinaryString(Parameters.parameters.binaryStringLength); - assertEquals(16,string.length()); - assertTrue(string.toSet().sort().join() == '01' | string.toSet().sort().join() == '10'); - } - - @Test - void createChainTest() { - List chain = Utils.createChain(5) - assertEquals(5,chain.size()); - } - - @Test - void calculateSimilarityTest() { - String value1 = "000000" - String value2 = "000111" - def d1 = Utils.veitasSimilarity(value1,value2); - assertNotNull(d1) - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def work = agent.ownsWork(value1,value2); - def item1 = agent.getWorksItems(work,"demands")[0] - def item2 = agent.getWorksItems(work,"offers")[0] - assertNotNull(item1) - assertNotNull(item2) - def d2 = Utils.calculateSimilarity(item1,item2); - assertNotNull(d2) - assertEquals(d1,d2); - assertEquals(3,d1); - } - - /* - * Parameters.class - */ - - @Test - void parametersTest() { - assertEquals(16,Parameters.parameters.binaryStringLength); - assertEquals(8,Parameters.parameters.similarityThreshold); - } - -} diff --git a/src/test/groovy/UtilsTests.groovy b/src/test/groovy/UtilsTests.groovy new file mode 100644 index 0000000..47fcd1f --- /dev/null +++ b/src/test/groovy/UtilsTests.groovy @@ -0,0 +1,68 @@ +package net.vveitas.offernet + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import static org.junit.Assert.* +import static org.hamcrest.CoreMatchers.instanceOf; + +import org.junit.Test; +import org.junit.BeforeClass; +import org.junit.AfterClass; + +import org.apache.log4j.PropertyConfigurator +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import akka.actor.Props +import akka.actor.ActorSystem; +import akka.actor.ActorRef; + +import akka.testkit.TestActorRef +import akka.testkit.JavaTestKit; + +public class UtilsTests { + static private OfferNet on = new OfferNet().flushVertices(); + static private Logger logger; + static ActorSystem system = ActorSystem.create(); + + @BeforeClass + static void initLogging() { + def config = new ConfigSlurper().parse(new File('configs/log4j-properties.groovy').toURL()) + PropertyConfigurator.configure(config.toProperties()) + logger = LoggerFactory.getLogger('Tests.class'); + } + + @Test + void generateBinaryStringTest() { + String string = Utils.generateBinaryString(Parameters.parameters.binaryStringLength); + assertEquals(16,string.length()); + assertTrue(string.toSet().sort().join() == '01' | string.toSet().sort().join() == '10'); + } + + @Test + void createChainTest() { + List chain = Utils.createChain(5) + assertEquals(5,chain.size()); + } + + @Test + void calculateSimilarityTest() { + String value1 = "000000" + String value2 = "000111" + def d1 = Utils.veitasSimilarity(value1,value2); + assertNotNull(d1) + def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def work = agent.ownsWork(value1,value2); + def item1 = agent.getWorksItems(work,"demands")[0] + def item2 = agent.getWorksItems(work,"offers")[0] + assertNotNull(item1) + assertNotNull(item2) + def d2 = Utils.calculateSimilarity(item1,item2); + assertNotNull(d2) + assertEquals(d1,d2); + assertEquals(3,d1); + } + +} \ No newline at end of file From b62c9cc91ffb9e9c8d3a393b04cf4f4803609bf2 Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 14:14:59 +0200 Subject: [PATCH 06/17] graph and actor system synchronization: actor name / path gets registered as a vertex property during construction --- src/main/groovy/Agent.groovy | 59 ++++++++++++++---------------------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/src/main/groovy/Agent.groovy b/src/main/groovy/Agent.groovy index 529e61c..a53c198 100644 --- a/src/main/groovy/Agent.groovy +++ b/src/main/groovy/Agent.groovy @@ -20,29 +20,22 @@ import org.apache.log4j.PropertyConfigurator import org.slf4j.Logger import org.slf4j.LoggerFactory -import akka.actor.UntypedActor; +import akka.actor.UntypedAbstractActor; import akka.actor.Props; import akka.japi.Creator; -public class Agent extends UntypedActor { +import java.util.UUID; + +public class Agent extends UntypedAbstractActor { private Vertex vertex; private DseSession session; private Logger logger; - static Props props(DseSession session) { - return Props.create(new Creator() { - @Override - public Agent create() throws Exception { - return new Agent(session); - } - }); - } - - static Props props(Object vertexId, DseSession session) { + static Props props(DseSession session, String agentId) { return Props.create(new Creator() { @Override public Agent create() throws Exception { - return new Agent(vertexId, session); + return new Agent(session,agentId); } }); } @@ -71,7 +64,17 @@ public class Agent extends UntypedActor { } } - public Agent(DseSession session) { + /** + * Agent constructor returning a new agent by creating a vertex in the graph + * if a vertex with the given UUID exists - connect this vertex to the newly created actor + * UUID is shared between graph identifier (agentId) and actor identifier (path) + * @param session the DSE graph session for communication with the graph + * @return Agent class instance; + * @author kabir@singularitynet.io + */ + + public Agent(DseSession session, String agentId) { + def start = System.currentTimeMillis(); def config = new ConfigSlurper().parse(new File('configs/log4j-properties.groovy').toURL()) PropertyConfigurator.configure(config.toProperties()) @@ -81,37 +84,21 @@ public class Agent extends UntypedActor { Map params = new HashMap(); params.put("labelValue", "agent"); + params.put("agentId",agentId); + params.put("agentIdLabel","agentId") - GraphResultSet rs = session.executeGraph(new SimpleGraphStatement("g.addV(label, labelValue)", params)); + GraphResultSet rs = session.executeGraph(new SimpleGraphStatement("g.V().choose(has(agentIdLabel,agentId).is(null),has(agentIdLabel,agentId),g.addV(label, labelValue).property(agentIdLabel,agentId))", params)); this.vertex = rs.one().asVertex(); - logger.warn("Created a new {} with id {}", vertex.getLabel(), vertex.getId()); + logger.warn("Created a new {} with id {} and agentId {}", vertex.getLabel(), vertex.getId(), vertex.getProperty("agentId").getValue()); logger.warn("Method {} took {} seconds to complete", Utils.getCurrentMethodName(), (System.currentTimeMillis()-start)/1000) } - public Agent(Object vertexId, DseSession session) { - def start = System.currentTimeMillis(); - def config = new ConfigSlurper().parse(new File('configs/log4j-properties.groovy').toURL()) - PropertyConfigurator.configure(config.toProperties()) - logger = LoggerFactory.getLogger('OfferNet.class'); - - this.session= session; - - Map params = new HashMap(); - params.put("vertexId",vertexId); - - GraphResultSet rs = session.executeGraph(new SimpleGraphStatement("g.V(vertexId)", params)); - this.vertex = rs.one().asVertex(); - - logger.warn("Instantiated an {} with existing vertex id {}", vertex.getLabel(), vertex.getId()); - logger.warn("Method {} took {} seconds to complete", Utils.getCurrentMethodName(), (System.currentTimeMillis()-start)/1000) - } - /* * returns an id of an Agent vertex */ - private id() { - return vertex.getId(); + private String id() { + return vertex.getProperty("agentId").getValue().asString(); } /* From 1b8703e629115cba9e8aa57a6a86089853260821 Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 14:17:31 +0200 Subject: [PATCH 07/17] network creation methods for OfferNet class --- src/main/groovy/OfferNet.groovy | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/groovy/OfferNet.groovy b/src/main/groovy/OfferNet.groovy index 444297e..654669b 100644 --- a/src/main/groovy/OfferNet.groovy +++ b/src/main/groovy/OfferNet.groovy @@ -142,6 +142,35 @@ public class OfferNet implements AutoCloseable { return agentIds; } + private void createAgentNetworkWithChains(String[] args){ + def numberOfAgents = args[1]; + def numberOfRandomWorks = args[2]; + def numberOfChains = args[3]; + def lenghtOfChain = args[4]; + List chains = []; + numberOfChains.times { + chains.add(Utils.createChain(lenghtOfChain)); + } + createAgentNetwork(numberOfAgents,numberOfRandomWorks,chains); + } + + private List createAgentNetwork(Integer numberOfAgents, Integer numberOfRandomWorks, ArrayList chains) { + + def start = System.currentTimeMillis(); + agentList = on.createAgentNetwork(numberOfAgents) + agentList.each {agent -> + agent.ownsWork() + } + on.addRandomWorksToAgents(numberOfRandomWorks) + chains.each {chain -> + on.addChainToNetwork(chain) + } + logger.warn("Created agentNetwork with {} agents, {} randomWorks and {} chains",numberOfAgents,numberOfRandomWorks,chains.size()) + logger.warn("Method {} took {} seconds to complete", Utils.getCurrentMethodName(), (System.currentTimeMillis()-start)/1000) + + return agentList; + } + public addRandomWorksToAgents(int numberOfWorks) { def start=System.currentTimeMillis(); List agentIds = this.getIds('agent'); From bddc655dcb02adefba8beb813871421c43ad7225 Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 14:29:16 +0200 Subject: [PATCH 08/17] added tests: createSimulationTest (passes); createAgentTest (passes); createAgentNetworkTest (does not pass..) --- src/main/groovy/Simulation.groovy | 91 ++++++++++---------------- src/main/groovy/Utils.groovy | 1 - src/test/groovy/SimulationTests.groovy | 32 ++++++++- 3 files changed, 66 insertions(+), 58 deletions(-) diff --git a/src/main/groovy/Simulation.groovy b/src/main/groovy/Simulation.groovy index ac46392..e8dc464 100644 --- a/src/main/groovy/Simulation.groovy +++ b/src/main/groovy/Simulation.groovy @@ -8,29 +8,23 @@ import com.datastax.driver.dse.graph.Vertex; import com.datastax.driver.dse.DseSession; import com.datastax.driver.dse.graph.GraphNode; -import akka.actor.UntypedActor; +import akka.actor.UntypedAbstractActor; import akka.actor.Props; import akka.japi.Creator; -class Simulation extends UntypedActor { +import akka.actor.ActorRef; +import java.util.UUID; + +class Simulation extends UntypedAbstractActor { OfferNet on; Logger logger; List agentList; - public static Props props(DseSession session) { - return Props.create(new Creator() { - @Override - public Simulation create() throws Exception { - return new Simulation(session); - } - }); - } - - public static Props props(Object vertexId, DseSession session) { + public static Props props() { return Props.create(new Creator() { @Override public Simulation create() throws Exception { - return new Simulation(vertexId, session); + return new Simulation(); } }); } @@ -81,52 +75,37 @@ class Simulation extends UntypedActor { - Simulations will be run by passing messages for running methods */ - private void createAgentNetworkWithChains(String[] args){ - def numberOfAgents = args[1]; - def numberOfRandomWorks = args[2]; - def numberOfChains = args[3]; - def lenghtOfChain = args[4]; - List chains = []; - numberOfChains.times { - chains.add(Utils.createChain(lenghtOfChain)); - } - createAgentNetwork(numberOfAgents,numberOfRandomWorks,chains); - } - - private List createAgentNetwork(int numberOfAgents) { - def start = System.currentTimeMillis() - List agentsList = new ArrayList() - agentsList.add(system.actorOf(Agent.props(on.session),"agent1")) - - while (agentsList.size() < numberOfAgents) { - def random = new Random(); - def i = random.nextInt(agentsList.size()) - Object agent1 = agentsList[i] - Object agent2 = system.actorOf(Agent.props(on.session),"agent1"); - agent1.tell(new Method("knowsAgent",[agent2id]),getRef()) - agentsList.add(agent2) - } - logger.info("Created a network of "+numberOfAgents+ " Agents") - logger.warn("Method {} took {} seconds to complete", Utils.getCurrentMethodName(), (System.currentTimeMillis()-start)/1000) - return agentsList; - } + private ActorRef createAgent() { + String agentId = UUID.randomUUID().toString(); + def actorRef = getContext().actorOf(Agent.props(on.session,agentId),agentId); + return actorRef + } - private List createAgentNetwork(Integer numberOfAgents, Integer numberOfRandomWorks, ArrayList chains) { + private Vertex getAgentVertexId(ActorRef actorRef) { + return actorRef.tell(id()); + } - def start = System.currentTimeMillis(); - agentList = on.createAgentNetwork(numberOfAgents) - agentList.each {agent -> - agent.ownsWork() - } - on.addRandomWorksToAgents(numberOfRandomWorks) - chains.each {chain -> - on.addChainToNetwork(chain) - } - logger.warn("Created agentNetwork with {} agents, {} randomWorks and {} chains",numberOfAgents,numberOfRandomWorks,chains.size()) - logger.warn("Method {} took {} seconds to complete", Utils.getCurrentMethodName(), (System.currentTimeMillis()-start)/1000) + private List createAgentNetwork(int numberOfAgents) { + def start = System.currentTimeMillis() + List agentsList = new ArrayList() + def firstAgent = Props.create(Agent.class, on.session) + + agentsList.add(system.actorOf(Agent.props(on.session),"agent1")) + + while (agentsList.size() < numberOfAgents) { + def random = new Random(); + def i = random.nextInt(agentsList.size()) + Object agent1 = agentsList[i] + Object agent2 = system.actorOf(Agent.props(on.session),"agent1"); + agent1.tell(new Method("knowsAgent",[agent2id]),getRef()) + agentsList.add(agent2) + } + logger.info("Created a network of "+numberOfAgents+ " Agents") + logger.warn("Method {} took {} seconds to complete", Utils.getCurrentMethodName(), (System.currentTimeMillis()-start)/1000) + return agentsList; + } - return agentList; - } + /* done until here */ private Integer connectIfSimilarForAllAgents(List agentList, Integer similarityThreshold, Integer maxReachDistance) { diff --git a/src/main/groovy/Utils.groovy b/src/main/groovy/Utils.groovy index 285182e..13bd496 100644 --- a/src/main/groovy/Utils.groovy +++ b/src/main/groovy/Utils.groovy @@ -13,7 +13,6 @@ import com.datastax.driver.dse.graph.Vertex import org.codehaus.groovy.runtime.StackTraceUtils - import static org.junit.Assert.* public class Utils { diff --git a/src/test/groovy/SimulationTests.groovy b/src/test/groovy/SimulationTests.groovy index b5c9771..43cb2b6 100644 --- a/src/test/groovy/SimulationTests.groovy +++ b/src/test/groovy/SimulationTests.groovy @@ -5,6 +5,7 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import static org.junit.Assert.* +import static org.hamcrest.CoreMatchers.instanceOf; import org.junit.Test; import org.junit.Ignore; @@ -29,8 +30,15 @@ import org.slf4j.LoggerFactory import java.text.SimpleDateFormat; -public class SimulationTests { +import akka.actor.Props +import akka.actor.ActorSystem; +import akka.actor.ActorRef; + +import akka.testkit.TestActorRef +import akka.testkit.JavaTestKit; +public class SimulationTests { + static ActorSystem system = ActorSystem.create(); static private Logger logger; @BeforeClass @@ -40,6 +48,28 @@ public class SimulationTests { logger = LoggerFactory.getLogger('OfferNet.class'); } + @Test + void createSimulationTest() { + def sim = TestActorRef.create(system, Simulation.props()).underlyingActor(); + assertThat(sim, instanceOf(Simulation.class)) + } + + @Test + void createAgentTest() { + def sim = TestActorRef.create(system, Simulation.props()).underlyingActor(); + def agent1 = sim.createAgent(); + assertThat(agent1, instanceOf(ActorRef.class)); + } + + @Test + void createAgentNetworkTest() { + def sim = TestActorRef.create(system, Simulation.props()).underlyingActor(); + int size + def agentList = sim.createAgentNetwork(size) + assertThat(agentList.size(), size) + + } + //@Ignore // for now -- takes too much time @Test void cycleSearchTest() { From df5845e7ae1dcd6f57768dc85532058174c6d3e5 Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 22:14:00 +0200 Subject: [PATCH 09/17] AgentTests.createAgentExistingVertexTest passes --- src/main/groovy/Agent.groovy | 6 +++++- src/test/groovy/AgentTests.groovy | 25 +++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/groovy/Agent.groovy b/src/main/groovy/Agent.groovy index a53c198..81dfe0c 100644 --- a/src/main/groovy/Agent.groovy +++ b/src/main/groovy/Agent.groovy @@ -87,7 +87,11 @@ public class Agent extends UntypedAbstractActor { params.put("agentId",agentId); params.put("agentIdLabel","agentId") - GraphResultSet rs = session.executeGraph(new SimpleGraphStatement("g.V().choose(has(agentIdLabel,agentId).is(null),has(agentIdLabel,agentId),g.addV(label, labelValue).property(agentIdLabel,agentId))", params)); + GraphResultSet rs = session.executeGraph(new SimpleGraphStatement( + "if (g.V().has(agentIdLabel,agentId).toList().size() == 0)\n"+ + "g.addV(label, labelValue).property(agentIdLabel,agentId)\n"+ + "else\n"+ + "g.V().has(agentIdLabel,agentId)", params)); this.vertex = rs.one().asVertex(); logger.warn("Created a new {} with id {} and agentId {}", vertex.getLabel(), vertex.getId(), vertex.getProperty("agentId").getValue()); diff --git a/src/test/groovy/AgentTests.groovy b/src/test/groovy/AgentTests.groovy index 7aadd01..9c33f13 100644 --- a/src/test/groovy/AgentTests.groovy +++ b/src/test/groovy/AgentTests.groovy @@ -165,7 +165,6 @@ public class AgentTests { items.each{ item -> assertEquals('item',item.getLabel()) } - } @Test @@ -177,17 +176,27 @@ public class AgentTests { logger.info("Original agent id {} is of type {}",agentId, agentId.getClass().getSimpleName()) logger.info("Agent id extracted from vertex {} is of type {}",agentIdFromVertex, agentIdFromVertex.getClass().getSimpleName()) assertEquals(agentId,agentIdFromVertex) - } + } @Test void createAgentExistingVertexTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - assertNotNull(agent1); - def id1 = agent1.id(); + String agentId = UUID.randomUUID().toString(); + def agent1ref = TestActorRef.create(system, Agent.props(on.session,agentId)) + def agent1 = agent1ref.underlyingActor(); + assertNotNull(agent1); + assertEquals(agentId,agent1.id()); + agent1ref.stop() - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session,agentId)).underlyingActor(); assertNotNull(agent2); - assertEquals(id1,agent2.id()); + assertEquals(agentId,agent2.id()); + + String agent3Id = UUID.randomUUID().toString(); + def agent3ref = TestActorRef.create(system, Agent.props(on.session,agent3Id)) + def agent3 = agent3ref.underlyingActor(); + assertNotNull(agent3); + assertNotEquals(agentId,agent3.id()); + } @@ -199,6 +208,7 @@ public class AgentTests { assertNotNull(agent2); def edge = agent1.knowsAgent(agent2.vertex,getId()); assertNotNull(edge); + } @Test @@ -277,7 +287,6 @@ public class AgentTests { */ assertEquals(3,agent1.searchAndConnect(5,2)) // this traverses part of the graph assertEquals(3,agent1.searchAndConnect(4,3)) // traverses the whole graph, and finds the rest of connections with similarity gte(4) - } @Test From b3d043df716ebb68aa43a475a6e6b23a9a712e1a Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 22:45:11 +0200 Subject: [PATCH 10/17] AgentTests.agentKnowsAgentTest passes --- src/test/groovy/AgentTests.groovy | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/groovy/AgentTests.groovy b/src/test/groovy/AgentTests.groovy index 9c33f13..cfffd6a 100644 --- a/src/test/groovy/AgentTests.groovy +++ b/src/test/groovy/AgentTests.groovy @@ -199,16 +199,16 @@ public class AgentTests { } - @Test void agentKnowsAgentTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); assertNotNull(agent1); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent2Id = UUID.randomUUID().toString(); + def agent2 = TestActorRef.create(system, Agent.props(on.session, agent2Id)).underlyingActor(); assertNotNull(agent2); - def edge = agent1.knowsAgent(agent2.vertex,getId()); + def edge = agent1.knowsAgent(agent2.vertex.getId()); assertNotNull(edge); - } @Test From 43f7d6b7e41903f8dc6e9f83b0f7d35104407ba8 Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 23:00:49 +0200 Subject: [PATCH 11/17] AgentTests.agentKnowsAgentViaMessageTest passes --- src/main/groovy/Agent.groovy | 12 ++++++++++-- src/test/groovy/AgentTests.groovy | 8 +++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/groovy/Agent.groovy b/src/main/groovy/Agent.groovy index 81dfe0c..83bb964 100644 --- a/src/main/groovy/Agent.groovy +++ b/src/main/groovy/Agent.groovy @@ -98,13 +98,21 @@ public class Agent extends UntypedAbstractActor { logger.warn("Method {} took {} seconds to complete", Utils.getCurrentMethodName(), (System.currentTimeMillis()-start)/1000) } - /* - * returns an id of an Agent vertex + /** + * returns the agentId property on the vertex, which is the unique id (is also the actor name in akka system) + * need to rename into something more intuitive -- agentId */ private String id() { return vertex.getProperty("agentId").getValue().asString(); } + /** + * returns the agentId property on the vertex, which is the unique id (is also the actor name in akka system) + */ + private Object vertexId() { + return vertex.getId(); + } + /* * Creates 'knows' edge between current agent and provided agent; returns that edge; */ diff --git a/src/test/groovy/AgentTests.groovy b/src/test/groovy/AgentTests.groovy index cfffd6a..7152a49 100644 --- a/src/test/groovy/AgentTests.groovy +++ b/src/test/groovy/AgentTests.groovy @@ -214,11 +214,13 @@ public class AgentTests { @Test void agentKnowsAgentViaMessageTest() { new JavaTestKit(system) {{ - def agent1Ref = system.actorOf(Agent.props(on.session),"agent1"); + String agent1Id = UUID.randomUUID().toString(); + def agent1Ref = system.actorOf(Agent.props(on.session, agent1Id)); assertNotNull(agent1Ref); - def agent2Ref = system.actorOf(Agent.props(on.session),"agent2"); + String agent2Id = UUID.randomUUID().toString(); + def agent2Ref = system.actorOf(Agent.props(on.session, agent2Id)); assertNotNull(agent2Ref); - agent2Ref.tell(new Method("id",[]),getRef()) + agent2Ref.tell(new Method("vertexId",[]),getRef()) def agent2id = receiveN(1) agent1Ref.tell(new Method("knowsAgent",[agent2id]),getRef()) def edge = receiveN(1) From da7effcf4817ab2a99c95287f9f6cbec714e9655 Mon Sep 17 00:00:00 2001 From: kabir Date: Mon, 2 Apr 2018 23:13:14 +0200 Subject: [PATCH 12/17] AgentTests.agentOwnsNewWorkTest passes --- src/main/groovy/Agent.groovy | 2 +- src/test/groovy/AgentTests.groovy | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/groovy/Agent.groovy b/src/main/groovy/Agent.groovy index 83bb964..6a1283d 100644 --- a/src/main/groovy/Agent.groovy +++ b/src/main/groovy/Agent.groovy @@ -176,7 +176,7 @@ public class Agent extends UntypedAbstractActor { Map params = new HashMap(); params.put("labelValue", "work"); - params.put("agent", this.id()); + params.put("agent", this.vertexId()); params.put("edgeLabel","owns"); logger.warn("Creating new work for agent {}", params.agent) diff --git a/src/test/groovy/AgentTests.groovy b/src/test/groovy/AgentTests.groovy index 7152a49..b5b5535 100644 --- a/src/test/groovy/AgentTests.groovy +++ b/src/test/groovy/AgentTests.groovy @@ -230,7 +230,8 @@ public class AgentTests { @Test void agentOwnsNewWorkTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); assertNotNull(agent1); def work = agent1.ownsWork(); @@ -239,7 +240,7 @@ public class AgentTests { @Test void agentOwnsKnownWorkTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent1 = TestActorRef.create(system, Agent.props(on.session,agent1Id)).underlyingActor(); assertNotNull(agent1); def work = agent1.ownsWork("00011","00001") From 0424649bea8a43ec3462d367a61fbbf0996e37f0 Mon Sep 17 00:00:00 2001 From: kabir Date: Thu, 5 Apr 2018 19:56:49 +0200 Subject: [PATCH 13/17] commented out running opscenter doecker image by defalult --- network-backends/dse-docker/scripts/start-docker.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network-backends/dse-docker/scripts/start-docker.sh b/network-backends/dse-docker/scripts/start-docker.sh index f4a8798..0b83901 100644 --- a/network-backends/dse-docker/scripts/start-docker.sh +++ b/network-backends/dse-docker/scripts/start-docker.sh @@ -7,8 +7,8 @@ docker ps -q -a | xargs docker rm # start dse-server with graph enabled docker run -e DS_LICENSE=accept -e LISTEN_ADDRESS=127.0.0.1 -e START_RPC=true --name dse -d -p 8182:8182 -p 9042:9042 store/datastax/dse-server:5.1.6 -g -# start dse-studio +# start dse-studio (not needed for tests) docker run -e DS_LICENSE=accept --name dse-studio -d -p 9091:9091 --link dse datastax/dse-studio # start dse-opscenter -docker run -e DS_LICENSE=accept --name opscenter -d -p 8888:8888 datastax/dse-opscenter +# docker run -e DS_LICENSE=accept --name opscenter -d -p 8888:8888 datastax/dse-opscenter From 535dc305b9144d6380bf23d9df3ee608608f0c6f Mon Sep 17 00:00:00 2001 From: kabir Date: Thu, 5 Apr 2018 19:57:25 +0200 Subject: [PATCH 14/17] AgentTest.addKnownDemandTest passes --- src/test/groovy/AgentTests.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/groovy/AgentTests.groovy b/src/test/groovy/AgentTests.groovy index b5b5535..04faa18 100644 --- a/src/test/groovy/AgentTests.groovy +++ b/src/test/groovy/AgentTests.groovy @@ -333,7 +333,8 @@ public class AgentTests { @Test void addKnownDemandTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); def work = agent.ownsWork() def offer = agent.addItemToWork("demands",work,"00011") assertEquals("00011",offer.getProperty("value").getValue().asString()); From 01de3121b85b42fc5c25a69740689caabb933c39 Mon Sep 17 00:00:00 2001 From: kabir Date: Thu, 5 Apr 2018 20:09:48 +0200 Subject: [PATCH 15/17] AgentTests.agentOwnsKnownWorkTest passes along with the bunch of others --- src/test/groovy/AgentTests.groovy | 62 ++++++++++++++++++------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/test/groovy/AgentTests.groovy b/src/test/groovy/AgentTests.groovy index 04faa18..c4beb2d 100644 --- a/src/test/groovy/AgentTests.groovy +++ b/src/test/groovy/AgentTests.groovy @@ -38,7 +38,7 @@ public class AgentTests { @Test void idStaticTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); assertNotNull(agent1.id()) logger.info("id of the actor via static interface is {}",agentId) } @@ -46,7 +46,7 @@ public class AgentTests { @Test void idMessageTest() { new JavaTestKit(system) {{ - def agentRef = system.actorOf(Agent.props(on.session),"agent1"); + def agentRef = system.actorOf(Agent.props(on.session, UUID.randomUUID().toString()),"agent1"); agentRef.tell(new Method("id",[]),getRef()) def agentId = receiveN(1) assertNotNull(agentId) @@ -56,8 +56,8 @@ public class AgentTests { @Test void connectTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); def work1 = agent1.ownsWork(); def work2 = agent2.ownsWork(); def item1 = agent1.addItemToWork("demands",work1) @@ -71,8 +71,8 @@ public class AgentTests { @Test void connectAllSimilarTest() { on.flushVertices(); - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); def work1 = agent1.ownsWork('1111','1110'); def work2 = agent2.ownsWork('1100','1000'); def start = agent1.addItemToWork("demands",work2,'0000') @@ -84,8 +84,8 @@ public class AgentTests { @Test void reciprocalDistanceLinkTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); def work1 = agent1.ownsWork(); def work2 = agent2.ownsWork(); def item1 = agent1.addItemToWork("demands",work1) @@ -104,8 +104,8 @@ public class AgentTests { @Test void existsSimilarityTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); def work1 = agent1.ownsWork(); def work2 = agent2.ownsWork(); def item1 = agent1.addItemToWork("demands",work1) @@ -122,8 +122,8 @@ public class AgentTests { @Test void connectIfSimilarTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); def work1 = agent1.ownsWork(); def work2 = agent2.ownsWork(); def item1 = agent1.addItemToWork("demands",work1) @@ -145,10 +145,10 @@ public class AgentTests { @Test void itemsOfKnownAgentsTest() { - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent3 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); - def agent4 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); + def agent2 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); + def agent3 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); + def agent4 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); agent1.knowsAgent(agent2.vertex.getId()); agent2.knowsAgent(agent3.vertex.getId()); @@ -240,6 +240,7 @@ public class AgentTests { @Test void agentOwnsKnownWorkTest() { + String agent1Id = UUID.randomUUID().toString(); def agent1 = TestActorRef.create(system, Agent.props(on.session,agent1Id)).underlyingActor(); assertNotNull(agent1); @@ -257,7 +258,8 @@ public class AgentTests { @Test void allItemsTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); assertNotNull(agent); def work = agent.ownsWork(); @@ -272,13 +274,17 @@ public class AgentTests { @Test void searchAndConnectTest() { on.flushVertices("agent"); - def agent1 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent1 = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); agent1.ownsWork('111110','000000'); - def agent2 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent2Id = UUID.randomUUID().toString(); + def agent2 = TestActorRef.create(system, Agent.props(on.session, agent2Id)).underlyingActor(); agent2.ownsWork('111100','110000'); - def agent3 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent3Id = UUID.randomUUID().toString(); + def agent3 = TestActorRef.create(system, Agent.props(on.session, agent3Id)).underlyingActor(); agent3.ownsWork('100000','111100'); - def agent4 = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent4Id = UUID.randomUUID().toString(); + def agent4 = TestActorRef.create(system, Agent.props(on.session, agent4Id)).underlyingActor(); agent4.ownsWork('111110','000000'); agent1.knowsAgent(agent2.vertex.getId()); @@ -294,7 +300,8 @@ public class AgentTests { @Test void getWorksTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); def work1 = agent.ownsWork(); def work2 = agent.ownsWork(); def work3 = agent.ownsWork(); @@ -309,7 +316,8 @@ public class AgentTests { @Test void addNewOfferTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); def work = agent.ownsWork() def offer = agent.addItemToWork("offers",work) assertNotNull(offer); @@ -317,7 +325,8 @@ public class AgentTests { @Test void addKnownOfferTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); def work = agent.ownsWork() def offer = agent.addItemToWork("offers",work,"00000") assertEquals("00000",offer.getProperty("value").getValue().asString()); @@ -325,7 +334,8 @@ public class AgentTests { @Test void addNewDemandTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + String agent1Id = UUID.randomUUID().toString(); + def agent = TestActorRef.create(system, Agent.props(on.session, agent1Id)).underlyingActor(); def work = agent.ownsWork() def offer = agent.addItemToWork("demands",work) assertNotNull(offer); @@ -342,7 +352,7 @@ public class AgentTests { @Test void getWorkItemsTest() { - def agent = TestActorRef.create(system, Agent.props(on.session)).underlyingActor(); + def agent = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); def work = agent.ownsWork(); def item1 = agent.addItemToWork("demands",work); def item2 = agent.addItemToWork("demands",work); From c2259cb4a1bf0db24fa5565e364941b877d541c2 Mon Sep 17 00:00:00 2001 From: kabir Date: Thu, 5 Apr 2018 20:46:05 +0200 Subject: [PATCH 16/17] AgentTests.allItemsTest passes --- src/main/groovy/Agent.groovy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/groovy/Agent.groovy b/src/main/groovy/Agent.groovy index 6a1283d..2c9726e 100644 --- a/src/main/groovy/Agent.groovy +++ b/src/main/groovy/Agent.groovy @@ -264,7 +264,7 @@ public class Agent extends UntypedAbstractActor { logger.warn("Getting all works owned by agent {}",params.agent); - SimpleGraphStatement s = new SimpleGraphStatement("g.V(agent).out(edgeLabel)",params); + SimpleGraphStatement s = new SimpleGraphStatement("g.V().has('agentId',agent).out(edgeLabel)",params); GraphResultSet rs = session.executeGraph(s); List works = rs.all().collect {it.asVertex()}; @@ -281,12 +281,13 @@ public class Agent extends UntypedAbstractActor { def start = System.currentTimeMillis(); Map params = new HashMap(); - params.put("agentLabelName", this.id()); + params.put("agentId", this.id()); + params.put("agentIdLabel", "agentId"); logger.warn("Getting all items of agent {}", this.id()) SimpleGraphStatement s = new SimpleGraphStatement( - "g.V(agentLabelName).outE('owns').inV().outE().inV().has(label,'item')", params) + "g.V().has(agentIdLabel,agentId).outE('owns').inV().outE().inV().has(label,'item')", params) GraphResultSet rs = session.executeGraph(s); List items = rs.all().collect {it.asVertex() }; From 02758e1e805f0d8b5098d5e0bc14129021f83a87 Mon Sep 17 00:00:00 2001 From: kabir Date: Thu, 5 Apr 2018 20:51:47 +0200 Subject: [PATCH 17/17] all AgentTests pass --- src/main/groovy/Agent.groovy | 5 +++-- src/test/groovy/AgentTests.groovy | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/groovy/Agent.groovy b/src/main/groovy/Agent.groovy index 2c9726e..c152c0b 100644 --- a/src/main/groovy/Agent.groovy +++ b/src/main/groovy/Agent.groovy @@ -335,13 +335,14 @@ public class Agent extends UntypedAbstractActor { private List itemsOfKnownAgents(Integer maxReachDistance) { def start = System.currentTimeMillis() Map params = new HashMap(); - params.put("thisAgent", this.id()); + params.put("thisAgentId", this.id()); + params.put("agentIdLabel","agentId") params.put("repeats", maxReachDistance); logger.warn("Getting a list of all connected items of agent {} with loop {}", this.id(), maxReachDistance) SimpleGraphStatement s = new SimpleGraphStatement( - "g.V(thisAgent).as('s').repeat("+ + "g.V().has(agentIdLabel,thisAgentId).as('s').repeat("+ "both('knows').has(label,'agent')).times(repeats).emit().dedup().as('t')"+ ".where('t',neq('s')).out('owns').out()",params); diff --git a/src/test/groovy/AgentTests.groovy b/src/test/groovy/AgentTests.groovy index c4beb2d..fe4f973 100644 --- a/src/test/groovy/AgentTests.groovy +++ b/src/test/groovy/AgentTests.groovy @@ -40,7 +40,7 @@ public class AgentTests { void idStaticTest() { def agent1 = TestActorRef.create(system, Agent.props(on.session, UUID.randomUUID().toString())).underlyingActor(); assertNotNull(agent1.id()) - logger.info("id of the actor via static interface is {}",agentId) + logger.info("id of the actor via static interface is {}",agent1.id()) } @Test