From 99eacf6a8d2d825c586782e09389e02fcb84d70a Mon Sep 17 00:00:00 2001
From: Marcel Krueger <krueger@vr.rwth-aachen.de>
Date: Mon, 3 Apr 2023 18:25:31 +0200
Subject: [PATCH] test: refactor tests for docker

---
 .gitlab-ci.yml                                |   9 +-
 .../arbor/test_arbor_get_cell_infos.py        |   3 +-
 .../arbor/test_arbor_get_probe_data.py        |   2 +-
 test/{ => access_node/nest}/access_node.log   |   0
 test/access_node/nest/config.py               |   6 +-
 test/access_node/nest/insite.log              |   1 +
 test/access_node/tvb/test_data.py             |  46 +++
 test/access_node/tvb/test_monitors.py         |  40 +++
 test/access_node/tvb/test_simulation_info.py  |  54 ++-
 .../tvb/tvb_general_test_functions.py         |  12 +
 test/conftest.py                              |  45 +--
 test/insite.yml                               |  43 +++
 test/nest_module/test_get_gids.py             |  11 -
 test/nest_module/test_get_multimeters.py      |  26 --
 .../nest_module/test_get_neuron_properties.py |  40 ---
 test/nest_module/test_get_populations.py      |  32 --
 .../test_get_simulation_time_info.py          |  40 ---
 test/nest_module/test_get_spikedetectors.py   |  20 --
 test/nest_module/test_kernel_status.py        |  17 -
 .../NESTServerClient/NESTServerClient.py      |  67 ++++
 .../NESTServerClient/__init__.py              |  23 ++
 .../examples/NESTClient_example.py            |  64 ++++
 .../examples/NESTClient_script.py             |  41 +++
 .../NESTServerClient/examples/__init__.py     |  23 ++
 .../NESTServerClient/setup.py                 |  35 ++
 .../pytest_simulation_server.py               | 318 ++++++++++++++++++
 test/run-tests.yml                            |  49 +++
 27 files changed, 848 insertions(+), 219 deletions(-)
 rename test/{ => access_node/nest}/access_node.log (100%)
 create mode 100644 test/access_node/nest/insite.log
 create mode 100644 test/access_node/tvb/test_data.py
 create mode 100644 test/access_node/tvb/test_monitors.py
 create mode 100644 test/insite.yml
 delete mode 100644 test/nest_module/test_get_gids.py
 delete mode 100644 test/nest_module/test_get_multimeters.py
 delete mode 100644 test/nest_module/test_get_neuron_properties.py
 delete mode 100644 test/nest_module/test_get_populations.py
 delete mode 100644 test/nest_module/test_get_simulation_time_info.py
 delete mode 100644 test/nest_module/test_get_spikedetectors.py
 delete mode 100644 test/nest_module/test_kernel_status.py
 create mode 100644 test/nest_test_network/NESTServerClient/NESTServerClient.py
 create mode 100644 test/nest_test_network/NESTServerClient/__init__.py
 create mode 100644 test/nest_test_network/NESTServerClient/examples/NESTClient_example.py
 create mode 100644 test/nest_test_network/NESTServerClient/examples/NESTClient_script.py
 create mode 100644 test/nest_test_network/NESTServerClient/examples/__init__.py
 create mode 100644 test/nest_test_network/NESTServerClient/setup.py
 create mode 100644 test/nest_test_network/pytest_simulation_server.py
 create mode 100644 test/run-tests.yml

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 023175fc..c77f24d3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,9 +9,14 @@
 
 test:
   tags: 
-    - shell-executor
+    - shell-runner
+  variables:
+      GIT_SUBMODULE_STRATEGY: recursive
   script:
-    - docker-compose -f ./test/run-tests.yml --build --exit-code-from pytest
+    - docker build . -f ./docker/simulators/Dockerfile_TVB -t insite-tvb
+    - docker build . -f ./docker/simulators/Dockerfile_Arbor -t insite-arbor
+    - docker build . -f ./docker/simulators/Dockerfile_NEST -t insite-nest
+    - docker-compose -f ./test/run-tests.yml up --build --exit-code-from pytest
 
 # build:
 #     stage: build
diff --git a/test/access_node/arbor/test_arbor_get_cell_infos.py b/test/access_node/arbor/test_arbor_get_cell_infos.py
index 35037bcf..490ebc2d 100644
--- a/test/access_node/arbor/test_arbor_get_cell_infos.py
+++ b/test/access_node/arbor/test_arbor_get_cell_infos.py
@@ -2,8 +2,7 @@ from arbor_general_test_functions import *
 import pytest
 
 #URL used for the "nest_get_spikes" HTTP-query
-URL_NEST_GET_CELL_INFOS = BASE_REQUEST_URL + "/cell_infos/"
-#TODO: Fix inconsistency between plural and singular regarding "cell_info(s)"
+URL_NEST_GET_CELL_INFOS = BASE_REQUEST_URL + "/cellInfos/"
 
 #Names for the "nest_get_spikes" request-parameters
 class NEST_GET_PROBE_DATA_PARAMETER_NAME_LIST (Enum):
diff --git a/test/access_node/arbor/test_arbor_get_probe_data.py b/test/access_node/arbor/test_arbor_get_probe_data.py
index 57d91af2..7d1c1caf 100644
--- a/test/access_node/arbor/test_arbor_get_probe_data.py
+++ b/test/access_node/arbor/test_arbor_get_probe_data.py
@@ -1,7 +1,7 @@
 from arbor_general_test_functions import *
 import pytest
 
-URL_NEST_GET_PROBE_DATA = BASE_REQUEST_URL + "/probe_data/"
+URL_NEST_GET_PROBE_DATA = BASE_REQUEST_URL + "/probeData/"
 
 class NEST_GET_PROBE_DATA_PARAMETER_NAME_LIST (Enum):
     #TODO: make "x_id_" and "xId" consistent between json and params
