"""
test objects
* main
* components
* control
* monitor
* plot
* control_panel
test types
* user interaction
* flooding
"""
from copy import copy
import pdb
import pytest
from time import sleep, time
from pytestqt import qt_compat
from pytestqt.qt_compat import qt_api
# mock before importing
from pvp import gui
from pvp.gui import styles
from pvp.gui import widgets
from pvp.common import message, values
from pvp.common.values import ValueName
from pvp.coordinator.coordinator import get_coordinator
from pvp.alarm import AlarmType, AlarmSeverity
# from pvp.common import prefs
# prefs.set_pref('ENABLE_DIALOGS', False)
from PySide2 import QtCore, QtGui, QtWidgets
import numpy as np
##################################
# turn off gui limiting
gui.limit_gui(False)
n_samples = 100
decimals = 5
global_minmax_range = (0, 100)
global_safe_range = (25, 75)
[docs]@pytest.fixture
def generic_minmax():
"""
Make a (min, max) range of values that has some safe space in between.
ie. min can be between 0 and 0.25*(minmax_range[1]-minmax_range[0])
and max can similarly be above the 75% mark of the range
Returns:
"""
def _generic_minmax():
abs_min = np.random.rand() * (global_minmax_range[1]-global_minmax_range[0]) * 0.25 + \
global_minmax_range[0]
abs_max = np.random.rand() * (global_minmax_range[1]-global_minmax_range[0]) * 0.25 + \
global_minmax_range[0] + (global_minmax_range[1]-global_minmax_range[0]) * 0.75
abs_min, abs_max = np.round(abs_min, decimals), np.round(abs_max, decimals)
return abs_min, abs_max
return _generic_minmax
[docs]@pytest.fixture
def generic_saferange():
"""
Make a (min, max) range of values that has some safe space in between.
ie. min can be between 0 and 0.25*(minmax_range[1]-minmax_range[0])
and max can similarly be above the 75% mark of the range
Returns:
"""
def _generic_saferange():
abs_min = np.random.rand() * (global_safe_range[1]-global_safe_range[0]) * 0.25 + \
global_safe_range[0]
abs_max = np.random.rand() * (global_safe_range[1]-global_safe_range[0]) * 0.25 + \
global_safe_range[0] + (global_safe_range[1]-global_safe_range[0]) * 0.75
abs_min, abs_max = np.round(abs_min, decimals), np.round(abs_max, decimals)
return abs_min, abs_max
return _generic_saferange
[docs]@pytest.fixture()
def spawn_gui(qtbot):
assert qt_api.QApplication.instance() is not None
app = qt_api.QApplication.instance()
app.setStyle('Fusion')
app.setStyleSheet(styles.DARK_THEME)
app = styles.set_dark_palette(app)
coordinator = get_coordinator(sim_mode=True, single_process=False)
vent_gui = gui.PVP_Gui(coordinator, set_defaults=True)
# vent_gui.init_controls()
#app, vent_gui = launch_gui(coordinator)
qtbot.addWidget(vent_gui)
vent_gui.init_controls()
return app, vent_gui
[docs]@pytest.fixture
def fake_sensors():
def _fake_sensor(arg=None):
# make an empty SensorValues
vals = {k:0 for k in ValueName}
vals.update({k:0 for k in message.SensorValues.additional_values})
# since 0 is out of range for fio2, manually set it
# FIXME: find values that by definition don't raise any of the default rules
vals[ValueName.FIO2] = 80
# update with any in kwargs
if arg:
for k, v in arg.items():
vals[k] = v
sensors = message.SensorValues(vals=vals)
return sensors
return _fake_sensor
[docs]def test_gui_launch(qtbot):
app = qt_api.QApplication.instance()
app.setStyle('Fusion')
app.setStyleSheet(styles.DARK_THEME)
app = styles.set_dark_palette(app)
coordinator = get_coordinator(sim_mode=True, single_process=False)
vent_gui = gui.PVP_Gui(coordinator, set_defaults=False)
qtbot.addWidget(vent_gui)
# try to launch without setting default controls
vent_gui.control_panel.start_button.click()
assert not vent_gui.running
assert not vent_gui.coordinator.is_running()
# now set defaults and try again
vent_gui.init_controls()
vent_gui.control_panel.start_button.click()
# wait for a second to let the simulation spin up and start spitting values
qtbot.wait(2000)
assert vent_gui.isVisible()
assert vent_gui.running
assert vent_gui.coordinator.is_running()
vent_gui.control_panel.start_button.click()
assert not vent_gui.running
assert not vent_gui.coordinator.is_running()
################################
# test user interaction
#
# @pytest.mark.parametrize("test_value", [(k, v) for k, v in values.CONTROL.items() if k == values.ValueName.PIP] )
[docs]@pytest.mark.parametrize("test_value", [(k, v) for k, v in values.DISPLAY_CONTROL.items() if k!=values.ValueName.IE_RATIO])
def test_gui_controls(qtbot, spawn_gui, test_value):
"""
test setting controls in all the ways available to the GUI
from the :class:`~pvp.gui.widgets.control.Control` widget:
* :class:`~pvp.gui.widgets.components.EditableLabel` - setting label text
* setting slider value
* using :meth:`~pvp.gui.main.PVP_Gui.set_value`
Args:
qtbot:
spawn_gui:
test_value:
"""
value_name = test_value[0]
value_params = test_value[1]
app, vent_gui = spawn_gui
# if one of the cycle variables, make sure we click the other one as being autocalculated
if value_name == values.ValueName.INSPIRATION_TIME_SEC:
vent_gui.control_panel.cycle_buttons[values.ValueName.BREATHS_PER_MINUTE].click()
elif value_name == values.ValueName.BREATHS_PER_MINUTE:
vent_gui.control_panel.cycle_buttons[values.ValueName.INSPIRATION_TIME_SEC].click()
vent_gui.start()
vent_gui.timer.stop()
vent_gui.control_panel.lock_button.click()
abs_range = value_params.abs_range
safe_range = value_params.safe_range
# generate target value
def gen_test_value():
test_value = np.random.rand()*(safe_range[1]-safe_range[0]) + safe_range[0]
test_value = np.round(test_value, value_params.decimals)
return test_value
####
# test setting controls from control widget
# from editablelabel
control_widget = vent_gui.controls[value_name.name]
for i in range(n_samples):
test_value = gen_test_value()
control_widget.set_value_label.setLabelEditableAction()
control_widget.set_value_label.lineEdit.setText(str(test_value))
control_widget.set_value_label.returnPressedAction()
# should call labelUpdatedAction and send to controller
control_value = vent_gui.coordinator.get_control(value_name)
assert(control_value.value == test_value)
# from slider if we've got one
if value_params.control_type == "slider":
# toggle it open
assert(control_widget.slider_frame.isVisible() == False)
control_widget.toggle_button.click()
assert(control_widget.slider_frame.isVisible() == True)
for i in range(n_samples):
test_value = gen_test_value()
control_widget.slider.setValue(test_value)
control_value = vent_gui.coordinator.get_control(value_name)
assert(control_value.value == test_value)
# or from the recorder if we've got one
elif value_params.control_type == "record":
for i in range(n_samples):
# press record
assert(control_widget.toggle_button.isChecked() == False)
control_widget.toggle_button.click()
assert (control_widget.toggle_button.isChecked() == True)
# feed it 10 test values
test_values = []
for j in range(10):
new_test_value = gen_test_value()
test_values.append(new_test_value)
control_widget.update_sensor_value(new_test_value)
# stop recording and check the value
control_widget.toggle_button.click()
assert(control_widget.toggle_button.isChecked() == False)
control_value = vent_gui.coordinator.get_control(value_name)
assert control_value.value == np.mean(test_values)
# should be one or the other
else:
assert False
# from set_value
for i in range(n_samples):
test_value = gen_test_value()
vent_gui.set_value(test_value, value_name = value_name)
control_value = vent_gui.coordinator.get_control(value_name)
assert(control_value.value == test_value)
[docs]def test_VTE_set(qtbot, spawn_gui):
app, vent_gui = spawn_gui
vent_gui.start()
vent_gui.timer.stop()
vent_gui.control_panel.lock_button.click()
control_widget = vent_gui.monitor[ValueName.VTE.name]
# record some VTEs n shit
for i in range(n_samples):
# press record
assert (control_widget.toggle_button.isChecked() == False)
control_widget.toggle_button.click()
assert (control_widget.toggle_button.isChecked() == True)
# feed it 10 test values
test_values = []
for j in range(10):
new_test_value = np.random.random()*2+1
test_values.append(new_test_value)
control_widget.update_sensor_value(new_test_value)
# stop recording and check the value
control_widget.toggle_button.click()
assert (control_widget.toggle_button.isChecked() == False)
control_value = vent_gui._state['controls'][ValueName.VTE.name]
assert control_value == np.mean(test_values)
[docs]@pytest.mark.parametrize("test_value", [(k, v) for k, v in values.VALUES.items() if k in \
(ValueName.BREATHS_PER_MINUTE,
ValueName.INSPIRATION_TIME_SEC,
ValueName.IE_RATIO,
)])
def test_autoset_cycle(qtbot, spawn_gui, test_value):
value_name = test_value[0]
value_params = test_value[1]
app, vent_gui = spawn_gui
if value_name == ValueName.BREATHS_PER_MINUTE:
vent_gui.control_panel.cycle_buttons[ValueName.BREATHS_PER_MINUTE].click()
assert vent_gui._autocalc_cycle == ValueName.BREATHS_PER_MINUTE
# set IE_RATIO and INSPIRATION_TIME_SEC
for i in range(n_samples):
test_ie = np.random.rand()+0.5
test_tinsp = np.random.rand()*3
vent_gui.set_value(test_ie, ValueName.IE_RATIO)
vent_gui.set_value(test_tinsp, ValueName.INSPIRATION_TIME_SEC)
ret_tinsp = vent_gui.coordinator.get_control(ValueName.INSPIRATION_TIME_SEC).value
ret_rr = vent_gui.coordinator.get_control(ValueName.BREATHS_PER_MINUTE).value
assert ret_tinsp == test_tinsp
assert ret_rr == 1/(test_tinsp + (test_tinsp/test_ie)) * 60
elif value_name == ValueName.INSPIRATION_TIME_SEC:
vent_gui.control_panel.cycle_buttons[ValueName.INSPIRATION_TIME_SEC].click()
assert vent_gui._autocalc_cycle == ValueName.INSPIRATION_TIME_SEC
for i in range(n_samples):
test_ie = np.random.rand()+0.5
test_rr = np.random.rand()*20+10
vent_gui.set_value(test_ie, ValueName.IE_RATIO)
vent_gui.set_value(test_rr, ValueName.BREATHS_PER_MINUTE)
ret_tinsp = vent_gui.coordinator.get_control(ValueName.INSPIRATION_TIME_SEC).value
ret_rr = vent_gui.coordinator.get_control(ValueName.BREATHS_PER_MINUTE).value
assert ret_tinsp == (1/(test_rr/60)) / (1+1/test_ie)
assert ret_rr == test_rr
elif value_name == ValueName.IE_RATIO:
# the easy one
vent_gui.control_panel.cycle_buttons[ValueName.IE_RATIO].click()
assert vent_gui._autocalc_cycle == ValueName.IE_RATIO
for i in range(n_samples):
test_tinsp = np.random.rand()*3
test_rr = np.random.rand() * 20 + 10
vent_gui.set_value(test_tinsp, ValueName.INSPIRATION_TIME_SEC)
vent_gui.set_value(test_rr, ValueName.BREATHS_PER_MINUTE)
ret_tinsp = vent_gui.coordinator.get_control(ValueName.INSPIRATION_TIME_SEC).value
ret_rr = vent_gui.coordinator.get_control(ValueName.BREATHS_PER_MINUTE).value
assert ret_tinsp == test_tinsp
assert ret_rr == test_rr
[docs]def test_alarm_manager_signals(qtbot, spawn_gui):
# ensure test alarm_manager.update
# TODO: This
pass
[docs]def test_handle_controller_alarm(qtbot, spawn_gui):
# TODO: This
pass
[docs]def test_save_restore_gui_state(qtbot, spawn_gui):
# TODO: This
pass
[docs]def test_raise_alarm_card(qtbot, spawn_gui, fake_sensors):
app, vent_gui = spawn_gui
# throw a hapa and make sure the alarm card shows up
uh_oh = fake_sensors()
vent_gui.alarm_manager.update(uh_oh)
# there will be some alarms because we dont have a way of generating safe values yet, but HAPA wont be in them
assert not any([a.alarm_type == AlarmType.HIGH_PRESSURE for a in vent_gui.alarm_bar.alarms])
# now poppa hapa
uh_oh['PRESSURE'] = 10000
vent_gui.alarm_manager.update(uh_oh)
assert any([a.alarm_type == AlarmType.HIGH_PRESSURE for a in vent_gui.alarm_bar.alarms])
[docs]def test_gui_main_etc(qtbot, spawn_gui):
app, vent_gui = spawn_gui
# test setting update period
vent_gui.update_period = 0.10
assert vent_gui.update_period == 0.10
#########################
# Test control panel
[docs]def test_pressure_unit_conversion():
# TODO: this
return
[docs]def test_sliders_during_unit_convertion():
# TODO: this
return
[docs]def test_set_breath_detection():
# TODO: this
pass
###################################
# Test base components
#####
# doubleslider
#
[docs]def test_doubleslider(qtbot):
"""
test that the doubleslider accurately represents floats
"""
doubleslider = widgets.components.DoubleSlider(decimals=decimals)
doubleslider.show()
qtbot.addWidget(doubleslider)
doubleslider.setMinimum(0)
doubleslider.setMaximum(1)
for i in np.random.rand(n_samples):
test_val = np.round(i, decimals)
# set value inside of signal catcher to test both value and signal
with qtbot.waitSignal(doubleslider.doubleValueChanged, timeout=1000) as blocker:
doubleslider.setValue(test_val)
assert(doubleslider.value() == test_val)
assert(blocker.args == test_val)
[docs]def test_doubleslider_minmax(qtbot, generic_minmax):
doubleslider = widgets.components.DoubleSlider(decimals=decimals)
doubleslider.show()
qtbot.addWidget(doubleslider)
multiplier = 100
for i in range(n_samples):
min, max = generic_minmax()
doubleslider.setMinimum(min)
doubleslider.setMaximum(max)
# test that values were set correctly
assert(doubleslider.minimum() == min)
assert(doubleslider.maximum() == max)
# test below min and above max
test_min = min - np.random.rand()*multiplier
test_max = max + np.random.rand()*multiplier
doubleslider.setValue(test_min)
assert(doubleslider.value() == doubleslider.minimum())
doubleslider.setValue(test_max)
assert(doubleslider.value() == doubleslider.maximum())
#
# #################
# # RangeSlider
#
# def test_rangeslider(qtbot, generic_minmax, generic_saferange):
# abs_range = generic_minmax()
# safe_range = generic_saferange()
# print(safe_range)
# orientation = QtCore.Qt.Orientation.Horizontal
#
#
# rangeslider = widgets.components.RangeSlider(
# abs_range,
# safe_range,
# decimals=decimals,
# orientation=orientation)
# rangeslider.show()
# qtbot.addWidget(rangeslider)
#
# # test ranges & values
# assert(rangeslider.minimum() == abs_range[0])
# assert(rangeslider.maximum() == abs_range[1])
# assert(rangeslider.low == safe_range[0])
# assert(rangeslider.high == safe_range[1])
#
# for i in range(n_samples):
# min, max = generic_saferange()
# print(min, max)
#
# with qtbot.waitSignal(rangeslider.valueChanged, timeout=1000) as blocker:
# rangeslider.setHigh(max)
# assert(rangeslider.high == max)
#
# rangeslider.setLow(min)
# assert(rangeslider.low == min)
#
# def test_rangeslider_minmax(qtbot, generic_minmax, generic_saferange):
# abs_range = generic_minmax()
# safe_range = generic_saferange()
# decimals = 5
# orientation = QtCore.Qt.Orientation.Horizontal
#
# rangeslider = widgets.components.RangeSlider(
# abs_range,
# safe_range,
# decimals=decimals,
# orientation=orientation)
# rangeslider.show()
# qtbot.addWidget(rangeslider)
#
# for i in range(n_samples):
# abs_min, abs_max = generic_minmax()
# safe_min, safe_max = generic_saferange()
#
# #pdb.set_trace()
# # Set min and max and test they were set correctly
# rangeslider.setMinimum(abs_min)
# assert(rangeslider.minimum() == abs_min)
#
# rangeslider.setMaximum(abs_max)
# assert(rangeslider.maximum() == abs_max)
#
# # set low and high and test they were set correctly
# rangeslider.setHigh(safe_max)
# assert(rangeslider.high == safe_max)
#
# rangeslider.setLow(safe_min)
# assert(rangeslider.low == safe_min)
#
# # try to set low and high outside of max
# rangeslider.setHigh(abs_max + 1)
# assert(rangeslider.high == rangeslider.maximum())
#
# rangeslider.setLow(abs_min - 1)
# assert(rangeslider.low == rangeslider.minimum())
#
# # try to set low higher and high and vice versa
# midpoint = np.round(np.mean([abs_min, abs_max]), decimals)
# highpoint = np.round(midpoint-1, decimals)
# lowpoint = np.round(midpoint-1-(10**-decimals), decimals)
#
# rangeslider.setLow(midpoint)
# rangeslider.setHigh(highpoint)
#
# assert(rangeslider.high == highpoint)
# assert(rangeslider.low == lowpoint)
#
# highpoint = np.round(midpoint+1+(10**-decimals), decimals)
# lowpoint = np.round(midpoint + 1, decimals)
#
# rangeslider.setHigh(midpoint)
# rangeslider.setLow(lowpoint)
#
# assert(rangeslider.low == lowpoint)
# assert(rangeslider.high == highpoint)
#
##########################
# keeping for good pytest-qt exmaples
# @pytest.mark.parametrize("test_value", [(k, v) for k, v in values.SENSOR.items()])
# def test_gui_monitor(qtbot, spawn_gui, test_value):
# app, vent_gui = spawn_gui
# vent_gui.start()
# vent_gui.timer.stop()
# value_name = test_value[0]
# value_params = test_value[1]
# abs_range = value_params.abs_range
# # generate target value
# def gen_test_values():
# test_value = np.random.rand(2)*(abs_range[1]-abs_range[0]) + abs_range[0]
# test_value = np.round(test_value, value_params.decimals)
# return np.min(test_value), np.max(test_value)
# monitor_widget = vent_gui.monitor[value_name.name]
# # open the control
# assert(monitor_widget.slider_frame.isVisible() == False)
# monitor_widget.toggle_button.click()
# assert (monitor_widget.slider_frame.isVisible() == True)
# # set handles to abs_min and max so are on absolute right and left sides
# monitor_widget.range_slider.setValue(abs_range)
# assert(monitor_widget.range_slider.low == monitor_widget.range_slider.minimum())
# assert (monitor_widget.range_slider.high == monitor_widget.range_slider.maximum())
# #
# # # move left a quarter of the way to the right
# # widget_size = monitor_widget.range_slider.size()
# #
# # # get low box position
# # low_pos = monitor_widget.range_slider.get_handle_rect(0)
# # click_pos = low_pos.center()
# # move_pos = copy(click_pos)
# # move_pos.setX(move_pos.x() + (widget_size.width()/4))
# #
# # qtbot.mouseMove(monitor_widget.range_slider, click_pos, delay=100)
# # qtbot.mousePress(monitor_widget.range_slider, QtCore.Qt.LeftButton, delay=200)
# # qtbot.mouseMove(monitor_widget.range_slider, move_pos)
# # qtbot.mouseRelease(monitor_widget.range_slider, QtCore.Qt.LeftButton, pos=move_pos, delay=200)
# # set with range_slider
# # for i in range(n_samples):
# # test_min, test_max = gen_test_values()
# #
# #
# # with qtbot.waitSignal(monitor_widget.limits_changed, timeout=1000) as blocker:
# # monitor_widget.range_slider.setLow(test_min)
# # sleep(0.01)
# # assert(blocker.args[0][0]==test_min)
# #
# # with qtbot.waitSignal(monitor_widget.limits_changed, timeout=1000) as blocker:
# # monitor_widget.range_slider.setHigh(test_max)
# # sleep(0.01)
# # assert(blocker.args[0][1]==test_max)