testexecutor-ui 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. testexecutor/VERSION +1 -0
  2. testexecutor/__init__.py +6 -0
  3. testexecutor/control/TestListenerApi.py +56 -0
  4. testexecutor/control/TestSuiteListener.py +249 -0
  5. testexecutor/control/__init__.py +0 -0
  6. testexecutor/model/GenericListModel.py +63 -0
  7. testexecutor/model/InstructionsModel.py +351 -0
  8. testexecutor/model/KeyValueModel.py +177 -0
  9. testexecutor/model/MultiTestWindowModel.py +184 -0
  10. testexecutor/model/ResultModel.py +314 -0
  11. testexecutor/model/TestSuiteControlModel.py +139 -0
  12. testexecutor/model/TestSuiteGroup.py +105 -0
  13. testexecutor/model/TestSuiteModel.py +301 -0
  14. testexecutor/model/__init__.py +0 -0
  15. testexecutor/model/selector/FilterGroupModel.py +90 -0
  16. testexecutor/model/selector/FilterModel.py +251 -0
  17. testexecutor/model/selector/MultiSelectDetails.py +106 -0
  18. testexecutor/model/selector/MultiSelectListModel.py +48 -0
  19. testexecutor/model/selector/__init__.py +0 -0
  20. testexecutor/ui/IdentificationWidget.qml +40 -0
  21. testexecutor/ui/InputWidget.qml +74 -0
  22. testexecutor/ui/InstructionWidget.qml +185 -0
  23. testexecutor/ui/KeyValueItem.qml +194 -0
  24. testexecutor/ui/KeyValueItemConfig.qml +41 -0
  25. testexecutor/ui/KeyValueList.qml +49 -0
  26. testexecutor/ui/MultiTestWidget.qml +48 -0
  27. testexecutor/ui/MultiTestWindow.qml +88 -0
  28. testexecutor/ui/ResultItem.qml +199 -0
  29. testexecutor/ui/ResultItemConfig.qml +46 -0
  30. testexecutor/ui/ResultList.qml +58 -0
  31. testexecutor/ui/TestSuiteConfig.qml +95 -0
  32. testexecutor/ui/TestSuiteController.qml +54 -0
  33. testexecutor/ui/TestSuiteWidget.qml +350 -0
  34. testexecutor/ui/selector/FilterItem.qml +75 -0
  35. testexecutor/ui/selector/FilterItemConfig.qml +24 -0
  36. testexecutor/ui/selector/FilterMultiSelectorItem.qml +169 -0
  37. testexecutor/ui/selector/FilterMultiSelectorItemConfig.qml +22 -0
  38. testexecutor/ui/selector/FilterMultiSelectorList.qml +50 -0
  39. testexecutor/ui/selector/HeaderConfig.qml +24 -0
  40. testexecutor/ui/selector/SectionConfig.qml +24 -0
  41. testexecutor/ui/selector/SectionDelegate.qml +85 -0
  42. testexecutor/ui/selector/SelectorHeader.qml +43 -0
  43. testexecutor/ui/selector/SuiteList.qml +150 -0
  44. testexecutor/ui/selector/SuiteSelector.qml +71 -0
  45. testexecutor/ui/selector/SuiteSelectorConfig.qml +38 -0
  46. testexecutor/ui/selector/TestConfig.qml +22 -0
  47. testexecutor/ui/selector/TestDelegate.qml +75 -0
  48. testexecutor/ui/te-64x64.ico +0 -0
  49. testexecutor_ui-0.6.1.dist-info/LICENSE +204 -0
  50. testexecutor_ui-0.6.1.dist-info/METADATA +27 -0
  51. testexecutor_ui-0.6.1.dist-info/NOTICE +3 -0
  52. testexecutor_ui-0.6.1.dist-info/RECORD +54 -0
  53. testexecutor_ui-0.6.1.dist-info/WHEEL +5 -0
  54. testexecutor_ui-0.6.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,351 @@