diff --git a/test/access_node.log b/test/access_node/nest/access_node.log
similarity index 100%
rename from test/access_node.log
rename to test/access_node/nest/access_node.log
diff --git a/test/access_node/nest/config.py b/test/access_node/nest/config.py
index 8faa56bd..66afb770 100644
--- a/test/access_node/nest/config.py
+++ b/test/access_node/nest/config.py
@@ -1,8 +1,8 @@
 from enum import Enum
 
 #Base URL and prefix for every HTTP-querie to the NEST-Server
-BASE_REQUEST_URL = "http://localhost:52056/nest"
-BASE_REQUEST_URL_V2 = "http://localhost:52056/v2/nest"
+BASE_REQUEST_URL = "http://insite-access-node:52056/nest"
+BASE_REQUEST_URL_V2 = "http://insite-access-node:52056/v2/nest"
 
 #Relates every value to its corresponding name in the spike-data JSON-field
 class JSON_VALUE_TO_FIELD_NAME(Enum):
@@ -14,4 +14,4 @@ class JSON_VALUE_TO_FIELD_NAME(Enum):
     lastFrame = "lastFrame"
     simulationId = "simId"
     nodeCollections = "nodeCollections"
-    kernelStatuses = "kernelStatuses"
\ No newline at end of file
+    kernelStatuses = "kernelStatuses"
diff --git a/test/access_node/nest/insite.log b/test/access_node/nest/insite.log
new file mode 100644
index 00000000..22c1d9d1
--- /dev/null
+++ b/test/access_node/nest/insite.log
@@ -0,0 +1 @@
+python: can't open file '/home/mk821590/Code/Work/HBP/insite/test/access_node/nest/nest_test_network/pytest_simulation_server.py': [Errno 2] No such file or directory
diff --git a/test/access_node/tvb/test_data.py b/test/access_node/tvb/test_data.py
new file mode 100644
index 00000000..f81f0616
--- /dev/null
+++ b/test/access_node/tvb/test_data.py
@@ -0,0 +1,46 @@
+import tvb_general_test_functions
+from tvb_config import *
+
+def data_has_all_fields(data):
+    assert("uid" in data), f"Data member uid missing"
+    assert("timesteps" in data), f"Data member timesteps missing"
+    
+def data_populated_all_fields(data):
+    for member in data:
+        assert(data[member] is not None), f"Data member {member} null"
+    assert(len(data["timesteps"]) > 0), f"Data member timesteps empty"
+    
+def timesteps_has_all_fields(data):
+    for timestep in data["timesteps"]:
+        assert("time" in timestep), f"Data[timesteps] member time missing"
+
+def timesteps_populated_all_fields(data):
+    for timestep in data["timesteps"]:
+        for member in timestep:
+            assert(timestep[member] is not None), f"Data[timesteps] member {member} null"
+
+def timesteps_valid(data):
+    history = []
+    for timestep in data["timesteps"]:
+        for prev_time in history:
+            assert(timestep["time"] > prev_time), f"Data[timesteps] members values not successive in time"
+        history.append(timestep["time"])
+
+def data_has_unique_ids(data_points):
+    uids = []
+    for data in data_points:
+        assert(not data["uid"] in uids), f"Data member uid not unique"
+        uids.append(data["uid"])
+            
+def test_monitors_exist(tvb_simulation):
+    # TESTS
+    datapoints = tvb_general_test_functions.return_json_body_if_status_ok(BASE_REQUEST_URL + "/monitorData")
+    assert(datapoints is not None)
+    assert(len(datapoints) > 0), f"Datapoints empty"
+    for data in datapoints:
+        data_has_all_fields(data)
+        data_populated_all_fields(data)
+        timesteps_has_all_fields(data)
+        timesteps_populated_all_fields(data)
+        timesteps_valid(data)
+    data_has_unique_ids(datapoints)
diff --git a/test/access_node/tvb/test_monitors.py b/test/access_node/tvb/test_monitors.py
new file mode 100644
index 00000000..43e37fb4
--- /dev/null
+++ b/test/access_node/tvb/test_monitors.py
@@ -0,0 +1,40 @@
+import tvb_general_test_functions
+from tvb_config import *
+
+def monitors_has_all_fields(monitor):
+    assert("uid" in monitor), f"Monitor member uid missing"
+    assert("internalId" in monitor), f"Monitor member internalId missing"
+    assert("type" in monitor), "Monitor member type missing"
+    assert("observedVariables" in monitor), "Monitor member observedVariables missing"
+    
+def monitors_populated_all_fields(monitor):
+    for member in monitor:
+        assert(monitor[member] is not None), f"Monitor member {member} null"
+    assert(len(monitor["observedVariables"]) > 0), f"Monitor member observedVariables empty"
+    
+def monitors_has_unique_ids(monitors):
+    uids = []
+    ids = []
+    for monitor in monitors:
+        assert(not monitor["internalId"] in ids), f"internalId not unique"
+        ids.append(monitor["internalId"])
+        assert(not monitor["uid"] in ids), f"Monitor member uid not unique"
+        uids.append(monitor["uid"])
+
+        
+def monitors_has_unique_variables(monitors):
+    for monitor in monitors:        
+        variables = []
+        for variable in monitor["observedVariables"]:
+            assert(not variable in variables), f"Monitor member observedVariables has duplicate entries"
+            variables.append(variable)
+            
+def test_monitors_exist(tvb_simulation):
+    monitors = tvb_general_test_functions.return_json_body_if_status_ok(BASE_REQUEST_URL + "/monitors")
+    assert(monitors is not None)
+    assert(len(monitors) > 0), f"Monitors empty"
+    for monitor in monitors:
+        monitors_has_all_fields(monitor)
+        monitors_populated_all_fields(monitor)
+    monitors_has_unique_ids(monitors)
+    monitors_has_unique_variables(monitors)
diff --git a/test/access_node/tvb/test_simulation_info.py b/test/access_node/tvb/test_simulation_info.py
index 933179b9..8f05d9d6 100644
--- a/test/access_node/tvb/test_simulation_info.py
+++ b/test/access_node/tvb/test_simulation_info.py
@@ -1,9 +1,55 @@
 import tvb_general_test_functions
 from tvb_config import *
