Merge branch 'dev/rx_disable_datastream_port' of github.com:slsdetectorgroup/slsDetectorPackage into dev/rx_disable_datastream_port
Build on RHEL9 docker image / build (push) Successful in 3m47s
Build on RHEL8 docker image / build (push) Successful in 5m10s
Run Simulator Tests on local RHEL9 / build (push) Successful in 18m53s
Run Simulator Tests on local RHEL8 / build (push) Successful in 22m23s

This commit is contained in:
2026-07-06 14:16:08 +02:00
18 changed files with 1786 additions and 792 deletions
+2 -1
View File
@@ -37,7 +37,8 @@ jobs:
-DSLS_USE_PYTHON=ON \
-DSLS_USE_HDF5=ON \
-DSLS_USE_GUI=ON \
-DSLS_USE_MOENCH=ON
-DSLS_USE_MOENCH=ON \
-DSLS_TREAT_WARNINGS_AS_ERRORS=ON
- name: Build
# Build your program with the given configuration
+12 -3
View File
@@ -236,6 +236,7 @@ option(SLS_USE_MOENCH "compile zmq and post processing for Moench" OFF)
option(SLS_USE_JUNGFRAU "compile post processing for Jungfrau" OFF)
option(SLS_USE_MATTERHORN "compile matterhorn server" OFF)
option(SLS_INSTALL_VERSIONED_BINARIES "Add version number to binaries on install" OFF) #Needed for multi version RPM
option(SLS_TREAT_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
#Convenience option to switch off defaults when building Moench binaries only
option(SLS_BUILD_ONLY_MOENCH "compile only Moench" OFF)
@@ -347,7 +348,9 @@ if (NOT TARGET slsProjectWarnings)
-Wno-missing-field-initializers)
endif()
if(SLS_TREAT_WARNINGS_AS_ERRORS)
target_compile_options(slsProjectWarnings INTERFACE -Werror)
endif()
endif()
@@ -376,6 +379,10 @@ if (NOT TARGET slsProjectCSettings)
target_compile_options(slsProjectCSettings INTERFACE -fsanitize=address,undefined -fno-omit-frame-pointer)
target_link_libraries(slsProjectCSettings INTERFACE -fsanitize=address,undefined)
endif()
if(SLS_TREAT_WARNINGS_AS_ERRORS)
target_compile_options(slsProjectCSettings INTERFACE -Werror)
endif()
endif()
@@ -483,13 +490,15 @@ if(SLS_BUILD_DOCS)
add_subdirectory(docs)
endif(SLS_BUILD_DOCS)
if(SLS_USE_MOENCH)
if(SLS_USE_MOENCH OR SLS_USE_JUNGFRAU)
add_subdirectory(slsDetectorCalibration/tiffio)
endif()
if(SLS_USE_MOENCH)
add_subdirectory(slsDetectorCalibration/moenchExecutables)
endif(SLS_USE_MOENCH)
if(SLS_USE_JUNGFRAU)
add_subdirectory(slsDetectorCalibration/tiffio)
add_subdirectory(slsDetectorCalibration/jungfrauExecutables)
endif(SLS_USE_JUNGFRAU)
@@ -133,6 +133,7 @@ class AcquisitionTab(QtWidgets.QWidget):
self.view.labelDigital.setDisabled(True)
self.view.labelTransceiver.setDisabled(True)
self.view.spinBoxTransceiver.setDisabled(True)
self.plotTab.enableallCounterCheckBox(False)
case readoutMode.DIGITAL_ONLY:
self.view.spinBoxAnalog.setDisabled(True)
self.view.labelAnalog.setDisabled(True)
@@ -140,6 +141,7 @@ class AcquisitionTab(QtWidgets.QWidget):
self.view.labelDigital.setEnabled(True)
self.view.labelTransceiver.setDisabled(True)
self.view.spinBoxTransceiver.setDisabled(True)
self.plotTab.enableallCounterCheckBox(False)
case readoutMode.ANALOG_AND_DIGITAL:
self.view.spinBoxAnalog.setEnabled(True)
self.view.labelAnalog.setEnabled(True)
@@ -147,6 +149,7 @@ class AcquisitionTab(QtWidgets.QWidget):
self.view.labelDigital.setEnabled(True)
self.view.labelTransceiver.setDisabled(True)
self.view.spinBoxTransceiver.setDisabled(True)
self.plotTab.enableallCounterCheckBox(False)
case readoutMode.TRANSCEIVER_ONLY:
self.view.spinBoxAnalog.setDisabled(True)
self.view.labelAnalog.setDisabled(True)
@@ -154,6 +157,7 @@ class AcquisitionTab(QtWidgets.QWidget):
self.view.labelDigital.setDisabled(True)
self.view.labelTransceiver.setEnabled(True)
self.view.spinBoxTransceiver.setEnabled(True)
self.plotTab.enableallCounterCheckBox(self.plotTab.view.radioButtonImage.isChecked()) # enable counter checkboxes for matterhorn
case _:
self.view.spinBoxAnalog.setDisabled(True)
self.view.labelAnalog.setDisabled(True)
@@ -161,6 +165,7 @@ class AcquisitionTab(QtWidgets.QWidget):
self.view.labelDigital.setEnabled(True)
self.view.labelTransceiver.setEnabled(True)
self.view.spinBoxTransceiver.setEnabled(True)
self.plotTab.enableallCounterCheckBox(self.plotTab.view.radioButtonImage.isChecked()) # enable counter checkboxes for matterhorn
self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut)
self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog)
+105 -38
View File
@@ -4,10 +4,10 @@ import random
from pathlib import Path
import numpy as np
from PyQt5 import QtWidgets, QtGui, uic
from PyQt5 import QtWidgets, QtGui, QtCore, uic
from aare import transform, ReadoutMode
from aare._aare import Matterhorn10, Matterhorn02, Moench04
from aare._aare import Matterhorn10, Matterhorn02, Moench04, Moench05
import pyqtgraph as pg
from pyctbgui.utils import recordOrApplyPedestal
@@ -43,6 +43,7 @@ class PlotTab(QtWidgets.QWidget):
self.pedestalApply: bool = True
self.__acqFrames = None
self.logger = logging.getLogger('PlotTab')
self.plotSplitter = None
def setup_ui(self):
self.signalsTab = self.mainWindow.signalsTab
@@ -51,10 +52,17 @@ class PlotTab(QtWidgets.QWidget):
self.adcTab = self.mainWindow.adcTab
self.initializeColorMaps()
# TODO use list comprehension
self.checkBoxCounters = [self.view.checkBoxCounter0, self.view.checkBoxCounter1, self.view.checkBoxCounter2, self.view.checkBoxCounter3]
for checkBox in self.checkBoxCounters:
checkBox.setChecked(False)
checkBox.setEnabled(False)
self.imagePlots = (
self.mainWindow.plotAnalogImage,
self.mainWindow.plotDigitalImage,
self.mainWindow.plotTransceiverImage,
self.transceiverTab.transceiverImageViews,
)
def connect_ui(self):
@@ -93,12 +101,54 @@ class PlotTab(QtWidgets.QWidget):
self.view.radioButtonFixed.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.fixed))
self.view.radioButtonCenter.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.center))
for plot in self.imagePlots:
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot))
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
for index, checkBox in enumerate(self.checkBoxCounters):
checkBox.stateChanged.connect(partial(self.displayCounter, index))
# show image Values for analog image
nMaxY = lambda : self.mainWindow.nAnalogRows
nMaxX = lambda : self.mainWindow.nAnalogCols
frame = lambda : self.mainWindow.analog_frame
plot = self.mainWindow.plotAnalogImage
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot, nMaxX, nMaxY, frame))
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
# show image Values for digital image
nMaxY = lambda : self.mainWindow.nDigitalRows
nMaxX = lambda : self.mainWindow.nDigitalCols
frame = lambda : self.mainWindow.digital_frame
plot = self.mainWindow.plotDigitalImage
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot, nMaxX, nMaxY, frame))
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
# show image Values for transceiver image
nMaxY = lambda : self.transceiverTab.nTransceiverRows
nMaxX = lambda : self.transceiverTab.nTransceiverCols
for index, image_view in enumerate(self.transceiverTab.transceiverImageViews):
frame = lambda : image_view.getImageItem().image
image_view.scene.sigMouseMoved.connect(partial(self.showPlotValues, image_view, nMaxX, nMaxY, frame))
image_view.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, image_view))
self.view.checkBoxShowLegend.stateChanged.connect(self.toggleLegend)
def displayCounter(self, index : int, state : int):
# toggle the display of the counter i and update the splitter
self.transceiverTab.shownCounters[index] = (state == QtCore.Qt.Checked)
self.transceiverTab.update_ImageSplitter()
def setCounterCheckBox(self, index : int, check : bool):
self.checkBoxCounters[index].setChecked(check)
def enableCounterCheckBox(self, index : int, enabled : bool):
is_transceiver = self.mainWindow.romode.value in [3, 4]
is_image = self.view.radioButtonImage.isChecked()
self.checkBoxCounters[index].setEnabled(enabled and is_transceiver and is_image)
def enableallCounterCheckBox(self, enabled : bool):
for checkBox in self.checkBoxCounters:
checkBox.setEnabled(enabled)
def refresh(self):
self.getZMQHWM()
@@ -262,7 +312,11 @@ class PlotTab(QtWidgets.QWidget):
updates UI views should be called after every change to cmin or cmax
"""
for plot in self.imagePlots:
plot.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
if isinstance(plot, list): # hacky for now only transceiver image is a PlotSplitter
for p in plot:
p.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
else:
plot.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
self.view.cminSpinBox.setValue(self.cmin)
self.view.cmaxSpinBox.setValue(self.cmax)
@@ -271,7 +325,9 @@ class PlotTab(QtWidgets.QWidget):
# print(f'color map:{self.comboBoxColorMap.currentText()}')
self.mainWindow.plotAnalogImage.setColorMap(cm)
self.mainWindow.plotDigitalImage.setColorMap(cm)
self.mainWindow.plotTransceiverImage.setColorMap(cm)
for i, showncounter in enumerate(self.transceiverTab.shownCounters):
if showncounter:
self.transceiverTab.transceiverImageViews[i].setColorMap(cm)
def getZMQHWM(self):
@@ -343,11 +399,11 @@ class PlotTab(QtWidgets.QWidget):
self.mainWindow.transceiverPlots[i].hide()
def addAllSelectedTransceiverPlots(self):
for i in range(Defines.transceiver.count):
for i in range(Defines.transceiver.maxcount):
self.addSelectedTransceiverPlots(i)
def removeAllTransceiverPlots(self):
for i in range(Defines.transceiver.count):
for i in range(Defines.transceiver.maxcount):
self.mainWindow.transceiverPlots[i].hide()
def showPlot(self):
@@ -356,7 +412,7 @@ class PlotTab(QtWidgets.QWidget):
self.mainWindow.plotTransceiverWaveform.hide()
self.mainWindow.plotAnalogImage.hide()
self.mainWindow.plotDigitalImage.hide()
self.mainWindow.plotTransceiverImage.hide()
self.transceiverTab.transceiverImageSplitter.hide()
self.view.labelDigitalWaveformOption.setDisabled(True)
self.view.radioButtonOverlay.setDisabled(True)
self.view.radioButtonStripe.setDisabled(True)
@@ -379,7 +435,10 @@ class PlotTab(QtWidgets.QWidget):
if self.view.radioButtonWaveform.isChecked():
self.mainWindow.plotTransceiverWaveform.show()
elif self.view.radioButtonImage.isChecked():
self.mainWindow.plotTransceiverImage.show()
for i, showncounter in enumerate(self.transceiverTab.shownCounters):
self.transceiverTab.transceiverImageViews[i].setVisible(showncounter)
self.transceiverTab.transceiverImageSplitter.show()
def plotOptions(self):
@@ -404,14 +463,18 @@ class PlotTab(QtWidgets.QWidget):
'bottom', "<span style=\"color:black;font-size:14px\">Transceiver Sample [#]</span>")
self.view.stackedWidgetPlotType.setCurrentIndex(0)
self.enableallCounterCheckBox(False) # disable counter checkboxes for waveform
elif self.view.radioButtonImage.isChecked():
self.view.stackedWidgetPlotType.setCurrentIndex(2)
is_transceiver = self.mainWindow.romode.value in [3, 4]
self.enableallCounterCheckBox(is_transceiver) # enable counter checkboxes for matterhorn
self.setDecoder()
if self.view.radioButtonNoPlot.isChecked():
self.view.labelPlotOptions.hide()
self.view.stackedWidgetPlotType.hide()
self.enableallCounterCheckBox(False) # disable counter checkboxes when no plot is selected
# enable plotting
else:
self.view.labelPlotOptions.show()
@@ -419,45 +482,57 @@ class PlotTab(QtWidgets.QWidget):
self.mainWindow.read_timer.start(Defines.Time_Plot_Refresh_ms)
def setDecoder(self):
# TODO: really dont like to set attributes on the fly - hard to understand whats going on
if self.view.comboBoxPlot.currentText() == "Matterhorn02":
print("Initializing decoder for Matterhorn02")
self.mainWindow.nTransceiverRows = Matterhorn02.nRows
self.mainWindow.nTransceiverCols = Matterhorn02.nCols
self.transceiverTab.nTransceiverRows = Matterhorn02.nRows
self.transceiverTab.nTransceiverCols = Matterhorn02.nCols
self.transceiverTab.update_numCounters(1)
self.mainWindow.decoder = transform.Matterhorn02TransceiverTransform()
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_16bit_1_counter":
print("Initializing decoder for Matterhorn1 with 1 counter 16 bit dynamic range")
self.mainWindow.nTransceiverRows = Matterhorn10.nRows
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.update_numCounters(1)
self.mainWindow.decoder = transform.Matterhorn10Transform(16, 1)
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_16bit_4_counters":
print("Initializing decoder for Matterhorn1 with 4 counters 16 bit dynamic range")
self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.update_numCounters(4)
self.mainWindow.decoder = transform.Matterhorn10Transform(16, 4)
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_8bit_1_counter":
print("Initializing decoder for Matterhorn1 with 1 counter 8 bit dynamic range")
self.mainWindow.nTransceiverRows = Matterhorn10.nRows
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.update_numCounters(1)
self.mainWindow.decoder = transform.Matterhorn10Transform(8, 1)
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_8bit_4_counters":
print("Initializing decoder for Matterhorn1 with 4 counters 8 bit dynamic range")
self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.update_numCounters(4)
self.mainWindow.decoder = transform.Matterhorn10Transform(8, 4)
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_4bit_4_counters":
print("Initializing decoder for Matterhorn1 with 4 counters 4 bit dynamic range")
self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.update_numCounters(4)
self.mainWindow.decoder = transform.Matterhorn10Transform(4, 4)
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_4bit_1_counter":
print("Initializing decoder for Matterhorn1 with 1 counter 4 bit dynamic range")
self.mainWindow.nTransceiverRows = Matterhorn10.nRows
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
self.transceiverTab.update_numCounters(1)
self.mainWindow.decoder = transform.Matterhorn10Transform(4, 1)
elif self.view.comboBoxPlot.currentText() == "Moench04":
self.mainWindow.nAnalogRows = Moench04.nRows
self.mainWindow.nAnalogCols = Moench04.nCols
self.mainWindow.decoder = transform.Moench04AnalogTransform()
elif self.view.comboBoxPlot.currentText() == "Moench05":
self.mainWindow.nAnalogRows = Moench05.nRows
self.mainWindow.nAnalogCols = Moench05.nCols
self.mainWindow.decoder = transform.Moench05Transform()
try:
if hasattr(self.mainWindow.decoder, "compatibility") and callable(getattr(self.mainWindow.decoder, "compatibility")):
@@ -533,21 +608,13 @@ class PlotTab(QtWidgets.QWidget):
# get the RGB Values
# print(color.getRgb())
def showPlotValues(self, sender, pos):
def showPlotValues(self, sender, get_nMaxX, get_nMaxY, get_frame, pos):
x = sender.getImageItem().mapFromScene(pos).x()
y = sender.getImageItem().mapFromScene(pos).y()
val = 0
nMaxY = self.mainWindow.nAnalogRows
nMaxX = self.mainWindow.nAnalogCols
frame = self.mainWindow.analog_frame
if sender == self.mainWindow.plotDigitalImage:
nMaxY = self.mainWindow.nDigitalRows
nMaxX = self.mainWindow.nDigitalCols
frame = self.mainWindow.digital_frame
elif sender == self.mainWindow.plotTransceiverImage:
nMaxY = self.mainWindow.nTransceiverRows
nMaxX = self.mainWindow.nTransceiverCols
frame = self.mainWindow.transceiver_frame
nMaxX = get_nMaxX()
nMaxY = get_nMaxY()
frame = get_frame()
if 0 <= x < nMaxX and 0 <= y < nMaxY and not np.array_equal(frame, []):
val = frame[int(y), int(x)]
message = f'[row, col]: [{y:.2f}, {x:.2f}] = {val:.2f}'
+57 -30
View File
@@ -2,7 +2,7 @@ from functools import partial
from pathlib import Path
import numpy as np
from PyQt5 import QtWidgets, uic
from PyQt5 import QtWidgets, QtCore, uic
import pyqtgraph as pg
from pyqtgraph import LegendItem
@@ -24,11 +24,18 @@ class TransceiverTab(QtWidgets.QWidget):
self.plotTab = None
self.legend: LegendItem | None = None
self.acquisitionTab = None
self.nCounters: int = Defines.transceiver.maxcount
self.nTransceiverRows : int = 0
self.nTransceiverCols : int = 0
self.shownCounters: list[bool] = [True] * Defines.transceiver.maxcount # per default show all 4 counters
self.transceiverImageSplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
self.transceiverImageViews : list = [] # pg image view for each counter
self.firstTransceiverImage : list[bool] = [True] * Defines.transceiver.maxcount # to keep track of first image for each counter to maintain zoom state
def setup_ui(self):
self.plotTab = self.mainWindow.plotTab
self.acquisitionTab = self.mainWindow.acquisitionTab
for i in range(Defines.transceiver.count):
for i in range(Defines.transceiver.maxcount):
self.setTransceiverButtonColor(i, self.plotTab.getRandomColor())
self.initializeAllTransceiverPlots()
@@ -39,7 +46,7 @@ class TransceiverTab(QtWidgets.QWidget):
self.plotTab.subscribeToggleLegend(self.updateLegend)
def connect_ui(self):
for i in range(Defines.transceiver.count):
for i in range(Defines.transceiver.maxcount):
getattr(self.view, f"checkBoxTransceiver{i}").stateChanged.connect(partial(self.setTransceiverEnable, i))
getattr(self.view,
f"checkBoxTransceiver{i}Plot").stateChanged.connect(partial(self.setTransceiverEnablePlot, i))
@@ -55,7 +62,7 @@ class TransceiverTab(QtWidgets.QWidget):
"""
enabledPlots = []
self.legend.clear()
for i in range(Defines.transceiver.count):
for i in range(Defines.transceiver.maxcount):
if getattr(self.view, f'checkBoxTransceiver{i}Plot').isChecked():
plotName = getattr(self.view, f"labelTransceiver{i}").text()
enabledPlots.append((self.mainWindow.transceiverPlots[i], plotName))
@@ -104,7 +111,7 @@ class TransceiverTab(QtWidgets.QWidget):
trans_array = self._processWaveformData(data, dSamples, self.mainWindow.romode.value,
self.mainWindow.nDBitEnabled, self.nTransceiverEnabled)
idx = 0
for i in range(Defines.transceiver.count):
for i in range(Defines.transceiver.maxcount):
checkBoxPlot = getattr(self.view, f"checkBoxTransceiver{i}Plot")
checkBoxEn = getattr(self.view, f"checkBoxTransceiver{i}")
if checkBoxEn.isChecked() and checkBoxPlot.isChecked():
@@ -133,9 +140,7 @@ class TransceiverTab(QtWidgets.QWidget):
transceiverOffset += nDBitEnabled * (nbitsPerDBit // 8)
trans_array = np.array(np.frombuffer(data, offset=transceiverOffset, dtype=np.uint8))
tmp = self.mainWindow.decoder(trans_array)
return tmp
return self.mainWindow.decoder(trans_array)
def processImageData(self, data, dSamples):
"""
@@ -145,28 +150,47 @@ class TransceiverTab(QtWidgets.QWidget):
data: raw image data
"""
# get zoom state
viewBox = self.mainWindow.plotTransceiverImage.getView()
state = viewBox.getState()
image_states = [image_view.getView().getState() for image_view in self.transceiverImageViews]
transceiver_frame : np.ndarray = None
try:
self.mainWindow.transceiver_frame = self._processImageData(data, dSamples, self.mainWindow.romode.value,
transceiver_frame = self._processImageData(data, dSamples, self.mainWindow.romode.value,
self.mainWindow.nDBitEnabled)
self.plotTab.ignoreHistogramSignal = True
self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame)
for i in range(transceiver_frame.shape[0]):
self.transceiverImageViews[i].setImage(transceiver_frame[i])
except Exception as e:
self.mainWindow.statusbar.setStyleSheet("color:red")
self.acquisitionTab.updateCurrentFrame('Invalid Image')
self.mainWindow.statusbar.showMessage(str(e))
print("Error: ", str(e))
self.plotTab.setFrameLimits(self.mainWindow.transceiver_frame)
self.plotTab.setFrameLimits(transceiver_frame)
# keep the zoomed in state (not 1st image)
if self.mainWindow.firstTransceiverImage:
self.mainWindow.firstTransceiverImage = False
else:
viewBox.setState(state)
return self.mainWindow.transceiver_frame
for idx, image_view in enumerate(self.transceiverImageViews):
if(self.firstTransceiverImage[idx] and self.shownCounters[idx]):
self.firstTransceiverImage[idx] = False
else:
image_view.getView().setState(image_states[idx])
return transceiver_frame
def update_numCounters(self, num_counters):
# update the number of counters and adjust the image splitter accordingly
self.nCounters = num_counters
for i in range(Defines.transceiver.maxcount):
self.shownCounters[i] = i < self.nCounters
self.plotTab.setCounterCheckBox(i, self.shownCounters[i]) # check the counter checkbox
self.plotTab.enableCounterCheckBox(i, self.shownCounters[i]) # disable counter checkbox
self.update_ImageSplitter() # update the splitter to show/hide image views based on the number of counters
def update_ImageSplitter(self):
for i, showncounter in enumerate(self.shownCounters):
self.transceiverImageViews[i].setVisible(showncounter)
def initializeAllTransceiverPlots(self):
self.mainWindow.plotTransceiverWaveform = pg.plot()
@@ -174,7 +198,7 @@ class TransceiverTab(QtWidgets.QWidget):
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverWaveform, 5)
self.mainWindow.transceiverPlots = {}
waveform = np.zeros(1000)
for i in range(Defines.transceiver.count):
for i in range(Defines.transceiver.maxcount):
pen = pg.mkPen(color=self.getTransceiverButtonColor(i), width=1)
legendName = getattr(self.view, f"labelTransceiver{i}").text()
self.mainWindow.transceiverPlots[i] = self.mainWindow.plotTransceiverWaveform.plot(waveform,
@@ -182,16 +206,19 @@ class TransceiverTab(QtWidgets.QWidget):
name=legendName)
self.mainWindow.transceiverPlots[i].hide()
self.mainWindow.plotTransceiverImage = pg.ImageView()
self.mainWindow.nTransceiverRows = 0
self.mainWindow.nTransceiverCols = 0
self.mainWindow.transceiver_frame = np.zeros(
(self.mainWindow.nTransceiverRows, self.mainWindow.nTransceiverCols))
self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame)
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverImage, 6)
# initialize image
cm = pg.colormap.get('CET-L9') # prepare a linear color map
self.mainWindow.plotTransceiverImage.setColorMap(cm)
for i in range(self.nCounters):
imageView = pg.ImageView()
imageView.setColorMap(cm)
self.transceiverImageViews.append(imageView)
self.transceiverImageSplitter.addWidget(imageView)
self.update_ImageSplitter() # update the splitter
self.mainWindow.verticalLayoutPlot.addWidget(self.transceiverImageSplitter, 6)
def getTransceiverEnableReg(self):
retval = self.det.transceiverenable
+1118 -654
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -35,8 +35,8 @@ class Defines:
loops_count = 6
class transceiver:
count = 4
tabIndex = 4
maxcount = 4
maxtabIndex = 4
class slowAdc:
tabIndex = 2
@@ -76,6 +76,14 @@ foreach(exe ${MOENCH_EXECUTABLES})
slsProjectOptions
)
target_compile_options(${exe} PRIVATE
-Wno-unused-but-set-variable
-Wno-format-nonliteral
-Wno-format-security
-Wno-double-promotion
-Wno-unused-variable
-Wno-format-overflow)
set_target_properties(${exe} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
@@ -204,9 +204,9 @@ int main(int argc, char *argv[]) {
*/
//#endif
if (totquad > cmin && cl.x >= xmin && cl.x <= xmax &&
if (totquad > static_cast<double>(cmin) && cl.x >= xmin && cl.x <= xmax &&
cl.y >= ymin && cl.y <= ymax &&
totquad < cmax) {
totquad < static_cast<double>(cmax)) {
// if (sum > cmin && totquad / sum > 0.8 && totquad / sum < 1.2 &&
// sum < cmax) {
+1
View File
@@ -21,6 +21,7 @@ class ClientSocket : public DataSocket {
private:
void readReply(int &ret, void *retval, size_t retval_size);
[[noreturn]] void throwError(const std::string &msg) const;
struct sockaddr_in serverAddr {};
std::string socketType;
};
+2 -2
View File
@@ -77,8 +77,6 @@ class DataSocket {
std::string Receive(size_t length);
int read(void *buffer, size_t size);
int write(void *buffer, size_t size);
int setTimeOut(int t_seconds);
int setReceiveTimeout(int us);
void close();
@@ -88,6 +86,8 @@ class DataSocket {
private:
int sockfd_ = -1;
int fnum_{0};
std::string_view errno_name(int e);
};
}; // namespace sls
+2
View File
@@ -96,4 +96,6 @@ bool replace_first(std::string *s, const std::string &substr,
std::pair<std::string, uint16_t> ParseHostPort(const std::string &s);
std::string to_lower(const std::string &s);
} // namespace sls
+26 -25
View File
@@ -5,9 +5,11 @@
#include "sls/sls_detector_defs.h"
#include "sls/sls_detector_exceptions.h"
#include "sls/sls_detector_funcs.h"
#include "sls/string_utils.h"
#include <arpa/inet.h>
#include <cassert>
#include <cstring>
#include <fmt/format.h>
#include <iostream>
#include <stdexcept>
#include <unistd.h>
@@ -24,9 +26,10 @@ ClientSocket::ClientSocket(std::string stype, const std::string &host,
hints.ai_flags |= AI_CANONNAME;
if (getaddrinfo(host.c_str(), nullptr, &hints, &result) != 0) {
std::string msg = "ClientSocket cannot decode host:" + host +
" on port " + std::to_string(port) + "\n";
throw SocketError(msg);
auto msg = fmt::format("Cannot resolve {} hostname: '{}'", to_lower(socketType), host);
throwError(msg);
}
// TODO! Erik, results could have multiple entries do we need to loop
@@ -40,10 +43,11 @@ ClientSocket::ClientSocket(std::string stype, const std::string &host,
if (::connect(getSocketId(), (struct sockaddr *)&serverAddr,
sizeof(serverAddr)) != 0) {
freeaddrinfo(result);
std::string msg = "ClientSocket: Cannot connect to " + socketType +
":" + host + " on port " + std::to_string(port) +
"\n";
throw SocketError(msg);
auto msg = fmt::format(
"Cannot connect to {} on {}:{}\n",
to_lower(socketType), host, port);
throwError(msg);
}
freeaddrinfo(result);
}
@@ -54,10 +58,18 @@ ClientSocket::ClientSocket(std::string sType, struct sockaddr_in addr)
if (::connect(getSocketId(), (struct sockaddr *)&addr, sizeof(addr)) != 0) {
char address[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, address, INET_ADDRSTRLEN);
std::string msg = "ClientSocket: Cannot connect to " + socketType +
":" + address + " on port " +
std::to_string(addr.sin_port) + "\n";
throw SocketError(msg);
auto msg = fmt::format("Cannot connect to {} on {}:{}", to_lower(socketType), address, addr.sin_port);
throwError(msg);
}
}
void ClientSocket::throwError(const std::string &msg) const {
if (socketType == "Receiver") {
throw ReceiverError(msg);
} else if (socketType == "Detector") {
throw DetectorError(msg);
} else {
throw GuiError(msg);
}
}
@@ -80,26 +92,15 @@ void ClientSocket::readReply(int &ret, void *retval, size_t retval_size) {
std::string mess = readErrorMessage();
// Do we need to know hostname here?
// In that case save it???
if (socketType == "Receiver") {
throw ReceiverError("Receiver returned: " + std::string(mess));
} else if (socketType == "Detector") {
throw DetectorError("Detector returned: " + std::string(mess));
} else {
throw GuiError(mess);
}
throwError(socketType + " returned: " + mess);
}
// get retval
Receive(retval, retval_size);
}
// debugging
catch (SocketError &e) {
if (socketType == "Receiver") {
throw ReceiverError("Receiver returned: " + std::string(e.what()));
} else if (socketType == "Detector") {
throw DetectorError("Detector returned: " + std::string(e.what()));
} else {
throw GuiError(e.what());
}
auto msg = fmt::format("While reading reply from {} {}", to_lower(socketType), e.what());
throwError(msg);
}
}
+116 -22
View File
@@ -1,12 +1,14 @@
// SPDX-License-Identifier: LGPL-3.0-or-other
// Copyright (C) 2021 Contributors to the SLS Detector Package
#include "sls/DataSocket.h"
#include "sls/Timer.h"
#include "sls/logger.h"
#include "sls/sls_detector_exceptions.h"
#include "sls/sls_detector_funcs.h"
#include <algorithm>
#include <arpa/inet.h>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <iostream>
@@ -21,6 +23,14 @@ namespace sls {
DataSocket::DataSocket(int socketId) : sockfd_(socketId) {
int value = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
#ifdef SO_NOSIGPIPE
// macOS/BSD: suppress SIGPIPE when sending to a peer that closed the
// connection, so a failed send returns an error instead of killing the
// process. On Linux we instead pass MSG_NOSIGNAL to send() (see Send()).
int nosigpipe = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe,
sizeof(nosigpipe));
#endif
}
DataSocket::~DataSocket() {
@@ -36,6 +46,7 @@ DataSocket::~DataSocket() {
void DataSocket::swap(DataSocket &other) noexcept {
std::swap(sockfd_, other.sockfd_);
std::swap(fnum_, other.fnum_);
}
DataSocket::DataSocket(DataSocket &&move) noexcept { move.swap(*this); }
@@ -48,23 +59,34 @@ void DataSocket::setFnum(const int fnum) { fnum_ = fnum; }
int DataSocket::Receive(void *buffer, size_t size) {
// TODO!(Erik) Add sleep? how many reties?
int bytes_expected = static_cast<int>(size); // signed size
int bytes_read = 0;
ssize_t bytes_expected = static_cast<ssize_t>(size);
ssize_t bytes_read = 0;
ssize_t this_read = 0; // last read result, kept for diagnostics
Timer timer;
while (bytes_read < bytes_expected) {
auto this_read =
this_read =
::read(getSocketId(), reinterpret_cast<char *>(buffer) + bytes_read,
bytes_expected - bytes_read);
if (this_read < 0 && errno == EINTR)
continue; // interrupted by a signal, retry
if (this_read <= 0)
break;
bytes_read += this_read;
}
if (bytes_read == bytes_expected) {
return bytes_read;
return static_cast<int>(bytes_read);
} else {
int err = errno; // capture before any other call can clobber it
std::ostringstream ss;
ss << "TCP socket read " << bytes_read << " bytes instead of "
<< bytes_expected << " bytes ("
<< getFunctionNameFromEnum(static_cast<detFuncs>(fnum_)) << ')';
if (this_read == 0)
ss << ": connection closed by peer (EOF)";
else if (this_read < 0)
ss << ": read error: " << std::strerror(err) << " ("
<< errno_name(err) << ")";
ss << " after " << timer.elapsed_ms() << " ms";
throw SocketError(ss.str());
}
}
@@ -78,38 +100,51 @@ std::string DataSocket::Receive(size_t length) {
return buff;
}
int DataSocket::Send(const void *buffer, size_t size) {
int bytes_sent = 0;
int data_size = static_cast<int>(size); // signed size
while (bytes_sent < (data_size)) {
auto this_send = ::write(getSocketId(), buffer, size);
ssize_t bytes_expected = static_cast<ssize_t>(size);
ssize_t bytes_sent = 0;
ssize_t this_send = 0; // last write result, kept for diagnostics
// Linux: avoid SIGPIPE on a broken connection by using send() with
// MSG_NOSIGNAL. macOS/BSD lack the flag and use SO_NOSIGPIPE instead
// (set in the constructor).
#ifdef MSG_NOSIGNAL
const int send_flags = MSG_NOSIGNAL;
#else
const int send_flags = 0;
#endif
Timer timer;
while (bytes_sent < bytes_expected) {
this_send = ::send(
getSocketId(),
reinterpret_cast<const char *>(buffer) + bytes_sent,
bytes_expected - bytes_sent, send_flags);
if (this_send < 0 && errno == EINTR)
continue; // interrupted by a signal, retry
if (this_send <= 0)
break;
bytes_sent += this_send;
}
if (bytes_sent != data_size) {
if (bytes_sent == bytes_expected) {
return static_cast<int>(bytes_sent);
} else {
int err = errno; // capture before any other call can clobber it
std::ostringstream ss;
ss << "TCP socket sent " << bytes_sent << " bytes instead of "
<< data_size << " bytes ("
<< bytes_expected << " bytes ("
<< getFunctionNameFromEnum(static_cast<detFuncs>(fnum_)) << ')';
if (this_send < 0)
ss << ": write error: " << std::strerror(err) << " ("
<< errno_name(err) << ")";
ss << " after " << timer.elapsed_ms() << " ms";
throw SocketError(ss.str());
}
return bytes_sent;
}
int DataSocket::Send(const std::string &s) { return Send(&s[0], s.size()); }
int DataSocket::write(void *buffer, size_t size) {
return ::write(getSocketId(), buffer, size);
}
int DataSocket::read(void *buffer, size_t size) {
return ::read(getSocketId(), buffer, size);
}
int DataSocket::setReceiveTimeout(int us) {
timeval t{};
t.tv_sec = 0;
t.tv_usec = us;
t.tv_sec = us / 1000000;
t.tv_usec = us % 1000000;
return ::setsockopt(getSocketId(), SOL_SOCKET, SO_RCVTIMEO, &t,
sizeof(struct timeval));
}
@@ -140,7 +175,9 @@ int DataSocket::setTimeOut(int t_seconds) {
void DataSocket::close() {
if (sockfd_ > 0) {
if (::close(sockfd_)) {
throw SocketError("could not close socket");
std::stringstream ss;
ss << "could not close socket (fd: " << sockfd_ << ")";
throw SocketError(ss.str());
}
sockfd_ = -1;
} else {
@@ -155,4 +192,61 @@ void DataSocket::shutDownSocket() {
void DataSocket::shutdown() { ::shutdown(sockfd_, SHUT_RDWR); }
std::string_view DataSocket::errno_name(int e) {
switch (e) {
#ifdef EACCES
case EACCES:
return "EACCES";
#endif
#ifdef EAGAIN
case EAGAIN:
return "EAGAIN";
#endif
#ifdef EBADF
case EBADF:
return "EBADF";
#endif
#ifdef ECONNABORTED
case ECONNABORTED:
return "ECONNABORTED";
#endif
#ifdef ECONNREFUSED
case ECONNREFUSED:
return "ECONNREFUSED";
#endif
#ifdef ECONNRESET
case ECONNRESET:
return "ECONNRESET";
#endif
#ifdef EINPROGRESS
case EINPROGRESS:
return "EINPROGRESS";
#endif
#ifdef EINTR
case EINTR:
return "EINTR";
#endif
#ifdef EINVAL
case EINVAL:
return "EINVAL";
#endif
#ifdef EPIPE
case EPIPE:
return "EPIPE";
#endif
#ifdef ETIMEDOUT
case ETIMEDOUT:
return "ETIMEDOUT";
#endif
#ifdef EWOULDBLOCK
#if EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
return "EWOULDBLOCK";
#endif
#endif
default:
return "UNKNOWN_ERRNO";
}
}
} // namespace sls
+3 -3
View File
@@ -10,16 +10,16 @@ namespace sls {
int ServerInterface::sendResult(int ret, void *retval, int retvalSize,
char *mess) {
write(&ret, sizeof(ret));
Send(&ret, sizeof(ret));
if (ret == defs::FAIL) {
if (mess != nullptr) {
write(mess, MAX_STR_LENGTH);
Send(mess, MAX_STR_LENGTH);
} else {
LOG(logERROR) << "No error message provided for this "
"failure. Will mess up TCP\n";
}
} else {
write(retval, retvalSize);
Send(retval, retvalSize);
}
return ret;
}
+7
View File
@@ -75,4 +75,11 @@ std::pair<std::string, uint16_t> ParseHostPort(const std::string &s) {
return std::make_pair(host, port);
}
std::string to_lower(const std::string &s) {
std::string result = s;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
}; // namespace sls
+291 -10
View File
@@ -3,37 +3,117 @@
#include "catch.hpp"
#include "sls/ClientSocket.h"
#include "sls/ServerSocket.h"
#include "sls/Timer.h"
#include "sls/sls_detector_defs.h"
#include "sls/sls_detector_exceptions.h"
#include "sls/sls_detector_funcs.h"
#include <atomic>
#include <chrono>
#include <future>
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/time.h>
#include <thread>
#include <unistd.h>
namespace sls {
std::vector<char> server() {
std::cout << "starting server\n";
auto server = ServerSocket(1950);
// One configurable test server: accept a connection, read a 100-byte request,
// reply with `bytes_to_send` bytes (first two set to 'O','K'), optionally wait
// `hold` so the client can time out, then close. Returns the received request.
std::vector<char> echo_server(uint16_t port, size_t bytes_to_send,
std::chrono::milliseconds hold) {
std::cout << "starting server on port " << port << '\n';
auto server = ServerSocket(port);
auto s = server.accept();
std::vector<char> buffer(100, '\0');
s.Receive(buffer.data(), buffer.size());
std::cout << "ServerReceived: " << std::string(buffer.begin(), buffer.end())
<< '\n';
std::vector<char> to_send(100, '\0');
to_send[0] = 'O';
to_send[1] = 'K';
s.Send(to_send.data(), to_send.size());
if (port==1960){
struct linger ling = {
.l_onoff = 1,
.l_linger = 0
};
auto fd = s.getSocketId();
setsockopt(fd, SOL_SOCKET, SO_LINGER, &ling, sizeof ling);
::close(fd);
return buffer;
}
if (bytes_to_send > 0) {
std::vector<char> to_send(bytes_to_send, '\0');
to_send[0] = 'O';
to_send[1] = 'K';
s.Send(to_send.data(), to_send.size());
}
std::this_thread::sleep_for(hold);
s.close();
return buffer;
}
// Minimal command server speaking the same protocol as sendCommandThenRead:
// accept a connection, read the function number and a single int argument,
// then reply via ServerInterface::sendResult with OK and (arg * 2). Returns
// the {fnum, arg} pair the server received so the test can verify them.
std::pair<int, int> command_server(uint16_t port) {
auto server = ServerSocket(port);
auto s = server.accept();
int fnum = -1;
int arg = 0;
s.Receive(&fnum, sizeof(fnum));
s.Receive(&arg, sizeof(arg));
int retval = arg * 2;
s.sendResult(slsDetectorDefs::OK, &retval, sizeof(retval));
s.close();
return {fnum, arg};
}
// Command server that replies with a truncated message: it reads the request,
// sends the OK return code, but then sends fewer retval bytes than the client
// expects before closing, so the client's Receive hits EOF mid-read.
void short_reply_server(uint16_t port, size_t retval_bytes_to_send) {
auto server = ServerSocket(port);
auto s = server.accept();
int fnum = -1;
int arg = 0;
s.Receive(&fnum, sizeof(fnum));
s.Receive(&arg, sizeof(arg));
int ret = slsDetectorDefs::OK;
s.Send(&ret, sizeof(ret));
if (retval_bytes_to_send > 0) {
std::vector<char> partial(retval_bytes_to_send, '\0');
s.Send(partial.data(), partial.size());
}
s.close();
}
// Server that accepts a connection but never reads from it, so a client
// trying to send more than fits in the kernel buffers will stall. A small
// receive buffer keeps the amount the test must send modest. Stays open until
// the client signals it is done (or a safety timeout) so it never closes
// mid-transfer and races the client's Send.
void non_reading_server(uint16_t port, std::atomic<bool> *client_done) {
auto server = ServerSocket(port);
auto s = server.accept();
int rcvbuf = 1024;
setsockopt(s.getSocketId(), SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
// Intentionally never Receive(): let the client's send path back up.
Timer t;
while (!client_done->load() && t.elapsed_ms() < 10000)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
s.close();
}
TEST_CASE("The server recive the same message as we send", "[support]") {
std::vector<char> received_message(100, '\0');
std::vector<char> sent_message(100, '\0');
const char m[]{"some message"};
std::copy(std::begin(m), std::end(m), sent_message.data());
auto s = std::async(std::launch::async, server);
auto s = std::async(std::launch::async, echo_server, 1950, 100,
std::chrono::milliseconds(0));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", 1950);
client.Send(sent_message.data(), sent_message.size());
@@ -48,6 +128,207 @@ TEST_CASE("The server recive the same message as we send", "[support]") {
TEST_CASE("throws on no server", "[support]") {
CHECK_THROWS(DetectorSocket("localhost", 1950));
CHECK_THROWS(ReceiverSocket("localhost", 1950));
CHECK_THROWS(GuiSocket("localhost", 1950));
}
TEST_CASE("Receiving a too short message throws and reports EOF", "[support]") {
std::vector<char> received_message(100, '\0');
std::vector<char> sent_message(100, '\0');
const char m[]{"some message"};
std::copy(std::begin(m), std::end(m), sent_message.data());
// Server replies with only 10 of the 100 expected bytes, then closes.
auto s = std::async(std::launch::async, echo_server, 1951, 10,
std::chrono::milliseconds(0));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", 1951);
client.Send(sent_message.data(), sent_message.size());
client.setFnum(F_GET_SERVER_VERSION);
// The server only sends 10 of the 100 expected bytes and then closes the
// connection, so Receive must throw a SocketError reporting the EOF.
std::string error_message;
try {
client.Receive(received_message.data(), received_message.size());
FAIL("Receive should have thrown on a too short message");
} catch (const SocketError &e) {
error_message = e.what();
}
client.close();
s.get();
CHECK_THAT(error_message,
Catch::Matchers::Contains("read 10 bytes instead of 100 bytes"));
CHECK_THAT(error_message,
Catch::Matchers::Contains("connection closed by peer (EOF)"));
}
TEST_CASE("Receiving with a socket error throws and reports the error",
"[support]") {
std::vector<char> received_message(100, '\0');
std::vector<char> sent_message(100, '\0');
const char m[]{"some message"};
std::copy(std::begin(m), std::end(m), sent_message.data());
// Server stays silent (sends nothing) but keeps the connection open long
// enough for the client to time out, so the read fails with an error.
auto s = std::async(std::launch::async, echo_server, 1952, 0,
std::chrono::milliseconds(500));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", 1952);
client.Send(sent_message.data(), sent_message.size());
// Force read() to fail with EAGAIN/EWOULDBLOCK instead of returning EOF by
// setting a short receive timeout while the server stays silent.
client.setReceiveTimeout(100000); // 100 ms
std::string error_message;
try {
client.Receive(received_message.data(), received_message.size());
FAIL("Receive should have thrown on a socket error");
} catch (const SocketError &e) {
error_message = e.what();
}
client.close();
s.get();
CHECK_THAT(error_message,
Catch::Matchers::Contains("read 0 bytes instead of 100 bytes"));
CHECK_THAT(error_message, Catch::Matchers::Contains("read error:"));
}
TEST_CASE("Socket crash?", "[support]") {
std::vector<char> received_message(100, '\0');
std::vector<char> sent_message(100, '\0');
const char m[]{"some message"};
std::copy(std::begin(m), std::end(m), sent_message.data());
auto s = std::async(std::launch::async, echo_server, 1960, 100,
std::chrono::milliseconds(0));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", 1960);
client.Send(sent_message.data(), sent_message.size());
REQUIRE_THROWS(client.Receive(received_message.data(), received_message.size()));
//Now try to send more
// client.Send(sent_message.data(), sent_message.size());
}
TEST_CASE("ClientSocket throws on invalid hostname", "[support]") {
CHECK_THROWS(ReceiverSocket("invalidhostname", 1950));
CHECK_THROWS(DetectorSocket("invalidhostname", 1950));
CHECK_THROWS(GuiSocket("invalidhostname", 1950));
}
TEST_CASE("Using DetectorSocket to talk to a Server Socket", "[support]") {
constexpr uint16_t port = 1961;
constexpr int fnum = F_GET_DETECTOR_TYPE;
constexpr int arg = 21;
auto s = std::async(std::launch::async, command_server, port);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", port);
int retval = 0;
int ret =
client.sendCommandThenRead(fnum, &arg, sizeof(arg), &retval,
sizeof(retval));
client.close();
auto server_received = s.get();
// Client got OK and the expected return value back from the server
CHECK(ret == slsDetectorDefs::OK);
CHECK(retval == arg * 2);
// Server received the function number and argument we sent
CHECK(server_received.first == fnum);
CHECK(server_received.second == arg);
// close() resets the underlying fd
CHECK(client.getSocketId() == -1);
}
TEST_CASE("ServerSocket replies with a too short message", "[support]") {
constexpr uint16_t port = 1962;
constexpr int fnum = F_GET_DETECTOR_TYPE;
constexpr int arg = 21;
// Server sends the OK return code, then only 1 of the 4 expected retval
// bytes before closing.
auto s = std::async(std::launch::async, short_reply_server, port, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", port);
int retval = 0;
std::string error_message;
try {
client.sendCommandThenRead(fnum, &arg, sizeof(arg), &retval,
sizeof(retval));
FAIL("sendCommandThenRead should have thrown on a too short message");
} catch (const DetectorError &e) {
error_message = e.what();
}
client.close();
s.get();
// The client read only 1 of the 4 expected retval bytes and then hit EOF.
CHECK_THAT(error_message,
Catch::Matchers::Contains("read 1 bytes instead of 4 bytes"));
CHECK_THAT(error_message,
Catch::Matchers::Contains("connection closed by peer (EOF)"));
}
TEST_CASE("Client cannot send the expected number of bytes", "[support]") {
constexpr uint16_t port = 1963;
// Server accepts but never reads; it stays open until we tell it the
// client is done, so it cannot close mid-transfer.
std::atomic<bool> client_done{false};
auto s = std::async(std::launch::async, non_reading_server, port,
&client_done);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", port);
// Shrink the send buffer and add a short send timeout so the write stalls
// and returns before all the data is sent.
int sndbuf = 4096;
setsockopt(client.getSocketId(), SOL_SOCKET, SO_SNDBUF, &sndbuf,
sizeof(sndbuf));
struct timeval tv {};
tv.tv_sec = 0;
tv.tv_usec = 300000; // 300 ms
setsockopt(client.getSocketId(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
// Much more than fits in the (shrunken) send + receive buffers, so the
// send cannot complete while the server refuses to read.
std::vector<char> big_message(8 * 1024 * 1024, '\0');
std::string error_message;
try {
client.Send(big_message.data(), big_message.size());
FAIL("Send should have thrown when it could not send all bytes");
} catch (const SocketError &e) {
error_message = e.what();
}
client_done = true;
client.close();
s.get();
// Fewer bytes were sent than expected, reported as a write error (the
// send timed out with EAGAIN/EWOULDBLOCK).
CHECK_THAT(error_message, Catch::Matchers::Contains("bytes instead of"));
CHECK_THAT(error_message, Catch::Matchers::Contains("write error:"));
}
} // namespace sls
+27
View File
@@ -123,6 +123,33 @@ TEST_CASE("port missing") {
REQUIRE(res.second == 0);
}
TEST_CASE("to_lower converts uppercase to lowercase") {
REQUIRE(to_lower("HELLO") == "hello");
REQUIRE(to_lower("Hello World") == "hello world");
}
TEST_CASE("to_lower leaves an already lowercase string unchanged") {
REQUIRE(to_lower("already lower") == "already lower");
}
TEST_CASE("to_lower only affects alphabetic characters") {
REQUIRE(to_lower("ABC123!?_-XYZ") == "abc123!?_-xyz");
}
TEST_CASE("to_lower on an empty string returns an empty string") {
REQUIRE(to_lower("").empty());
}
TEST_CASE("to_lower does not modify the original string") {
std::string original = "MixedCase";
auto result = to_lower(original);
REQUIRE(result == "mixedcase");
// the source string must be untouched
REQUIRE(original == "MixedCase");
}
// TEST_CASE("concat things not being strings")
} // namespace sls