1
+ # Copyright 2020 Direkt, Australia
2
+ # Copyright 2021 Direkt Embedded Pty Ltd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # This Python file uses the following encoding: utf-8
17
+ import threading
18
+ from PySide6.QtCore import Slot
19
+ from PySide6.QtCore import Signal
20
+ from PySide6.QtCore import Property
21
+ from PySide6.QtCore import QObject
22
+ from PySide6.QtGui import Qt
23
+
24
+
25
+ class ControlButtonConfig(QObject):
26
+
27
+ def __init__(self, text=None):
28
+ QObject.__init__(self)
29
+ self.lock = threading.RLock()
30
+ self._text = text
31
+
32
+ def _settext(self, text):
33
+ """ Setter for text Property """
34
+ en = self.enabled
35
+ changed = False
36
+ with self.lock:
37
+ if self._text != text:
38
+ self._text = text
39
+ changed = True
40
+ if changed:
41
+ self.text_changed.emit()
42
+ if en is not self.enabled:
43
+ self.enable_changed.emit()
44
+
45
+ def _gettext(self):
46
+ """ Getter for text Property """
47
+ with self.lock:
48
+ t = self._text
49
+ return t
50
+
51
+ text_changed = Signal()
52
+ text = Property(str, _gettext, _settext, notify=text_changed)
53
+
54
+ def _getenabled(self):
55
+ """ Getter for enabled Property """
56
+ en = (self._text is not None) and (self._text != "")
57
+ return en
58
+
59
+ enable_changed = Signal()
60
+ enabled = Property(bool, _getenabled, None, notify=enable_changed)
61
+
62
+
63
+ class InstructionControl(QObject):
64
+
65
+ def __init__(self):
66
+ QObject.__init__(self)
67
+ self.lock = threading.RLock()
68
+ self._buttons = [ControlButtonConfig(), ControlButtonConfig()]
69
+ self._controlReceived = None
70
+ self._inputReceived = None
71
+ self._expectInput = False
72
+ self.userInputEvent = threading.Event()
73
+
74
+ onUserInput = Signal(str, str)
75
+
76
+ def requestDecision(self, buttontextList):
77
+ with self.lock:
78
+ self._controlReceived = None
79
+ self._inputReceived = None
80
+ return self.setButtons(buttontextList)
81
+
82
+ def lastUserDecision(self):
83
+ """
84
+ Obtain the last user decision that was chosen by the user, or None if no decision pending.
85
+ The decision is not cleared until the next call to requestDecision.
86
+ Current implementation returns the lower case of the text on the control buttons. This is not a clean solution
87
+ and should be changed by adding a decision string list to requestDecision instead.
88
+ :return: decision string
89
+ """
90
+ with self.lock:
91
+ decision = self._controlReceived
92
+ if decision:
93
+ decision = decision.lower()
94
+ return decision
95
+
96
+ def lastUserInput(self):
97
+ """
98
+ Obtain the last user input decision that was chosen by the user, or None if no decision pending.
99
+ The decision is not cleared until the next call to requestDecision.
100
+ :return: decision string
101
+ """
102
+ with self.lock:
103
+ input = self._inputReceived
104
+ return input
105
+
106
+ def clear(self):
107
+ self.cancelWaiting()
108
+ self.setButtons([])
109
+
110
+ @Slot(str)
111
+ def onControl(self, decision):
112
+ """
113
+ A callback called once a button has been pressed in the instruction window.
114
+ onControl does not clear the instruction window, as the caller may wish to have multiple callbacks.
115
+ This means the caller needs to clear the instruction window when it wants to.
116
+ :param decision: The button press or decision made
117
+ :return:
118
+ """
119
+ self._onControl(decision)
120
+
121
+ def _onControl(self, decision, input_value=None):
122
+ with self.lock:
123
+ self._controlReceived = decision
124
+ self._inputReceived = input_value
125
+ self.userInputEvent.set()
126
+ self.onUserInput.emit(self.lastUserDecision(), self.lastUserInput())
127
+
128
+ @Slot(str)
129
+ def onExternalInput(self, input_value):
130
+ """
131
+ A callback providing input from a relevant external control. The control should be supplied externally to
132
+ this component and provide complete string input.
133
+ """
134
+ if input_value:
135
+ self._onControl(self.USER_INPUT, input_value)
136
+
137
+ def cancelWaiting(self):
138
+ self._controlReceived = None
139
+ self.userInputEvent.set()
140
+
141
+ def setButtons(self, buttonTextList):
142
+ info = buttonTextList
143
+ if type(buttonTextList) is str:
144
+ info = [buttonTextList]
145
+ with self.lock:
146
+ if type(info) is list:
147
+ i = 0
148
+ for txt in info:
149
+ self._buttons[i].text = txt
150
+ i = i + 1
151
+ for j in range(i, len(self._buttons)):
152
+ self._buttons[j].text = None
153
+ present = False
154
+ if buttonTextList:
155
+ present = len(info) > 0
156
+ return present
157
+
158
+ def lastWasUserInput(self):
159
+ return self.lastUserDecision() == self.USER_INPUT
160
+
161
+ # TODO: Consider modifying to provide more buttons, rather than fixed right left.
162
+ def _getleftButton(self):
163
+ """ Getter for leftButton Property """
164
+ with self.lock:
165
+ b = self._buttons[0]
166
+ return b
167
+
168
+ leftButton_changed = Signal()
169
+ leftButton = Property(QObject, _getleftButton, None, notify=leftButton_changed)
170
+
171
+ def _getrightButton(self):
172
+ """ Getter for rightButton Property """
173
+ with self.lock:
174
+ b = self._buttons[1]
175
+ return b
176
+
177
+ rightButton_changed = Signal()
178
+ rightButton = Property(QObject, _getrightButton, None, notify=rightButton_changed)
179
+
180
+ USER_INPUT = 'te_input'
181
+
182
+ class InstructionModel(QObject):
183
+
184
+ def __init__(self):
185
+ QObject.__init__(self)
186
+ self.lock = threading.RLock()
187
+ self._instructionText = None
188
+ self._control = InstructionControl()
189
+ self._enabled = False
190
+ self._input_hidden = False
191
+ self._instructionTitle = None
192
+ self._instructionText = None
193
+
194
+ def clear(self):
195
+ self.instructionTitle = ""
196
+ self.instructionText = ""
197
+ self.control.clear()
198
+
199
+ @Slot(str, str, list, bool)
200
+ def userDecision(self, title, message, control, enable=True):
201
+ """
202
+ Method called to send instructions to the user
203
+ The caller will use control.lastUserDecision() to obtain the decision chosen, implementing a wait state
204
+ if required.
205
+ If no control information is given, then this model will remain disabled as their is no expectation for the
206
+ user to provide input.
207
+ :param name: unique name of the test, not used
208
+ :param message: class containing information to display to the user
209
+ :param control: list of text to display on decision/control buttons
210
+ :param enable: enable text widget even if no buttons so highlighted colour is shown
211
+ :return: None
212
+ """
213
+ self.instructionTitle = title
214
+ self.instructionText = message
215
+ buttons_enabled = self.control.requestDecision(control)
216
+ self._internal_setenabled(buttons_enabled | enable)
217
+
218
+ @Slot(str, str, list, str, bool, tuple, bool)
219
+ def userInputRequest(self, title, message, control, default_value=None, hidden=False, values=None, enable=True):
220
+ """
221
+ Method called to send instructions to the user
222
+ The caller will use control.lastUserDecision() to obtain the decision chosen, implementing a wait state
223
+ if required.
224
+ If no control information is given, then this model will remain disabled as their is no expectation for the
225
+ user to provide input.
226
+ :param name: unique name of the test, not used
227
+ :param message: class containing information to display to the user
228
+ :param control: list of text to display on decision/control buttons
229
+ :param default_value: value to provide as a default to the user - future
230
+ :param values: enable text widget even if no buttons so highlighted colour is shown - future
231
+ :param enable: enable text widget even if no buttons so highlighted colour is shown
232
+ :param hidden: hide text as entered
233
+ :return: None
234
+ """
235
+ self.instructionTitle = title
236
+ self.instructionText = message
237
+ self.input_hidden = hidden
238
+ buttons_enabled = self.control.requestDecision(control)
239
+ self._internal_setenabled(buttons_enabled | enable)
240
+
241
+ def userDecisionWait(self):
242
+ """
243
+ If the caller wishes to block until the user decision requested is made, they can call this immediately
244
+ after calling userDecision.
245
+ """
246
+ self.control.userInputEvent.clear()
247
+ self.control.userInputEvent.wait()
248
+
249
+ def _setinstructionText(self, instructionText):
250
+ """ Setter for instructionText Property """
251
+ changed = False
252
+ with self.lock:
253
+ if self._instructionText != instructionText:
254
+ changed = True
255
+ self._instructionText = instructionText
256
+ if instructionText and "html" not in instructionText.lower():
257
+ self._instructionText = Qt.convertFromPlainText(instructionText, Qt.WhiteSpaceNormal)
258
+
259
+ if changed:
260
+ self.instructionText_changed.emit()
261
+
262
+ def _getinstructionText(self):
263
+ """ Getter for instructionText Property """
264
+ with self.lock:
265
+ instr = self._instructionText
266
+ return instr
267
+
268
+ instructionText_changed = Signal()
269
+ instructionText = Property(str, _getinstructionText, _setinstructionText, notify=instructionText_changed)
270
+
271
+ def _setinstructionTitle(self, instructionTitle):
272
+ """ Setter for instructionTitle Property """
273
+ changed = False
274
+ with self.lock:
275
+ if self._instructionTitle != instructionTitle:
276
+ self._instructionTitle = instructionTitle
277
+ changed = True
278
+ if changed:
279
+ self.instructionTitle_changed.emit()
280
+
281
+ def _getinstructionTitle(self):
282
+ """ Getter for instructionTitle Property """
283
+ with self.lock:
284
+ t = self._instructionTitle
285
+ return t
286
+
287
+ instructionTitle_changed = Signal()
288
+ instructionTitle = Property(str, _getinstructionTitle, _setinstructionTitle, notify=instructionTitle_changed)
289
+
290
+ def _setcontrol(self, control):
291
+ """ Setter for control Property """
292
+ changed = False
293
+ with self.lock:
294
+ if self._control != control:
295
+ self._control = control
296
+ changed = True
297
+ if changed:
298
+ self.control_changed.emit()
299
+
300
+ def _getcontrol(self):
301
+ """ Getter for control Property """
302
+ with self.lock:
303
+ c = self._control
304
+ return c
305
+
306
+ control_changed = Signal()
307
+ control = Property(QObject, _getcontrol, _setcontrol, notify=control_changed)
308
+
309
+ def _internal_setenabled(self, enabled):
310
+ changed = False
311
+ with self.lock:
312
+ if self._enabled != enabled:
313
+ self._enabled = enabled
314
+ changed = True
315
+ if changed:
316
+ self.enabled_changed.emit()
317
+
318
+ def _setenabled(self, enabled):
319
+ """ Setter for enabled Property """
320
+ if not enabled:
321
+ # If we have been disabled, ensure there is no pending control blocking operation
322
+ self.control.cancelWaiting()
323
+ self._internal_setenabled(enabled)
324
+
325
+ def _getenabled(self):
326
+ """ Getter for enabled Property """
327
+ with self.lock:
328
+ e = self._enabled
329
+ return e
330
+
331
+ enabled_changed = Signal()
332
+ enabled = Property(bool, _getenabled, _setenabled, notify=enabled_changed)
333
+
334
+ def _set_input_hidden(self, input_hidden):
335
+ """ Setter for _input_hidden Property """
336
+ changed = False
337
+ with self.lock:
338
+ if self._input_hidden != input_hidden:
339
+ self._input_hidden = input_hidden
340
+ changed = True
341
+ if changed:
342
+ self.input_hidden_changed.emit()
343
+
344
+ def _get_input_hidden(self):
345
+ """ Getter for _input_hidden Property """
346
+ with self.lock:
347
+ e = self._input_hidden
348
+ return e
349
+
350
+ input_hidden_changed = Signal()
351
+ input_hidden = Property(bool, _get_input_hidden, _set_input_hidden, notify=input_hidden_changed)
@@ -0,0 +1,177 @@
1
+ # Copyright 2020 Direkt, Australia
2
+ # Copyright 2021 Direkt Embedded Pty Ltd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # This Python file uses the following encoding: utf-8
17
+ import threading
18
+ from PySide6.QtCore import QAbstractListModel
19
+ from PySide6.QtCore import Qt
20
+ from PySide6.QtCore import QObject
21
+ from PySide6.QtCore import QModelIndex
22
+ from PySide6.QtCore import Property, Signal
23
+
24
+
25
+ class KeyValue(QObject):
26
+ def __init__(self, label, value):
27
+ QObject.__init__(self)
28
+ self.lock = threading.RLock()
29
+ self._value = value
30
+ self._label = label
31
+
32
+ def _setvalue(self, value):
33
+ """ Setter for value Property """
34
+ changed = False
35
+ with self.lock:
36
+ if self._value != value:
37
+ self._value = value
38
+ changed = True
39
+ if changed:
40
+ self.value_changed.emit()
41
+
42
+ def _getvalue(self):
43
+ """ Getter for value Property """
44
+ with self.lock:
45
+ v = self._value
46
+ return v
47
+
48
+ value_changed = Signal()
49
+ value = Property(str, _getvalue, _setvalue, notify=value_changed)
50
+
51
+ def _setlabel(self, label):
52
+ """ Setter for label Property """
53
+ changed = False
54
+ with self.lock:
55
+ if self._label != label:
56
+ self._label = label
57
+ changed = True
58
+ if changed:
59
+ self.label_changed.emit()
60
+
61
+ def _getlabel(self):
62
+ """ Getter for label Property """
63
+ with self.lock:
64
+ l = self._label
65
+ return l
66
+
67
+ label_changed = Signal()
68
+ label = Property(str, _getlabel, _setlabel, notify=label_changed)
69
+
70
+
71
+
72
+ class KeyValueModel(QAbstractListModel):
73
+
74
+ KeyRole = Qt.UserRole + 1
75
+ ValueRole = Qt.UserRole + 2
76
+ PossibleValuesRole = Qt.UserRole + 3
77
+ KeyKey = b'key'
78
+ ValueKey = b'value'
79
+ PossibleValuesKey = b'possibles'
80
+
81
+ def __init__(self, parent = None, clone = None):
82
+ QAbstractListModel.__init__(self, parent)
83
+ self.lock = threading.RLock()
84
+ if clone:
85
+ self._data = clone._data
86
+ else:
87
+ self._data = []
88
+
89
+ def rowCount(self, index):
90
+ return len(self._data)
91
+
92
+ def roleNames(self):
93
+ return {KeyValueModel.KeyRole: KeyValueModel.KeyKey,
94
+ KeyValueModel.PossibleValuesRole: KeyValueModel.PossibleValuesKey,
95
+ KeyValueModel.ValueRole: KeyValueModel.ValueKey}
96
+
97
+ def data(self, index, role=None):
98
+ with self.lock:
99
+ d = self._data[index.row()]
100
+ if role == KeyValueModel.KeyRole:
101
+ return d[KeyValueModel.KeyKey]
102
+ elif role == KeyValueModel.ValueRole:
103
+ return d[KeyValueModel.ValueKey]
104
+ elif role == KeyValueModel.PossibleValuesRole:
105
+ return d[KeyValueModel.PossibleValuesKey]
106
+ return None
107
+
108
+ def add(self, key, value, possibleValues=None):
109
+ if possibleValues is None:
110
+ possibleValues = []
111
+ rowCount = self.rowCount(QModelIndex())
112
+ self.beginInsertRows(QModelIndex(), rowCount, rowCount)
113
+ with self.lock:
114
+ self._data.append({KeyValueModel.KeyKey: key, KeyValueModel.ValueKey: value, KeyValueModel.PossibleValuesKey: possibleValues})
115
+ self.endInsertRows()
116
+
117
+ def setData(self, index, value, role=None):
118
+ with self.lock:
119
+ self._data[index.row()] = value
120
+ self.dataChanged.emit(index, index, self.roleNames())
121
+
122
+ def setValue(self, key, value=None, label=None):
123
+ changed = False
124
+ for row in range(len(self._data)):
125
+ with self.lock:
126
+ if self._data[row][KeyValueModel.KeyKey] == key:
127
+ if value:
128
+ self._data[row][KeyValueModel.ValueKey].value = value
129
+ if label:
130
+ self._data[row][KeyValueModel.ValueKey].label = label
131
+ break
132
+
133
+ def setLabel(self, key, label):
134
+ self.setValue(key, value=None, label=label)
135
+
136
+ def setPossibleValues(self, key, values):
137
+ with self.lock:
138
+ for row in range(len(self._data)):
139
+ if self._data[row][KeyValueModel.KeyKey] == key:
140
+ self._data[row][KeyValueModel.PossibleValuesKey] = values
141
+ ix = self.index(row, 0)
142
+ changed = True
143
+ break
144
+ if changed:
145
+ self.dataChanged.emit(ix, ix, self.roleNames())
146
+
147
+ def getValue(self, key):
148
+ value = None
149
+ for row in range(len(self._data)):
150
+ with self.lock:
151
+ if self._data[row][KeyValueModel.KeyKey] == key:
152
+ value = self._data[row][KeyValueModel.ValueKey].value
153
+ return value
154
+
155
+ def getValues(self):
156
+ """
157
+ Returns dictionary with key:value pairs as string:string. Note the label is not included.
158
+ :return: Returns dictionary with key:value pairs
159
+ """
160
+ values = {}
161
+ with self.lock:
162
+ for row in range(len(self._data)):
163
+ item = self._data[row]
164
+ values[item[KeyValueModel.KeyKey]] = item[KeyValueModel.ValueKey].value
165
+ return values
166
+
167
+ def clearData(self):
168
+ """
169
+ Clear the data content, namely the values and active value.
170
+ :return: None
171
+ """
172
+ for row in range(len(self._data)):
173
+ with self.lock:
174
+ self._data[row][KeyValueModel.ValueKey].value = ""
175
+ self._data[row][KeyValueModel.PossibleValuesKey] = []
176
+ ix = self.index(row, 0)
177
+ self.dataChanged.emit(ix, ix, self.roleNames())