def test_coalesce_widget_states(self): old_states = WidgetStates() _create_widget("old_set_trigger", old_states).trigger_value = True _create_widget("old_unset_trigger", old_states).trigger_value = False _create_widget("missing_in_new", old_states).int_value = 123 _create_widget("shape_changing_trigger", old_states).trigger_value = True new_states = WidgetStates() _create_widget("old_set_trigger", new_states).trigger_value = False _create_widget("new_set_trigger", new_states).trigger_value = True _create_widget("added_in_new", new_states).int_value = 456 _create_widget("shape_changing_trigger", new_states).int_value = 3 widgets = Widgets() widgets.set_state(coalesce_widget_states(old_states, new_states)) self.assertIsNone(widgets.get_widget_value("old_unset_trigger")) self.assertIsNone(widgets.get_widget_value("missing_in_new")) self.assertEqual(True, widgets.get_widget_value("old_set_trigger")) self.assertEqual(True, widgets.get_widget_value("new_set_trigger")) self.assertEqual(456, widgets.get_widget_value("added_in_new")) # Widgets that were triggers before, but no longer are, will *not* # be coalesced self.assertEqual(3, widgets.get_widget_value("shape_changing_trigger"))
def __init__(self, script_name): """Initializes the ScriptRunner for the given script_name""" # DeltaGenerator deltas will be enqueued into self.report_queue. self.report_queue = ReportQueue() def enqueue_fn(msg): self.report_queue.enqueue(msg) self.maybe_handle_execution_control_request() self.script_request_queue = ScriptRequestQueue() script_path = os.path.join(os.path.dirname(__file__), "test_data", script_name) super(TestScriptRunner, self).__init__( session_id="test session id", report=Report(script_path, "test command line"), enqueue_forward_msg=enqueue_fn, widget_states=WidgetStates(), request_queue=self.script_request_queue, ) # Accumulates uncaught exceptions thrown by our run thread. self.script_thread_exceptions = [] # Accumulates all ScriptRunnerEvents emitted by us. self.events = [] def record_event(event, **kwargs): self.events.append(event) self.on_event.connect(record_event, weak=False)
def test_widgets(self): """Tests that widget values behave as expected.""" scriptrunner = TestScriptRunner("widgets_script.py") scriptrunner.enqueue_rerun() scriptrunner.start() # Default widget values time.sleep(0.1) self._assert_text_deltas( scriptrunner, ["False", "ahoy!", "0", "False", "loop_forever"]) # Update widgets states = WidgetStates() _create_widget("checkbox-checkbox", states).bool_value = True _create_widget("text_area-text_area", states).string_value = "matey!" _create_widget("radio-radio", states).int_value = 2 _create_widget("button-button", states).trigger_value = True scriptrunner.enqueue_rerun(widget_state=states) time.sleep(0.1) self._assert_text_deltas( scriptrunner, ["True", "matey!", "2", "True", "loop_forever"]) # Rerun with previous values. Our button should be reset; # everything else should be the same. scriptrunner.enqueue_rerun() time.sleep(0.1) self._assert_text_deltas( scriptrunner, ["True", "matey!", "2", "False", "loop_forever"]) scriptrunner.enqueue_shutdown() scriptrunner.join() self._assert_no_exceptions(scriptrunner)
def __init__(self, script_name): """Initializes the ScriptRunner for the given script_name""" # DeltaGenerator deltas will be enqueued into self.report_queue. self.report_queue = ReportQueue() def enqueue_fn(msg): self.report_queue.enqueue(msg) self.maybe_handle_execution_control_request() return True self.main_dg = DeltaGenerator(enqueue_fn, container=BlockPath.MAIN) self.sidebar_dg = DeltaGenerator(enqueue_fn, container=BlockPath.SIDEBAR) self.script_request_queue = ScriptRequestQueue() script_path = os.path.join(os.path.dirname(__file__), 'test_data', script_name) super(TestScriptRunner, self).__init__(report=Report(script_path, []), main_dg=self.main_dg, sidebar_dg=self.sidebar_dg, widget_states=WidgetStates(), request_queue=self.script_request_queue) # Accumulates uncaught exceptions thrown by our run thread. self.script_thread_exceptions = [] # Accumulates all ScriptRunnerEvents emitted by us. self.events = [] def record_event(event, **kwargs): self.events.append(event) self.on_event.connect(record_event, weak=False)
def test_widgets(self): """Tests that widget values behave as expected.""" scriptrunner = TestScriptRunner('widgets_script.py') scriptrunner.enqueue_rerun() scriptrunner.start() # Default widget values time.sleep(0.1) self._assert_text_deltas( scriptrunner, ['False', 'ahoy!', '0', 'False', 'loop_forever']) # Update widgets states = WidgetStates() _create_widget('checkbox-checkbox', states).bool_value = True _create_widget('text_area-text_area', states).string_value = 'matey!' _create_widget('radio-radio', states).int_value = 2 _create_widget('button-button', states).trigger_value = True scriptrunner.enqueue_rerun(widget_state=states) time.sleep(0.1) self._assert_text_deltas( scriptrunner, ['True', 'matey!', '2', 'True', 'loop_forever']) # Rerun with previous values. Our button should be reset; # everything else should be the same. scriptrunner.enqueue_rerun() time.sleep(0.1) self._assert_text_deltas( scriptrunner, ['True', 'matey!', '2', 'False', 'loop_forever']) scriptrunner.enqueue_shutdown() scriptrunner.join() self._assert_no_exceptions(scriptrunner)
def test_multiple_scriptrunners(self): """Tests that multiple scriptrunners can run simultaneously.""" # This scriptrunner will run in parallel to the other 3. # It's used to retrieve the widget id before initializing deltas on other runners. # Wait a beat to access deltas. scriptrunner = TestScriptRunner("widgets_script.py") scriptrunner.enqueue_rerun() scriptrunner.start() time.sleep(0.1) # Build several runners. Each will set a different int value for # its radio button. runners = [] for ii in range(3): runner = TestScriptRunner("widgets_script.py") runners.append(runner) states = WidgetStates() wid = scriptrunner.get_widget_id("radio", "radio") _create_widget(wid, states).int_value = ii runner.enqueue_rerun(widget_state=states) # Start the runners and wait a beat. for runner in runners: runner.start() time.sleep(0.1) # Ensure that each runner's radio value is as expected. for ii, runner in enumerate(runners): self._assert_text_deltas( runner, ["False", "ahoy!", "%s" % ii, "False", "loop_forever"] ) runner.enqueue_shutdown() time.sleep(0.1) # Shut 'em all down! for runner in runners: runner.join() for runner in runners: self._assert_no_exceptions(runner) self._assert_events( runner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN, ], ) scriptrunner.enqueue_shutdown() scriptrunner.join() self._assert_no_exceptions(scriptrunner)
def get_state(self): """ Returns ------- WidgetStates A new WidgetStates protobuf containing the contents of our widget state dictionary. """ states = WidgetStates() states.widgets.extend(self._state.values()) return states
def __init__( self, is_preheat, ioloop, script_path, command_line, uploaded_file_manager ): """Initialize the ReportSession. Parameters ---------- is_preheat : bool True if this is the "preheated" session. ioloop : tornado.ioloop.IOLoop The Tornado IOLoop that we're running within. script_path : str Path of the Python file from which this report is generated. command_line : str Command line as input by the user. uploaded_file_manager : UploadedFileManager The server's UploadedFileManager. """ # Each ReportSession has a unique string ID. if is_preheat: self.id = PREHEATED_ID else: self.id = str(uuid.uuid4()) self._ioloop = ioloop self._report = Report(script_path, command_line) self._uploaded_file_mgr = uploaded_file_manager self._state = ReportSessionState.REPORT_NOT_RUNNING self._widget_states = WidgetStates() self._local_sources_watcher = LocalSourcesWatcher( self._report, self._on_source_file_changed ) self._sent_initialize_message = False self._storage = None self._maybe_reuse_previous_run = False self._run_on_save = config.get_option("server.runOnSave") # The ScriptRequestQueue is the means by which we communicate # with the active ScriptRunner. self._script_request_queue = ScriptRequestQueue() self._scriptrunner = None LOGGER.debug("ReportSession initialized (id=%s)", self.id)
def test_reset_triggers(self): states = WidgetStates() widgets = Widgets() _create_widget("trigger", states).trigger_value = True _create_widget("int", states).int_value = 123 widgets.set_state(states) self.assertEqual(True, widgets.get_widget_value("trigger")) self.assertEqual(123, widgets.get_widget_value("int")) widgets.reset_triggers() self.assertEqual(None, widgets.get_widget_value("trigger")) self.assertEqual(123, widgets.get_widget_value("int"))
def __init__(self, ioloop, script_path, command_line): """Initialize the ReportSession. Parameters ---------- ioloop : tornado.ioloop.IOLoop The Tornado IOLoop that we're running within. script_path : str Path of the Python file from which this report is generated. command_line : str Command line as input by the user. """ # Each ReportSession gets a unique ID self.id = ReportSession._next_id ReportSession._next_id += 1 self._ioloop = ioloop self._report = Report(script_path, command_line) self._state = ReportSessionState.REPORT_NOT_RUNNING self._uploaded_file_mgr = UploadedFileManager() self._main_dg = DeltaGenerator(enqueue=self.enqueue, container=BlockPath.MAIN) self._sidebar_dg = DeltaGenerator( enqueue=self.enqueue, container=BlockPath.SIDEBAR ) self._widget_states = WidgetStates() self._local_sources_watcher = LocalSourcesWatcher( self._report, self._on_source_file_changed ) self._sent_initialize_message = False self._storage = None self._maybe_reuse_previous_run = False self._run_on_save = config.get_option("server.runOnSave") # The ScriptRequestQueue is the means by which we communicate # with the active ScriptRunner. self._script_request_queue = ScriptRequestQueue() self._scriptrunner = None LOGGER.debug("ReportSession initialized (id=%s)", self.id)
def test_values(self): states = WidgetStates() _create_widget("trigger", states).trigger_value = True _create_widget("bool", states).bool_value = True _create_widget("float", states).float_value = 0.5 _create_widget("int", states).int_value = 123 _create_widget("string", states).string_value = "howdy!" widgets = Widgets() widgets.set_state(states) self.assertEqual(True, widgets.get_widget_value("trigger")) self.assertEqual(True, widgets.get_widget_value("bool")) self.assertAlmostEqual(0.5, widgets.get_widget_value("float")) self.assertEqual(123, widgets.get_widget_value("int")) self.assertEqual("howdy!", widgets.get_widget_value("string"))
def test_values(self): states = WidgetStates() _create_widget('trigger', states).trigger_value = True _create_widget('bool', states).bool_value = True _create_widget('float', states).float_value = 0.5 _create_widget('int', states).int_value = 123 _create_widget('string', states).string_value = 'howdy!' widgets = Widgets() widgets.set_state(states) self.assertEqual(True, widgets.get_widget_value('trigger')) self.assertEqual(True, widgets.get_widget_value('bool')) self.assertAlmostEqual(0.5, widgets.get_widget_value('float')) self.assertEqual(123, widgets.get_widget_value('int')) self.assertEqual('howdy!', widgets.get_widget_value('string'))
def coalesce_widget_states(old_states, new_states): """Coalesces an older WidgetStates into a newer one, and returns a new WidgetState containing the result. For most widget values, we just take the latest version. However, any trigger_values (the state set by buttons) that are True in the older WidgetStates will be set to True in the coalesced result, so that button presses don't go missing. Parameters ---------- old_states : WidgetStates The older WidgetStates protobuf new_states : WidgetStates The newer WidgetStates protobuf Returns ------- WidgetStates The resulting coalesced protobuf """ states_by_id = {} for new_state in new_states.widgets: states_by_id[new_state.id] = new_state for old_state in old_states.widgets: if old_state.WhichOneof( "value") == "trigger_value" and old_state.trigger_value: # Ensure the corresponding new_state is also a trigger; # otherwise, a widget that was previously a button but no longer is # could get a bad value. new_state = states_by_id.get(old_state.id) if new_state and new_state.WhichOneof("value") == "trigger_value": states_by_id[old_state.id] = old_state coalesced = WidgetStates() coalesced.widgets.extend(states_by_id.values()) return coalesced
def test_multiple_scriptrunners(self): """Tests that multiple scriptrunners can run simultaneously.""" # Build several runners. Each will set a different int value for # its radio button. runners = [] for ii in range(3): runner = TestScriptRunner('widgets_script.py') runners.append(runner) states = WidgetStates() _create_widget('radio-radio', states).int_value = ii runner.enqueue_rerun(widget_state=states) # Start the runners and wait a beat. for runner in runners: runner.start() time.sleep(0.1) # Ensure that each runner's radio value is as expected. for ii, runner in enumerate(runners): self._assert_text_deltas( runner, ['False', 'ahoy!', '%s' % ii, 'False', 'loop_forever']) runner.enqueue_shutdown() time.sleep(0.1) # Shut 'em all down! for runner in runners: runner.join() for runner in runners: self._assert_no_exceptions(runner) self._assert_events(runner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN ])
def test_rerun_data_coalescing(self): """Test that multiple RERUN requests get coalesced with expected values. (This is similar to widgets_test.test_coalesce_widget_states - it's testing the same thing, but through the ScriptEventQueue interface.) """ queue = ScriptRequestQueue() states = WidgetStates() _create_widget('trigger', states).trigger_value = True _create_widget('int', states).int_value = 123 queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=states)) states = WidgetStates() _create_widget('trigger', states).trigger_value = False _create_widget('int', states).int_value = 456 queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=states)) event, data = queue.dequeue() self.assertEqual(event, ScriptRequest.RERUN) widgets = Widgets() widgets.set_state(data.widget_state) # Coalesced triggers should be True if either the old or # new value was True self.assertEqual(True, widgets.get_widget_value('trigger')) # Other widgets should have their newest value self.assertEqual(456, widgets.get_widget_value('int')) # We should have no more events self.assertEqual((None, None), queue.dequeue(), 'Expected empty event queue') # Test that we can coalesce if previous widget state is None queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=None)) queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=None)) states = WidgetStates() _create_widget('int', states).int_value = 789 queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=states)) event, data = queue.dequeue() widgets = Widgets() widgets.set_state(data.widget_state) self.assertEqual(event, ScriptRequest.RERUN) self.assertEqual(789, widgets.get_widget_value('int')) # We should have no more events self.assertEqual((None, None), queue.dequeue(), 'Expected empty event queue') # Test that we can coalesce if our *new* widget state is None states = WidgetStates() _create_widget('int', states).int_value = 101112 queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=states)) queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=None)) event, data = queue.dequeue() widgets = Widgets() widgets.set_state(data.widget_state) self.assertEqual(event, ScriptRequest.RERUN) self.assertEqual(101112, widgets.get_widget_value('int')) # We should have no more events self.assertEqual((None, None), queue.dequeue(), 'Expected empty event queue')