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