From 33e0544eb3e16544efcb0b7693bbc09362586332 Mon Sep 17 00:00:00 2001 From: Kepa Cantero <cantero@fortiss.org> Date: Thu, 28 Feb 2019 10:00:02 +0000 Subject: [PATCH] Merged in NRRPLT-7290 (pull request #9) [NRRPLT-7290] VC integration test fails Approved-by: Manos Angelidis <angelidis@fortiss.org> Approved-by: Susie Murphy <susie.murphy@epfl.ch> --- examples/integration_test/it.py | 21 +- .../hbp_nrp_virtual_coach/simulation.py | 187 +++++++++++++----- .../tests/test_simulation.py | 28 ++- 3 files changed, 164 insertions(+), 72 deletions(-) diff --git a/examples/integration_test/it.py b/examples/integration_test/it.py index 3c7392f..b544d7c 100644 --- a/examples/integration_test/it.py +++ b/examples/integration_test/it.py @@ -217,7 +217,6 @@ def run(oidc_username, storage_username): sim.start() wait_condition(30, 'Waiting for simulation to resume.', lambda x: x['simulationTime'] > 5.0) results.done(True) - # pause the simulation results.start('Pausing Simulation') sim.pause() @@ -235,7 +234,6 @@ def run(oidc_username, storage_username): sim.start() wait_condition(5, 'Waiting for simulation to resume.', lambda x: x['state'] == 'started') results.done(True) - ## ## Transfer Function Interaction ## @@ -324,7 +322,6 @@ def tf(t): if tfs != sim._Simulation__get_simulation_scripts('transfer-function')['data']: raise TestCaseError('Deleting a nonexistent TF deleted something else.') results.done(True) - ## ## State Machine Interaction ## @@ -338,8 +335,7 @@ def tf(t): if len(sim._Simulation__get_simulation_scripts('state-machine')['data']) != len(sms) + 1: raise TestCaseError('Failed to add a new valid state machine') results.done(True) - - # valid state machine editing + # valid state machine editing results.start('Valid State Machine Update') sim.start() valid_sm = 'import math\n' + sim.get_state_machine('statemachine_valid') @@ -381,28 +377,22 @@ def tf(t): # modify populations results.start('Modifying Neural Populations') sim.start() + populations = sim.get_populations() - populations['RECORD'] = populations['record'] - del populations['record'] + populations['record_test'] = populations['record'] sim.edit_populations(populations) - if 'RECORD' not in sim.get_populations().keys(): - raise TestCaseError('Population name update did not succeed.') - if sim.get_state() != 'started': - raise TestCaseError('Population name update did not resume running simulation.') populations = sim.get_populations() - populations['RECORD']['step'] = 2 - sim.edit_populations(populations) - if sim.get_populations()['RECORD']['step'] != 2: + if not populations['record_test']: raise TestCaseError('Population Step size update did not succeed.') if sim.get_state() != 'started': raise TestCaseError('Population step update did not resume running simulation.') + results.done(True) ## ## Reset Different Simulation Parts ## - results.start('Resetting full simulation') sim.reset('full') wait_condition(5, 'Waiting for simulation to pause.', lambda x: x['state'] == 'paused') @@ -420,7 +410,6 @@ def tf(t): wait_condition(5, 'Waiting for simulation to pause.', lambda x: x['state'] == 'paused') sim.start() results.done(True) - ## ## Shutdown, Cleanup and Delete the cloned experiment used in the tests ## diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py index 0eeb441..ab56251 100644 --- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py +++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py @@ -88,10 +88,12 @@ class Simulation(object): # do not allow reuse of this instance if a simulation has been launched if self.__server: - raise Exception('Invalid call to launch on already created simulation!') + raise Exception( + 'Invalid call to launch on already created simulation!') # print information about the specific server/reservation - self.__logger.info('Attempting to launch %s on %s.', experiment_id, server) + self.__logger.info('Attempting to launch %s on %s.', + experiment_id, server) if reservation is not None: self.__logger.info('Using supplied reservation: %s', reservation) @@ -99,7 +101,8 @@ class Simulation(object): try: # get the information for the server - this provides urls for endpoints - server_info_url = '%s/%s' % (self.__config['proxy-services']['server-info'], server) + server_info_url = '%s/%s' % ( + self.__config['proxy-services']['server-info'], server) _, server_json = self.__http_client.get(server_info_url) self.__server_info = json.loads(server_json) @@ -118,10 +121,12 @@ class Simulation(object): # check to see if the launch was successful, any other failure return codes # such as 404 will trigger an exception by the OIDCClient itself if status_code == httplib.CONFLICT: - raise Exception('Simulation server is launching another experiment.') + raise Exception( + 'Simulation server is launching another experiment.') elif status_code != httplib.CREATED: - raise Exception("Simulation responded with HTTP status %s" % status_code) + raise Exception( + "Simulation responded with HTTP status %s" % status_code) # retrieve and store the simulation information self.__sim_info = json.loads(sim_json) @@ -154,7 +159,8 @@ class Simulation(object): CLEError, self.__on_error) except ImportError: - self.__logger.warn('ROS is not installed, some functionality will be disabled.') + self.__logger.warn( + 'ROS is not installed, some functionality will be disabled.') # store server information, experiment_id and the endpoint url for this server/simulation id self.__server = server @@ -200,15 +206,18 @@ class Simulation(object): # ensure the simulation is started and valid if not self.__server or not self.__sim_url: - raise Exception("Simulation has not been created, cannot set state!") + raise Exception( + "Simulation has not been created, cannot set state!") # make sure ROS is enabled (this is only a valid check after simulation is created) if not self.__status_sub: - raise Exception("Unable to register status callback, ROS is not available.") + raise Exception( + "Unable to register status callback, ROS is not available.") # make sure the callback is not registered already, warn the user if it is if callback in self.__status_callbacks: - self.__logger.warn('Attempting to register duplicate status callback, ignoring.') + self.__logger.warn( + 'Attempting to register duplicate status callback, ignoring.') return # register the callback @@ -229,16 +238,19 @@ class Simulation(object): # ensure the simulation is started and valid if not self.__server or not self.__sim_url: - raise Exception("Simulation has not been created, cannot set state!") + raise Exception( + "Simulation has not been created, cannot set state!") # attempt to transition the state self.__logger.info('Attempting to transition to state: %s', state) - url = '%s/%s' % (self.__sim_url, self.__config['simulation-services']['state']) + url = '%s/%s' % (self.__sim_url, + self.__config['simulation-services']['state']) status_code, _ = self.__http_client.put(url, body={'state': state}) # check the return code, this will return OK if the REST call succeeds if status_code != httplib.OK: - raise Exception("Unable to set simulation state, HTTP status %s" % status_code) + raise Exception( + "Unable to set simulation state, HTTP status %s" % status_code) self.__logger.info('Simulation state: %s', state) def __on_error(self, msg): @@ -278,7 +290,8 @@ class Simulation(object): return self.__previous_subtask = status['progress']['subtask'] - self.__logger.info('[%s] %s', status['progress']['task'], status['progress']['subtask']) + self.__logger.info('[%s] %s', status['progress'] + ['task'], status['progress']['subtask']) # an actual simulation status / info message, check if we have stopped and forward it along else: @@ -312,9 +325,11 @@ class Simulation(object): Returns the current simulation state. """ if not self.__server or not self.__sim_url: - raise Exception("Simulation has not been created, cannot get simulation state!") + raise Exception( + "Simulation has not been created, cannot get simulation state!") - url = '%s/%s' % (self.__sim_url, self.__config['simulation-services']['state']) + url = '%s/%s' % (self.__sim_url, + self.__config['simulation-services']['state']) status_code, content = self.__http_client.get(url) if status_code != httplib.OK: @@ -334,7 +349,8 @@ class Simulation(object): assert isinstance(script_type, (str, unicode)) if not self.__server or not self.__sim_url: - raise Exception("Simulation has not been created, cannot get %s!" % script_type) + raise Exception( + "Simulation has not been created, cannot get %s!" % script_type) if script_type not in self.__config['simulation-scripts']: raise ValueError("Script type %s not defined. Possible values are: %s" @@ -342,7 +358,8 @@ class Simulation(object): self.__logger.info("Attempting to retrieve %s" % script_type) - url = '%s/%s' % (self.__sim_url, self.__config['simulation-scripts'][script_type]) + url = '%s/%s' % (self.__sim_url, + self.__config['simulation-scripts'][script_type]) status_code, content = self.__http_client.get(url) @@ -396,11 +413,13 @@ class Simulation(object): script_type_display = ' '.join(script_type.split('-')).title() if not self.__server or not self.__sim_url: - raise Exception("Simulation has not been created, cannot set %s!" % script_type_display) + raise Exception( + "Simulation has not been created, cannot set %s!" % script_type_display) defined_scripts = self.__get_simulation_scripts(script_type)['data'] - self.__logger.info('Attempting to set %s %s' % (script_type_display, script_name)) + self.__logger.info('Attempting to set %s %s' % + (script_type_display, script_name)) url = '%s/%s/%s' % (self.__sim_url, self.__config['simulation-scripts'][script_type], script_name) started = False @@ -432,12 +451,15 @@ class Simulation(object): if status_code != httplib.OK: raise Exception("Unable to set %s, HTTP status %s" % (script_type_display, status_code)) - self.__logger.info("%s '%s' successfully updated" % (script_type_display, script_name)) + self.__logger.info("%s '%s' successfully updated" % + (script_type_display, script_name)) except HTTPError as err: self.__logger.info(err) if not new: - self.__logger.info('Attempting to restore the old %s.' % script_type_display) - status_code, _ = self.__http_client.put(url, body=script_original) + self.__logger.info( + 'Attempting to restore the old %s.' % script_type_display) + status_code, _ = self.__http_client.put( + url, body=script_original) if status_code != httplib.OK: raise Exception("Unable to restore %s, HTTP status %s" % (script_type_display, status_code)) @@ -460,7 +482,8 @@ class Simulation(object): script_type_display = ' '.join(script_type.split('-')).title() if not self.__server or not self.__sim_url: - raise Exception("Simulation has not been created, cannot set %s!" % script_type_display) + raise Exception( + "Simulation has not been created, cannot set %s!" % script_type_display) script_list = self.__get_simulation_scripts(script_type)['data'] @@ -469,7 +492,8 @@ class Simulation(object): % (script_type_display, script_name, script_type_display, str('\n'.join(script_list)))) - self.__logger.info('Attempting to delete %s %s' % (script_type_display, script_name)) + self.__logger.info('Attempting to delete %s %s' % + (script_type_display, script_name)) url = '%s/%s/%s' % (self.__sim_url, self.__config['simulation-scripts'][script_type], script_name) status_code = self.__http_client.delete(url, body=script_name) @@ -477,19 +501,22 @@ class Simulation(object): if status_code != httplib.OK: raise Exception("Unable to delete %s, HTTP status %s" % (script_type_display, status_code)) - self.__logger.info("%s '%s' successfully deleted" % (script_type_display, script_name)) + self.__logger.info("%s '%s' successfully deleted" % + (script_type_display, script_name)) def print_transfer_functions(self): """ Prints a list of the transfer-function names defined in the experiment. """ - print "\n".join(self.__get_simulation_scripts('transfer-function')['data'].keys()) + print "\n".join(self.__get_simulation_scripts( + 'transfer-function')['data'].keys()) def print_state_machines(self): """ Prints a list of the state-machine names defined in the experiment. """ - print "\n".join(self.__get_simulation_scripts('state-machine')['data'].keys()) + print "\n".join(self.__get_simulation_scripts( + 'state-machine')['data'].keys()) def get_transfer_function(self, transfer_function_name): """ @@ -556,7 +583,8 @@ class Simulation(object): """ Saves the current transfer functions to the storage """ - transfer_functions = self.__get_simulation_scripts('transfer-function')['data'].keys() + transfer_functions = self.__get_simulation_scripts( + 'transfer-function')['data'].keys() transfer_functions_list = [] for tf in transfer_functions: transfer_functions_list.append(self.get_transfer_function(str(tf))) @@ -615,7 +643,8 @@ class Simulation(object): :param state_machine_name: A string containing the name of the state machine to be edited. :param state_machine: A string containing the new state machine code. """ - self.__set_script('state-machine', state_machine, script_name=state_machine_name) + self.__set_script('state-machine', state_machine, + script_name=state_machine_name) def add_state_machine(self, state_machine_name, state_machine): """ @@ -624,7 +653,8 @@ class Simulation(object): :param state_machine_name: A string containing the name of the state machine to be added. :param state_machine: A string containing the new state machine code. """ - self.__set_script('state-machine', state_machine, script_name=state_machine_name, new=True) + self.__set_script('state-machine', state_machine, + script_name=state_machine_name, new=True) def delete_state_machine(self, state_machine_name): """ @@ -634,11 +664,10 @@ class Simulation(object): """ self.__delete_script('state-machine', state_machine_name) - def __set_brain(self, brain_script, neural_population, change_population): + def __set_populations(self, neural_population, change_population): """ Internal helper method to help set either the brain script or the neural populations. - :param brain_script: A string containing a python script that defines a pyNN neural network. :param neural_population: A dictionary containing neuron indices and is indexed by population names. Neuron indices could be defined by individual integers, lists of integers or python slices. @@ -649,51 +678,110 @@ class Simulation(object): 1 (permission granted) replace old name with a new one; 2 proceed with no replace action. """ + assert isinstance(neural_population, dict) + assert isinstance(change_population, int) + + if not self.__server or not self.__sim_url: + raise Exception( + "Simulation has not been created, cannot set populations!") + + self.__logger.info('Attempting to set Populations') + + # keep reference to the old script body in case of syntax errors + old_populations = self.get_populations() + + # Get old brain parameters to reuse them + old_data_type = self.__get_simulation_scripts('brain')['data_type'] + old_brain_type = self.__get_simulation_scripts('brain')['brain_type'] + body = {'brain_type': old_brain_type, 'brain_populations': neural_population, + 'data_type': old_data_type, 'change_population': change_population} + + url = '%s/%s' % (self.__sim_url, + self.__config['simulation-scripts']['populations']) + started = False + + # check for current simulation state and pause simulation if it's running + if self.get_state() == 'started': + started = True + self.pause() + + try: + status_code, _ = self.__http_client.put(url, body=body) + if status_code != httplib.OK: + raise Exception( + "Unable to set Populations, HTTP status %s" % status_code) + self.__logger.info("Populations successfully updated.") + except HTTPError as err: + self.__logger.info(err) + self.__logger.info('Attempting to restore the old Populations.') + body['brain_populations'] = old_populations + body['data_type'] = old_data_type + body['brain_type'] = old_brain_type + + status_code, _ = self.__http_client.put(url, body=body) + if status_code != httplib.OK: + raise Exception( + "Unable to restore Brain, HTTP status %s" % status_code) + + raise Exception("Error detected. The Simulation is now paused and the old script is " + "restored.") + # resume simulation if it was initially running + if started: + self.start() + + def __set_brain(self, brain_script, neural_population): + """ + Internal helper method to help set either the brain script or the neural populations. + + :param brain_script: A string containing a python script that defines a pyNN neural network. + :param neural_population: A dictionary containing neuron indices and is indexed by + population names. Neuron indices could be defined by individual + integers, lists of integers or python slices. + """ assert isinstance(brain_script, (str, unicode)) assert isinstance(neural_population, dict) - assert isinstance(change_population, int) if not self.__server or not self.__sim_url: - raise Exception("Simulation has not been created, cannot set Brain!") + raise Exception( + "Simulation has not been created, cannot set Brain!") self.__logger.info('Attempting to set Brain') # keep reference to the old script body in case of syntax errors old_brain = self.get_brain() - old_populations = self.get_populations() # Get old brain parameters to reuse them old_data_type = self.__get_simulation_scripts('brain')['data_type'] old_brain_type = self.__get_simulation_scripts('brain')['brain_type'] - + old_populations = self.get_populations() # change_population is set to 2, which means proceed with no replace_population action - body = {'data': brain_script, 'data_type': old_data_type, 'brain_type': old_brain_type, - 'brain_populations': neural_population, 'change_population': change_population} - - url = '%s/%s' % (self.__sim_url, self.__config['simulation-scripts']['brain']) + body = {'data': brain_script, 'data_type': old_data_type, + 'brain_type': old_brain_type, 'brain_populations': neural_population} + url = '%s/%s' % (self.__sim_url, + self.__config['simulation-scripts']['brain']) started = False - # check for current simulation state and pause simulation if it's running if self.get_state() == 'started': started = True self.pause() - + # check for current simulation state and pause simulation if it's running try: status_code, _ = self.__http_client.put(url, body=body) if status_code != httplib.OK: - raise Exception("Unable to set Brain, HTTP status %s" % status_code) + raise Exception( + "Unable to set Brain, HTTP status %s" % status_code) self.__logger.info("Brain successfully updated.") except HTTPError as err: self.__logger.info(err) self.__logger.info('Attempting to restore the old Brain.') body['data'] = old_brain - body['brain_populations'] = old_populations - + body['populations'] = old_populations status_code, _ = self.__http_client.put(url, body=body) if status_code != httplib.OK: - raise Exception("Unable to restore Brain, HTTP status %s" % status_code) + raise Exception( + "Unable to restore Brain, HTTP status %s" % status_code) raise Exception("Error detected. The Simulation is now paused and the old script is " "restored.") @@ -715,7 +803,7 @@ class Simulation(object): :param brain_script: A string containing the new pyNN script. """ - self.__set_brain(brain_script, self.get_populations(), 2) + self.__set_brain(brain_script, self.get_populations()) def get_populations(self): """ @@ -735,7 +823,7 @@ class Simulation(object): containing the 'from', 'to' and 'step' values. """ assert isinstance(populations, dict) - self.__set_brain(self.get_brain(), populations, 1) + self.__set_populations(populations, False) def reset(self, reset_type): """ @@ -765,6 +853,7 @@ class Simulation(object): # check the return code, this will return OK if the REST call succeeds if status_code != httplib.OK: self.start() - raise Exception("Unable to reset simulation, HTTP status %s" % status_code) + raise Exception( + "Unable to reset simulation, HTTP status %s" % status_code) self.__logger.info('Reset completed. The simulation has been paused and will not be started' ' automatically.') diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py index f21f6af..f51d830 100644 --- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py +++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py @@ -322,11 +322,9 @@ class TestSimulation(unittest.TestCase): self.assertRaises(Exception, self._sim.edit_transfer_function, 'foo', u'foo') def test_set_brain_asserts(self): - self.assertRaises(AssertionError, self._sim._Simulation__set_brain, 1, {'foo': 'bar'}, 1) - self.assertRaises(AssertionError, self._sim._Simulation__set_brain, u'foo', 1, 1) - self.assertRaises(AssertionError, self._sim._Simulation__set_brain, 'foo', {'f': 'b'}, None) + self.assertRaises(AssertionError, self._sim._Simulation__set_brain, 1, {'foo': 'bar'} ) - def test_edit_brain_and_populations(self): + def test_edit_brain(self): # mock the http call, the get_brain call, the get_populations call, the start call, # the pause call, the __get_simulation_scripts call and the get_state call self._sim.get_brain = Mock() @@ -368,9 +366,25 @@ class TestSimulation(unittest.TestCase): (httplib.OK, None)] self.assertRaises(Exception, self._sim.edit_brain, 'foo') - self._sim._Simulation__set_brain = Mock() - self._sim.edit_populations({}) - self._sim._Simulation__set_brain.assert_called_once() + def test_edit_populations(self): + self._sim._Simulation__server = 'server' + self._sim._Simulation__sim_url = 'http://url/populations' + self._sim.get_brain = Mock(return_value='brain_script') + self._sim.get_populations = Mock(return_value={ + 'data': {'foo': "foo_script_name"}}) + self._sim.get_state = Mock(return_value='started') + self._sim.pause = Mock() + self._sim.start = Mock() + + self._sim._Simulation__get_simulation_scripts = Mock(return_value={'data_type': 'foo', + 'brain_type': 'foo', + 'brain_populations': 'bar'}) + self._sim._Simulation__http_client.put = Mock(return_value=(httplib.OK, + {})) + + self._sim.edit_populations({'foo': 'bar'}) + self._sim.start.assert_called_once() + self._sim.pause.assert_called_once() def test_get_brain_and_populations(self): self._sim._Simulation__get_simulation_scripts = Mock() -- GitLab