+from numbers import Number
 
+simulationInfoEndpoint = "/simulationInfo/"
 
-def tests_get_simulation_info():
-    tvb_general_test_functions.return_json_body_if_status_ok(BASE_REQUEST_URL + "/simulation_info/")
+def simulation_has_all_fields(info):
+    assert("Type" in info), f"Simulation info member Type missing"
+    assert("title" in info), f"Simulation info member title missing"
+    assert("connectivity" in info), f"Simulation info member connectivity missing"
+    assert("conduction_speed" in info), f"Simulation info member conduction_speed missing"
+    assert("coupling" in info), f"Simulation info member coupling missing"
+    assert("surface" in info), f"Simulation info member surface missing"
+    assert("stimulus" in info), f"Simulation info member stimulus missing"
+    assert("model" in info), f"Simulation info member model missing"
+    assert("integrator" in info), f"Simulation info member integrator missing"
+    assert("initial_conditions" in info), f"Simulation info member initial_conditions missing"
+    assert("monitors" in info), f"Simulation toplevel info member monitors missing"
+    assert("simulation_length" in info), f"Simulation info member simulation_length missing"
+    assert("gid" in info), f"Simulation info gid missing"
+    
+def simulation_populated_all_fields(info):
+    for member in info:
+        assert(info[member] is not None), f"Simulation toplevel info has null member"
+        
+def simulation_populated_first_sublevel(subinfo):
+    if type(subinfo) is dict:
+        for member in subinfo:
+            assert(not member.lower() == "error"), f"Simulation sublevel info has error member"
+            assert(subinfo[member] is not None), f"Simulation sublevel info has null member"
+            return
+        
+    if type(subinfo) is list:
+        for member in subinfo:
+            assert(member is not None), f"Simulation sublevel info has null member in list"
+    
+    assert(subinfo is not None), f"Simulation sublevel info is null"
+    
+def tests_get_simulation_info(tvb_simulation):
+    simulation = tvb_general_test_functions.return_json_body_if_status_ok(BASE_REQUEST_URL + simulationInfoEndpoint)
+    assert(simulation is not None)
+    simulation_has_all_fields(simulation)
+    simulation_populated_all_fields(simulation)
+    
+    for member in simulation:
+        if member == "Type": continue                   
+        if simulation[member] == "None": continue
 
-# def test_get_simulation_gid():
-#     tvb_general_test_functions.return_json_body_if_status_ok(BASE_REQUEST_URL + "/simulation_info/gid")
+        simulation_sublevel = tvb_general_test_functions.return_json_body_if_status_ok(BASE_REQUEST_URL + simulationInfoEndpoint + member)
+        assert(simulation_sublevel is not None)
+        assert(isinstance(simulation_sublevel,Number) or len(simulation_sublevel) > 0)
+        simulation_populated_first_sublevel(simulation_sublevel)
+    
+def test_get_simulation_gid(tvb_simulation):
+    tvb_general_test_functions.return_json_body_if_status_ok(BASE_REQUEST_URL + simulationInfoEndpoint + "gid")
diff --git a/test/access_node/tvb/tvb_general_test_functions.py b/test/access_node/tvb/tvb_general_test_functions.py
index 3a22d3e8..97789259 100644
--- a/test/access_node/tvb/tvb_general_test_functions.py
+++ b/test/access_node/tvb/tvb_general_test_functions.py
@@ -10,6 +10,18 @@ from requests.api import request
 from requests.sessions import Request
 from tvb_config import *
 
+def print_todo(message):
+    HEADER = '\033[95m'
+    OKBLUE = '\033[94m'
+    OKCYAN = '\033[96m'
+    OKGREEN = '\033[92m'
+    WARNING = '\033[93m'
+    FAIL = '\033[91m'
+    ENDC = '\033[0m'
+    BOLD = '\033[1m'
+    UNDERLINE = '\033[4m'
+    print(WARNING + UNDERLINE + BOLD + "[TODO] " + message + ENDC)
+
 #Checks if the given HTTP-request has a valid http-response-code. Returns the spike-data in Json format.
 def return_json_body_if_status_ok(request):
     request = requests.get(request)
diff --git a/test/conftest.py b/test/conftest.py
index 1c5238f2..8fcfd1ac 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -5,6 +5,11 @@ import requests
 import time
 import os
 
+@pytest.fixture(scope="session")
+def tvb_simulation(request):
+    time.sleep(2)
+    return True
+
 @pytest.fixture(scope="session")
 def nest_simulation(request):
     logfile = open("insite.log", "w")
@@ -20,7 +25,6 @@ def nest_simulation(request):
 
     # process2 = subprocess.Popen(["./insite-access-node"], stdout=logfile_access_node, stderr=subprocess.STDOUT)
     time.sleep(1)
-    print("\nWaiting for NEST start up",end='')
 
     def finalize():
         # process.send_signal(signal.SIGINT)
@@ -44,26 +48,25 @@ def nest_simulation(request):
     print("\nWaiting for NEST start up",end='')
     request.addfinalizer(finalize)
 
-    begin_time = time.time()
-    print("\nWaiting for NEST start up",end='')
-    while True:
-        print(".",end='',flush=True)
-        time.sleep(1.0)
-        try:
-            r = requests.get("http://insite-nest-module:52425/")
-            print("\nNEST started",end='')
-
-            if r.status_code == 200:
-                print("started.")
-                print("sending nest sim")
-                process = subprocess.Popen(["python", "/tests/nest_test_network/pytest_simulation_server.py"], stdout=logfile, stderr=subprocess.STDOUT)
+    # print("\nWaiting for NEST start up",end='')
+    # while True:
+    #     # print(".",end='',flush=True)
+    #     time.sleep(1.0)
+        # try:
+            # r = requests.get("http://insite-nest-module:52425/")
+            # print("\nNEST started",end='')
 
-                break
-        except requests.exceptions.ConnectionError:
-            pass
-        current_time = time.time()
-        if current_time - begin_time > 60:
-            pytest.fail('Timeout during container start')
+        #     if r.status_code == 200:
+        #         print("started.")
+        #         print("sending nest sim")
+        #         process = subprocess.Popen(["python", "/tests/nest_test_network/pytest_simulation_server.py"], stdout=logfile, stderr=subprocess.STDOUT)
+        #
+        #         break
+        # except requests.exceptions.ConnectionError:
+        #     pass
+        # current_time = time.time()
+        # if current_time - begin_time > 60:
+        #     pytest.fail('Timeout during container start')
 
     print("Waiting for access node start up",end='')
     while True:
@@ -80,4 +83,4 @@ def nest_simulation(request):
         if current_time - begin_time > 60:
             pytest.fail('Timeout during container start')
 
-    return process
+    return True
diff --git a/test/insite.yml b/test/insite.yml
new file mode 100644
index 00000000..eea2a980
--- /dev/null
+++ b/test/insite.yml
@@ -0,0 +1,43 @@
+version: "3"
+
+services:
+  insite-access-node:
+    image: access_node:rc2.1 
+    ports:
+      - "52056:52056"
+    depends_on:
+      - "insite-nest-module"
+    network_mode: "host"
+    environment:
+      INSITE_NEST_BASE_URL: "insite-nest-module"
+      # INSITE_ARBOR_BASE_URL: "arbor-module"
+    # volumes:
+    #   - ${PWD}/config.toml:/config.toml
+
+  nest-desktop:
+    image: docker-registry.ebrains.eu/nest/nest-desktop:3.2
+    environment:
+      NEST_SIMULATOR_PORT: 52425
+    ports:
+      - '54286:54286'
+
+  insite-nest-module:
+    image: insite-nest_module:v2
+    ports:
+      - "18080:18080"
+      - "52425:52425"
+    volumes:
+      - ${PWD}/data:/opt/data
+    environment:
+      NEST_CONTAINER_MODE: "nest-server"
+  tvb-module: 
+    network_mode: "host"
+    image: insite-tvb
+
+    # ports:
+      # - "1337:1337"
+
+  # arbor-module: 
+  #   image: insite-arbor
+  #   ports:
+  #     - "1337:1337"
diff --git a/test/nest_module/test_get_gids.py b/test/nest_module/test_get_gids.py
deleted file mode 100644
index 510c789a..00000000
--- a/test/nest_module/test_get_gids.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import requests
-import numbers
-
-# def test_get_gids(nest_simulation):
-#     r = requests.get("http://localhost:8080/nest/gids")
-#     gids = r.json()
-
-#     assert(isinstance(gids, list))
-#     for gid in gids:
-#         assert(isinstance(gid, numbers.Number))
-#         assert(gids.count(gid) == 1)
diff --git a/test/nest_module/test_get_multimeters.py b/test/nest_module/test_get_multimeters.py
deleted file mode 100644
index bf3a44f4..00000000
--- a/test/nest_module/test_get_multimeters.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import requests
-import numbers
-
-def test_get_multimeters(nest_simulation):
-    request = requests.get("http://localhost:18080/multimeters")
-    assert(request.status_code == 200)
-    
-    multimeters = request.json()
-
-    assert(isinstance(multimeters, list))
-
-    for multimeter in multimeters:
-        assert('multimeterId' in multimeter)
-        assert(isinstance(multimeter['multimeterId'], int))
-
-        assert('attributes' in multimeter)
-        assert(isinstance(multimeter['attributes'], list))
-
-        for attribute in multimeter['attributes']:
-            assert(isinstance(attribute, str))
-
-        assert('nodeIds' in multimeter)
-        assert(isinstance(multimeter['nodeIds'], list))
-
-        for node_id in multimeter['nodeIds']:
-            assert(isinstance(node_id, int))
diff --git a/test/nest_module/test_get_neuron_properties.py b/test/nest_module/test_get_neuron_properties.py
deleted file mode 100644
index 610b1308..00000000
--- a/test/nest_module/test_get_neuron_properties.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import requests
-import numbers
-
-# Currently, this endpoint does not behave according to the specs, uncomment this test if they do
-
-# def test_get_neuron_properties(nest_simulation):
-#     r = requests.get("http://localhost:8080/nest/neuron_properties")
-#     neurons_properties = r.json()
-
-#     assert(isinstance(neurons_properties, list))
-
-#     for neuron_properties in neurons_properties:
-#         assert('gid' in neuron_properties)
-#         assert(isinstance(neuron_properties['gid'], numbers.Number))
-#         assert('properties' in neuron_properties)
-#         assert('population' in neuron_properties['properties'])
-#         assert(isinstance(neuron_properties['properties']['population'], numbers.Integral))
-#         assert('position' in neuron_properties['properties'])
-#         assert(isinstance(neuron_properties['properties']['position'], list))
-
-# def test_get_neuron_properties_with_gid_filter(nest_simulation):
-#     r = requests.get("http://localhost:8080/nest/neuron_properties?gids=3&gids=2")
-#     neurons_properties = r.json()
-
-#     assert(isinstance(neurons_properties, list))
-#     assert(len(neurons_properties) == 2)
-
-#     for neuron_properties in neurons_properties:
-#         assert('gid' in neuron_properties)
-#         assert(isinstance(neuron_properties['gid'], numbers.Number))
-#         assert('properties' in neuron_properties)
-#         assert('population' in neuron_properties['properties'])
-#         assert(isinstance(neuron_properties['properties']['population'], numbers.Integral))
-#         assert('position' in neuron_properties['properties'])
-#         assert(isinstance(neuron_properties['properties']['position'], list))
-
-#     assert(neuron_properties[0].gid == 3)
-#     assert(neuron_properties[1].gid == 2)
-
-
diff --git a/test/nest_module/test_get_populations.py b/test/nest_module/test_get_populations.py
deleted file mode 100644
index 96bcba5f..00000000
--- a/test/nest_module/test_get_populations.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import requests
-import numbers
-
-# def test_get_populations(nest_simulation):
-#     r = requests.get("http://localhost:8080/nest/populations")
-#     populations = r.json()
-
-#     assert(isinstance(populations, list))
-#     for population in populations:
-#         assert(isinstance(population, numbers.Number))
-#         assert(populations.count(population) == 1)
-
-# def test_get_population_gids(nest_simulation):
-#     populations_request = requests.get("http://localhost:8080/nest/populations")
-
-#     assert(populations_request.status_code == 200)
-#     populations = populations_request.json()
-
-#     combined = []
-#     for population in populations:
-#         population_gids_request = requests.get("http://localhost:8080/nest/population/${}/gids".format(population))
-#         assert(population_gids_request.status_code == 200)
-#         population_gids = population_gids_request.json()
-#         assert(isinstance(population_gids, list))
-#         for gid in population_gids:
-#             assert(isinstance(gid, numbers.Integral))
-#         combined.extend(population_gids)
-
-#     gids_request = requests.get("http://localhost:8080/nest/gids")
-#     assert(gids_request.status_code == 200)
-#     gids = gids_request.json()
-#     assert(combined.sort() == gids.sort())
diff --git a/test/nest_module/test_get_simulation_time_info.py b/test/nest_module/test_get_simulation_time_info.py
deleted file mode 100644
index f7966024..00000000
--- a/test/nest_module/test_get_simulation_time_info.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import requests
-import pytest
-import time
-import numbers
-import math
-
-class NestSimulationTime:
-    def __init__(self):
-        self.current_time = 0.0
-
-@pytest.fixture(scope="session")
-def simulation_time(request):
-    return NestSimulationTime()
-
-def test_simulation_time_info(nest_simulation, simulation_time):
-    r = requests.get("http://localhost:18080/simulationTimeInfo")
-    simulation_time_info = r.json()
-
-    assert(r.status_code == 200)
-
-    assert('stepSize' in simulation_time_info)
-    assert(isinstance(simulation_time_info['stepSize'], numbers.Number))
-    assert('current' in simulation_time_info)
-    assert(isinstance(simulation_time_info['current'], numbers.Number))
-    assert('begin' in simulation_time_info)
-    assert(isinstance(simulation_time_info['begin'], numbers.Number))
-    assert('end' in simulation_time_info)
-    assert(isinstance(simulation_time_info['end'], numbers.Number))
-
-    assert(simulation_time_info['begin'] <= simulation_time_info['current'])
-    # assert(simulation_time_info['end'] >= simulation_time_info['current'])
-
-    # In theory these should be true but due to floating point inaccuracies and bad default values for 'step_size' it is not
-    # assert(math.fmod(simulation_time_info['current'], simulation_time_info['step_size']) == 0.0)
-    # assert(math.fmod(simulation_time_info['begin'], simulation_time_info['step_size']) == 0.0)
-    # assert(math.fmod(simulation_time_info['end'], simulation_time_info['step_size']) == 0.0)
-
-    new_time = simulation_time_info['current']
-    assert(simulation_time.current_time <= new_time)
-    simulation_time.current_time = new_time
diff --git a/test/nest_module/test_get_spikedetectors.py b/test/nest_module/test_get_spikedetectors.py
deleted file mode 100644
index de12c73a..00000000
--- a/test/nest_module/test_get_spikedetectors.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import requests
-import numbers
-
-def test_get_spikedetectors(nest_simulation):
-    request = requests.get("http://localhost:18080/spikerecorders")
-    assert(request.status_code == 200)
-    
-    spike_detectors = request.json()
-
-    assert(isinstance(spike_detectors, list))
-
-    for spike_detector in spike_detectors:
-        assert('spikerecorderId' in spike_detector)
-        assert(isinstance(spike_detector['spikerecorderId'], int))
-
-        assert('nodeIds' in spike_detector)
-        assert(isinstance(spike_detector['nodeIds'], list))
-
-        for node_id in spike_detector['nodeIds']:
-            assert(isinstance(node_id, int))
diff --git a/test/nest_module/test_kernel_status.py b/test/nest_module/test_kernel_status.py
deleted file mode 100644
index 77c213f1..00000000
--- a/test/nest_module/test_kernel_status.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import requests
-import numbers
-
-def test_get_kernel_status(nest_simulation):
-    request = requests.get("http://localhost:18080/kernelStatus")
-    assert(request.status_code == 200)
-    
-    kernel_status = request.json()
-
-
-    assert(isinstance(kernel_status, dict))
-
-    assert('num_processes' in kernel_status)
-    assert(isinstance(kernel_status['num_processes'],numbers.Number))
-        
-    assert('local_num_threads' in kernel_status)
-    assert(isinstance(kernel_status['local_num_threads'],numbers.Number))
diff --git a/test/nest_test_network/NESTServerClient/NESTServerClient.py b/test/nest_test_network/NESTServerClient/NESTServerClient.py
new file mode 100644
index 00000000..f599705c
--- /dev/null
+++ b/test/nest_test_network/NESTServerClient/NESTServerClient.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+#
+# NESTServerClient.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST.  If not, see <http://www.gnu.org/licenses/>.
+
+import requests
+
+
+__all__ = [
+    'NESTServerClient',
+]
+
+
+def encode(response):
+    if response.ok:
+        return response.json()
+    elif response.status_code == 400:
+        raise response.text
+
+
+class NESTServerClient:
+
+    def __init__(self, host='localhost', port=52425):
+        self.url = 'http://{}:{}/'.format(host, port)
+        self.headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
+
+    def __getattr__(self, call):
+        def method(*args, **kwargs):
+            kwargs.update({'args': args})
+            response = requests.post(self.url + 'api/' + call, json=kwargs, headers=self.headers)
+            return encode(response)
+        return method
+
+    def exec_script(self, source, return_vars=None):
+        params = {
+            'source': source,
+            'return': return_vars,
+        }
+        response = requests.post(self.url + 'exec', json=params, headers=self.headers)
+        return encode(response)
+
+    def from_file(self, filename, return_vars=None):
+        with open(filename, 'r') as f:
+            lines = f.readlines()
+        script = ''.join(lines)
+        print('Execute script code of {}'.format(filename))
+        print('Return variables: {}'.format(return_vars))
+        print(20 * '-')
+        print(script)
+        print(20 * '-')
+        return self.exec_script(script, return_vars)
diff --git a/test/nest_test_network/NESTServerClient/__init__.py b/test/nest_test_network/NESTServerClient/__init__.py
new file mode 100644
index 00000000..1a2f534c
--- /dev/null
+++ b/test/nest_test_network/NESTServerClient/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# __init__.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST.  If not, see <http://www.gnu.org/licenses/>.
+
+
+from .NESTServerClient import *
diff --git a/test/nest_test_network/NESTServerClient/examples/NESTClient_example.py b/test/nest_test_network/NESTServerClient/examples/NESTClient_example.py
new file mode 100644
index 00000000..a8b709e0
--- /dev/null
+++ b/test/nest_test_network/NESTServerClient/examples/NESTClient_example.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# NESTClient_example.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST.  If not, see <http://www.gnu.org/licenses/>.
+
+from NESTServerClient import NESTServerClient
+
+print('Running client examples using NEST via NEST Server')
+
+# Load NEST Server client
+nestsc = NESTServerClient()
+
+#
+# Use NEST Server API
+#
+print('\n')
+print('Execute script code with NEST Server API')
+print('-' * 20)
+
+# Reset kernel
+nestsc.ResetKernel()
+
+# Create nodes
+pg = nestsc.Create("poisson_generator", params={"rate": 6500.})
+neurons = nestsc.Create("iaf_psc_alpha", 100)
+sr = nestsc.Create("spike_recorder")
+
+# Connect nodes
+nestsc.Connect(pg, neurons, syn_spec={'weight': 10.})
+nestsc.Connect(neurons[::10], sr)
+
+# Simulate
+nestsc.Simulate(1000.0)
+
+# Get events
+n_events = nestsc.GetStatus(sr, 'n_events')[0]
+print('Number of events:', n_events)
+
+
+#
+# Use NEST Server exec
+#
+print('\n')
+print('Execute script code from file')
+print('-' * 20)
+
+n_events = nestsc.from_file('NESTClient_script.py', 'n_events')['data']
+print('Number of events:', n_events)
diff --git a/test/nest_test_network/NESTServerClient/examples/NESTClient_script.py b/test/nest_test_network/NESTServerClient/examples/NESTClient_script.py
new file mode 100644
index 00000000..f5c72146
--- /dev/null
+++ b/test/nest_test_network/NESTServerClient/examples/NESTClient_script.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# NESTClient_script.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST.  If not, see <http://www.gnu.org/licenses/>.
+
+import nest
+
+
+# Reset kernel
+nest.ResetKernel()
+
+# Create nodes
+pg = nest.Create("poisson_generator", params={"rate": 6500.})
+neurons = nest.Create("iaf_psc_alpha", 100)
+sr = nest.Create("spike_recorder")
+
+# Connect nodes
+nest.Connect(pg, neurons, syn_spec={"weight": 10.})
+nest.Connect(neurons[::10], sr)
+
+# Simulate
+nest.Simulate(1000.)
+
+# Get events
+n_events = sr.get("n_events")
diff --git a/test/nest_test_network/NESTServerClient/examples/__init__.py b/test/nest_test_network/NESTServerClient/examples/__init__.py
new file mode 100644
index 00000000..1a2f534c
--- /dev/null
+++ b/test/nest_test_network/NESTServerClient/examples/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# __init__.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST.  If not, see <http://www.gnu.org/licenses/>.
+
+
+from .NESTServerClient import *
diff --git a/test/nest_test_network/NESTServerClient/setup.py b/test/nest_test_network/NESTServerClient/setup.py
new file mode 100644
index 00000000..8cbe11c6
--- /dev/null
+++ b/test/nest_test_network/NESTServerClient/setup.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+#
+# setup.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST.  If not, see <http://www.gnu.org/licenses/>.
+
+# NESTServerClient --- A Client for NEST Server
+
+from distutils.core import setup
+
+setup(name='NESTServerClient',
+      version='0.1',
+      description=('NESTServerClient sends JSON requests to NEST Server.'),
+      author='Sebastian Spreizer',
+      author_email='spreizer@web.de',
+      url='https://www.nest-simulator.org',
+      license='GNU Public License v2 or later',
+      packages=['NESTServerClient', 'NESTServerClient.examples'],
+      package_dir={'NESTServerClient': ''}
+      )
diff --git a/test/nest_test_network/pytest_simulation_server.py b/test/nest_test_network/pytest_simulation_server.py
new file mode 100644
index 00000000..565d4120
--- /dev/null
+++ b/test/nest_test_network/pytest_simulation_server.py
@@ -0,0 +1,318 @@
+# -*- coding: utf-8 -*-
+#
+# brunel_delta_nest.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Random balanced network (delta synapses)
+----------------------------------------------
+
+This script simulates an excitatory and an inhibitory population on
+the basis of the network used in [1]_
+
+When connecting the network customary synapse models are used, which
+allow for querying the number of created synapses. Using spike
+detectors the average firing rates of the neurons in the populations
+are established. The building as well as the simulation time of the
+network are recorded.
+
+References
+~~~~~~~~~~~~~~
+
+.. [1] Brunel N (2000). Dynamics of sparsely connected networks of excitatory and
+       inhibitory spiking neurons. Journal of Computational Neuroscience 8,
+       183-208.
+
+"""
+
+###############################################################################
+# Import all necessary modules for simulation, analysis and plotting.
+
+from NESTServerClient import NESTServerClient
+import signal
+import math
+
+import time
+import sys
+from numpy import exp
+import pprint
+
+
+def sigint_handler(sig, frame):
+    print('Exiting...')
+    sys.exit(0)
+
+
+signal.signal(signal.SIGINT, sigint_handler)
+
+nsc = NESTServerClient(host="insite-nest-module")
+
+try:
+    nsc.Install("insitemodule")
+except:
+    pass
+
+nsc.ResetKernel()
+
+###############################################################################
+# Assigning the current time to a variable in order to determine the build
+# time of the network.
+
+startbuild = time.time()
+
+###############################################################################
+# Assigning the simulation parameters to variables.
+
+dt = 0.1  # the resolution in ms
+simtime = float(sys.argv[1]) if len(
+    sys.argv) > 1 else 1000.0  # Simulation time in ms
+delay = 1.5  # synaptic delay in ms
+
+
+###############################################################################
+# Definition of the parameters crucial for asynchronous irregular firing of
+# the neurons.
+
+g = 5.0  # ratio inhibitory weight/excitatory weight
+eta = 2.0  # external rate relative to threshold rate
+epsilon = 0.1  # connection probability
+
+###############################################################################
+# Definition of the number of neurons in the network and the number of neuron
+# recorded from
+
+order = int(sys.argv[2]) if len(
+    sys.argv) > 2 else 25 # Should be square, otherwise the position grid becomes invalid
+NE = 4 * order  # number of excitatory neurons
+NI = 1 * order  # number of inhibitory neurons
+N_neurons = NE + NI  # number of neurons in total
+
+###############################################################################
+# Definition of connectivity parameter
+
+CE = int(epsilon * NE)  # number of excitatory synapses per neuron
+CI = int(epsilon * NI)  # number of inhibitory synapses per neuron
+C_tot = int(CI + CE)  # total number of synapses per neuron
+
+###############################################################################
+# Initialization of the parameters of the integrate and fire neuron and the
+# synapses. The parameter of the neuron are stored in a dictionary.
+
+tauMem = 20.0  # time constant of membrane potential in ms
+theta = 20.0  # membrane threshold potential in mV
+neuron_params = {"C_m": 1.0,
+                 "tau_m": tauMem,
+                 "t_ref": 2.0,
+                 "E_L": 0.0,
+                 "V_reset": 0.0,
+                 "V_m": 0.0,
+                 "V_th": theta}
+J = 0.1  # postsynaptic amplitude in mV
+J_ex = J  # amplitude of excitatory postsynaptic potential
+J_in = -g * J_ex  # amplitude of inhibitory postsynaptic potential
+
+###############################################################################
+# Definition of threshold rate, which is the external rate needed to fix the
+# membrane potential around its threshold, the external firing rate and the
+# rate of the poisson generator which is multiplied by the in-degree CE and
+# converted to Hz by multiplication by 1000.
+
+nu_th = theta / (J * CE * tauMem)
+nu_ex = eta * nu_th
+p_rate = 1000.0 * nu_ex * CE
+
+###############################################################################
+# Configuration of the simulation kernel by the previously defined time
+# resolution used in the simulation. Setting ``print_time`` to `True` prints the
+# already processed simulation time as well as its percentage of the total
+# simulation time.
+
+nsc.SetKernelStatus({"resolution": dt,
+                      "overwrite_files": True})
+
+print("Building network")
+
+###############################################################################
+# Configuration of the model ``iaf_psc_delta`` and ``poisson_generator`` using
+# ``SetDefaults``. This function expects the model to be the inserted as a
+# string and the parameter to be specified in a dictionary. All instances of
+# theses models created after this point will have the properties specified
+# in the dictionary by default.
+
+nsc.SetDefaults("iaf_psc_delta", neuron_params)
+nsc.SetDefaults("poisson_generator", {"rate": p_rate})
+
+###############################################################################
+# Creation of the nodes using ``Create``. We store the returned handles in
+# variables for later reference. Here the excitatory and inhibitory, as well
+# as the poisson generator and two spike detectors. The spike detectors will
+# later be used to record excitatory and inhibitory spikes.
+
+# pos = nest.spatial.free(nest.random.uniform(-0.5,0.5), extent=[1.5,1.5,1.5])
+pos = [[0,0,0], [1,1,1]]
+pos2 = [[2,2,2], [3,3,3]]
+
+
+# nodes_ex = nsc.Create("iaf_psc_delta", positions=nest.spatial.grid([int(math.sqrt(NE)), int(math.sqrt(NE))]))
+# nodes_ex = nsc.Create("iaf_psc_delta", positions=pos)
+nodes_ex = nsc.Create("iaf_psc_delta",NE)
+# nodes_in = nsc.Create("iaf_psc_delta", positions=nest.spatial.grid([int(math.sqrt(NI)), int(math.sqrt(NI))]))
+# nodes_in = nsc.Create("iaf_psc_delta", positions=pos2)
+nodes_in = nsc.Create("iaf_psc_delta", NI)
+noise = nsc.Create("poisson_generator")
+espikes = nsc.Create("spike_recorder")
+ispikes = nsc.Create("spike_recorder")
+
+nsc.SetStatus(espikes, [{"label": "brunel-py-ex",
+                         "record_to": "insite","user_id":0}])
+
+nsc.SetStatus(ispikes, [{"label": "brunel-py-in",
+                          "record_to": "insite"}])
+
+print("Connecting devices")
+
+###############################################################################
+# Definition of a synapse using ``CopyModel``, which expects the model name of
+# a pre-defined synapse, the name of the customary synapse and an optional
+# parameter dictionary. The parameters defined in the dictionary will be the
+# default parameter for the customary synapse. Here we define one synapse for
+# the excitatory and one for the inhibitory connections giving the
+# previously defined weights and equal delays.
+
+nsc.CopyModel("static_synapse", "excitatory",
+               {"weight": J_ex, "delay": delay})
+nsc.CopyModel("static_synapse", "inhibitory",
+               {"weight": J_in, "delay": delay})
+
+###############################################################################
+# Connecting the previously defined poisson generator to the excitatory and
+# inhibitory neurons using the excitatory synapse. Since the poisson
+# generator is connected to all neurons in the population the default rule
+# (# ``all_to_all``) of ``Connect`` is used. The synaptic properties are inserted
+# via ``syn_spec`` which expects a dictionary when defining multiple variables
+# or a string when simply using a pre-defined synapse.
+
+nsc.Connect(noise, nodes_ex, syn_spec="excitatory")
+nsc.Connect(noise, nodes_in, syn_spec="excitatory")
+
+###############################################################################
+# Connecting the first ``N_rec`` nodes of the excitatory and inhibitory
+# population to the associated spike detectors using excitatory synapses.
+# Here the same shortcut for the specification of the synapse as defined
+# above is used.
+
+nsc.Connect(nodes_ex, espikes, syn_spec="excitatory")
+nsc.Connect(nodes_in, ispikes, syn_spec="excitatory")
+
+print("Connecting network")
+
+print("Excitatory connections")
+
+###############################################################################
+# Connecting the excitatory population to all neurons using the pre-defined
+# excitatory synapse. Beforehand, the connection parameter are defined in a
+# dictionary. Here we use the connection rule ``fixed_indegree``,
+# which requires the definition of the indegree. Since the synapse
+# specification is reduced to assigning the pre-defined excitatory synapse it
+# suffices to insert a string.
+
+conn_params_ex = {'rule': 'fixed_indegree', 'indegree': CE}
+nsc.Connect(nodes_ex, nodes_ex, conn_params_ex, "excitatory")
+nsc.Connect(nodes_ex, nodes_in, conn_params_ex, "excitatory")
+
+print("Inhibitory connections")
+
+###############################################################################
+# Connecting the inhibitory population to all neurons using the pre-defined
+# inhibitory synapse. The connection parameter as well as the synapse
+# paramtere are defined analogously to the connection from the excitatory
+# population defined above.
+
+conn_params_in = {'rule': 'fixed_indegree', 'indegree': CI}
+nsc.Connect(nodes_in, nodes_ex, conn_params_in, "inhibitory")
+nsc.Connect(nodes_in, nodes_in, conn_params_in, "inhibitory")
+
+###############################################################################
+# Storage of the time point after the buildup of the network in a variable.
+
+endbuild = time.time()
+
+###############################################################################
+# Simulation of the network.
+
+print("Simulating")
+
+nsc.Simulate(simtime)
+
+###############################################################################
+# Storage of the time point after the simulation of the network in a variable.
+
+endsimulate = time.time()
+
+###############################################################################
+# Reading out the total number of spikes received from the spike detector
+# connected to the excitatory population and the inhibitory population.
+
+# events_ex = nest.GetStatus(espikes, "n_events")[0]
+# events_in = nest.GetStatus(ispikes, "n_events")[0]
+
+###############################################################################
+# Calculation of the average firing rate of the excitatory and the inhibitory
+# neurons by dividing the total number of recorded spikes by the number of
+# neurons recorded from and the simulation time. The multiplication by 1000.0
+# converts the unit 1/ms to 1/s=Hz.
+
+# rate_ex = events_ex / simtime * 1000.0 / NE
+# rate_in = events_in / simtime * 1000.0 / NI
+
+###############################################################################
+# Reading out the number of connections established using the excitatory and
+# inhibitory synapse model. The numbers are summed up resulting in the total
+# number of synapses.
+
+# num_synapses = (nest.GetDefaults("excitatory")["num_connections"] +
+                # nest.GetDefaults("inhibitory")["num_connections"])
+
+###############################################################################
+# Establishing the time it took to build and simulate the network by taking
+# the difference of the pre-defined time variables.
+
+build_time = endbuild - startbuild
+sim_time = endsimulate - endbuild
+
+###############################################################################
+# Printing the network properties, firing rates and building times.
+
+# print("Brunel network simulation (Python)")
+# print("Number of neurons : {0}".format(N_neurons))
+# print("Number of synapses: {0}".format(num_synapses))
+# print("       Exitatory  : {0}".format(int(CE * N_neurons) + N_neurons))
+# print("       Inhibitory : {0}".format(int(CI * N_neurons)))
+# print("Excitatory rate   : %.2f Hz" % rate_ex)
+# print("Inhibitory rate   : %.2f Hz" % rate_in)
+# print("Building time     : %.2f s" % build_time)
+# print("Simulation time   : %.2f s" % sim_time)
+# kernel_status = nest.GetKernelStatus()
+# pprint.pprint(kernel_status)
+
+try:
+    input("Press Enter to quit...")
+except EOFError:
+    print("Simulation finished, press ctrl+c to exit.")
+    while True:
+        time.sleep(1)
diff --git a/test/run-tests.yml b/test/run-tests.yml
new file mode 100644
index 00000000..4399bce5
--- /dev/null
+++ b/test/run-tests.yml
@@ -0,0 +1,49 @@
+version: "3"
+
+services:
+  insite-access-node:
+    build: 
+      context: ../
+      dockerfile: ./docker/Dockerfile_AccessNode
+    image:
+      insite-access-node
+    ports:
+      - "52056:52056" # depends_on:
+      - "9011:9011" # depends_on:
+    environment:
+      INSITE_NEST_BASE_URL: "insite-nest-module"
+      INSITE_ARBOR_BASE_URL: "arbor-module"
+
+  insite-nest-module:
+    build: 
+      context: ../
+      dockerfile: ./docker/examples/Dockerfile_NestExample
+    image:
+      insite-nest-example
+    ports:
+      - "18080:18080"
+      - "52425:52425"
+
+  tvb-module: 
+    build:
+      context: ../
+      dockerfile: ./docker/examples/Dockerfile_TVBExample
+    image:
+      insite-tvb-example
+    environment:
+      INSITE_ACCESS_NODE_URL: "insite-access-node"
+    depends_on:
+      - insite-access-node
+
+  arbor-module: 
+    build: 
+      context: ../
+      dockerfile: ./docker/examples/Dockerfile_ArborRingExample
+    image:
+      insite-arbor-example-ring
+  #
+  pytest:
+    build: .
+    depends_on: 
+      - insite-access-node
+
-- 
GitLab