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.
- testexecutor/VERSION +1 -0
- testexecutor/__init__.py +6 -0
- testexecutor/control/TestListenerApi.py +56 -0
- testexecutor/control/TestSuiteListener.py +249 -0
- testexecutor/control/__init__.py +0 -0
- testexecutor/model/GenericListModel.py +63 -0
- testexecutor/model/InstructionsModel.py +351 -0
- testexecutor/model/KeyValueModel.py +177 -0
- testexecutor/model/MultiTestWindowModel.py +184 -0
- testexecutor/model/ResultModel.py +314 -0
- testexecutor/model/TestSuiteControlModel.py +139 -0
- testexecutor/model/TestSuiteGroup.py +105 -0
- testexecutor/model/TestSuiteModel.py +301 -0
- testexecutor/model/__init__.py +0 -0
- testexecutor/model/selector/FilterGroupModel.py +90 -0
- testexecutor/model/selector/FilterModel.py +251 -0
- testexecutor/model/selector/MultiSelectDetails.py +106 -0
- testexecutor/model/selector/MultiSelectListModel.py +48 -0
- testexecutor/model/selector/__init__.py +0 -0
- testexecutor/ui/IdentificationWidget.qml +40 -0
- testexecutor/ui/InputWidget.qml +74 -0
- testexecutor/ui/InstructionWidget.qml +185 -0
- testexecutor/ui/KeyValueItem.qml +194 -0
- testexecutor/ui/KeyValueItemConfig.qml +41 -0
- testexecutor/ui/KeyValueList.qml +49 -0
- testexecutor/ui/MultiTestWidget.qml +48 -0
- testexecutor/ui/MultiTestWindow.qml +88 -0
- testexecutor/ui/ResultItem.qml +199 -0
- testexecutor/ui/ResultItemConfig.qml +46 -0
- testexecutor/ui/ResultList.qml +58 -0
- testexecutor/ui/TestSuiteConfig.qml +95 -0
- testexecutor/ui/TestSuiteController.qml +54 -0
- testexecutor/ui/TestSuiteWidget.qml +350 -0
- testexecutor/ui/selector/FilterItem.qml +75 -0
- testexecutor/ui/selector/FilterItemConfig.qml +24 -0
- testexecutor/ui/selector/FilterMultiSelectorItem.qml +169 -0
- testexecutor/ui/selector/FilterMultiSelectorItemConfig.qml +22 -0
- testexecutor/ui/selector/FilterMultiSelectorList.qml +50 -0
- testexecutor/ui/selector/HeaderConfig.qml +24 -0
- testexecutor/ui/selector/SectionConfig.qml +24 -0
- testexecutor/ui/selector/SectionDelegate.qml +85 -0
- testexecutor/ui/selector/SelectorHeader.qml +43 -0
- testexecutor/ui/selector/SuiteList.qml +150 -0
- testexecutor/ui/selector/SuiteSelector.qml +71 -0
- testexecutor/ui/selector/SuiteSelectorConfig.qml +38 -0
- testexecutor/ui/selector/TestConfig.qml +22 -0
- testexecutor/ui/selector/TestDelegate.qml +75 -0
- testexecutor/ui/te-64x64.ico +0 -0
- testexecutor_ui-0.6.1.dist-info/LICENSE +204 -0
- testexecutor_ui-0.6.1.dist-info/METADATA +27 -0
- testexecutor_ui-0.6.1.dist-info/NOTICE +3 -0
- testexecutor_ui-0.6.1.dist-info/RECORD +54 -0
- testexecutor_ui-0.6.1.dist-info/WHEEL +5 -0
- testexecutor_ui-0.6.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
from PySide6.QtCore import QObject, QJsonDocument
|
|
17
|
+
from PySide6.QtCore import Signal, Property, Slot
|
|
18
|
+
from PySide6.QtWidgets import QApplication
|
|
19
|
+
from PySide6.QtCore import QUrl
|
|
20
|
+
from PySide6.QtCore import Qt
|
|
21
|
+
from PySide6.QtCore import QCoreApplication
|
|
22
|
+
from PySide6.QtGui import QIcon
|
|
23
|
+
from PySide6.QtQml import QQmlApplicationEngine
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
import json
|
|
28
|
+
|
|
29
|
+
notice = """
|
|
30
|
+
<b>This software includes code from Test Executor ™</b><br/>
|
|
31
|
+
<i>Copyright © Direkt Embedded Pty Ltd<br/>
|
|
32
|
+
https://www.direktembedded.com</i>"""
|
|
33
|
+
|
|
34
|
+
class MultiTestWindowModel(QObject):
|
|
35
|
+
|
|
36
|
+
def __init__(self, suiteGroup=None, title="Test Executor", closeHeading="There Are Still Tests Running", closeText="Stop all suites if you want to quit"):
|
|
37
|
+
QObject.__init__(self)
|
|
38
|
+
self._title = title
|
|
39
|
+
self._visibility = "Maximized"
|
|
40
|
+
self._abortCallback = None
|
|
41
|
+
self._closeHeading = closeHeading
|
|
42
|
+
self._closeText = closeText
|
|
43
|
+
self._allowAbort = False
|
|
44
|
+
self._config = None
|
|
45
|
+
self._suiteGroup = suiteGroup
|
|
46
|
+
self._about = None
|
|
47
|
+
if suiteGroup is not None and suiteGroup.abortAll is not None:
|
|
48
|
+
self._abortCallback = suiteGroup.abortAll
|
|
49
|
+
self._allowAbort = True
|
|
50
|
+
self._closeText = "Do you want to abort all tests?"
|
|
51
|
+
|
|
52
|
+
def _settitle(self, title):
|
|
53
|
+
""" Setter for title Property """
|
|
54
|
+
if self._title != title:
|
|
55
|
+
self._title = title
|
|
56
|
+
self.title_changed.emit()
|
|
57
|
+
|
|
58
|
+
def _gettitle(self):
|
|
59
|
+
""" Getter for title Property """
|
|
60
|
+
return self._title
|
|
61
|
+
|
|
62
|
+
title_changed = Signal()
|
|
63
|
+
title = Property(str, _gettitle, _settitle, notify=title_changed)
|
|
64
|
+
|
|
65
|
+
def _setvisibility(self, visibility):
|
|
66
|
+
""" Setter for visibility Property """
|
|
67
|
+
if self._visibility != visibility:
|
|
68
|
+
self._visibility = visibility
|
|
69
|
+
self.visibility_changed.emit()
|
|
70
|
+
|
|
71
|
+
def _getvisibility(self):
|
|
72
|
+
""" Getter for visibility Property """
|
|
73
|
+
return self._visibility
|
|
74
|
+
|
|
75
|
+
visibility_changed = Signal()
|
|
76
|
+
visibility = Property(str, _getvisibility, _setvisibility, notify=visibility_changed)
|
|
77
|
+
|
|
78
|
+
def _setabout(self, about):
|
|
79
|
+
""" Setter for about Property. Expects html (or rich text subset there of) """
|
|
80
|
+
if self._about != about:
|
|
81
|
+
self._about = about
|
|
82
|
+
self.about_changed.emit()
|
|
83
|
+
|
|
84
|
+
def _getabout(self):
|
|
85
|
+
""" Getter for about Property """
|
|
86
|
+
return self._add_notice(self._about)
|
|
87
|
+
|
|
88
|
+
def _add_notice(self, about):
|
|
89
|
+
if about:
|
|
90
|
+
about = f"{about}<br/>{notice}"
|
|
91
|
+
else:
|
|
92
|
+
about = notice
|
|
93
|
+
return about
|
|
94
|
+
|
|
95
|
+
about_changed = Signal()
|
|
96
|
+
about = Property(str, _getabout, _setabout, notify=about_changed)
|
|
97
|
+
|
|
98
|
+
def _setcloseHeading(self, closeHeading):
|
|
99
|
+
""" Setter for closeHeading Property """
|
|
100
|
+
if self._closeHeading != closeHeading:
|
|
101
|
+
self._closeHeading = closeHeading
|
|
102
|
+
self.closeHeading_changed.emit()
|
|
103
|
+
|
|
104
|
+
def _getcloseHeading(self):
|
|
105
|
+
""" Getter for closeHeading Property """
|
|
106
|
+
return self._closeHeading
|
|
107
|
+
|
|
108
|
+
closeHeading_changed = Signal()
|
|
109
|
+
closeHeading = Property(str, _getcloseHeading, _setcloseHeading, notify=closeHeading_changed)
|
|
110
|
+
|
|
111
|
+
def _setcloseText(self, closeText):
|
|
112
|
+
""" Setter for closeText Property """
|
|
113
|
+
if self._closeText != closeText:
|
|
114
|
+
self._closeText = closeText
|
|
115
|
+
self.closeText_changed.emit()
|
|
116
|
+
|
|
117
|
+
def _getcloseText(self):
|
|
118
|
+
""" Getter for closeText Property """
|
|
119
|
+
return self._closeText
|
|
120
|
+
|
|
121
|
+
closeText_changed = Signal()
|
|
122
|
+
closeText = Property(str, _getcloseText, _setcloseText, notify=closeText_changed)
|
|
123
|
+
|
|
124
|
+
def _setallowAbort(self, allowAbort):
|
|
125
|
+
""" Setter for allowAbort Property """
|
|
126
|
+
if self._allowAbort != allowAbort:
|
|
127
|
+
self._allowAbort = allowAbort
|
|
128
|
+
self.allowAbort_changed.emit()
|
|
129
|
+
|
|
130
|
+
def _getallowAbort(self):
|
|
131
|
+
""" Getter for allowAbort Property """
|
|
132
|
+
return self._allowAbort
|
|
133
|
+
|
|
134
|
+
allowAbort_changed = Signal()
|
|
135
|
+
allowAbort = Property(bool, _getallowAbort, _setallowAbort, notify=allowAbort_changed)
|
|
136
|
+
|
|
137
|
+
def _setconfig(self, config):
|
|
138
|
+
""" Setter for config Property """
|
|
139
|
+
if self._config != config:
|
|
140
|
+
self._config = config
|
|
141
|
+
self.config_changed.emit()
|
|
142
|
+
|
|
143
|
+
def _getconfig(self):
|
|
144
|
+
""" Getter for config Property """
|
|
145
|
+
return self._config
|
|
146
|
+
|
|
147
|
+
config_changed = Signal()
|
|
148
|
+
config = Property(str, _getconfig, _setconfig, notify=config_changed)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@Slot(bool)
|
|
152
|
+
def abortAll(self, hard=False):
|
|
153
|
+
if self._abortCallback:
|
|
154
|
+
self._abortCallback(hard)
|
|
155
|
+
|
|
156
|
+
def exec(self, iconFile=None):
|
|
157
|
+
current_path = os.path.dirname(os.path.abspath(__file__))
|
|
158
|
+
relative_path = os.path.join(current_path, "..")
|
|
159
|
+
ui_path = os.path.join(relative_path, 'ui')
|
|
160
|
+
qml_file = os.path.join(ui_path, 'MultiTestWindow.qml')
|
|
161
|
+
url = QUrl.fromLocalFile(qml_file)
|
|
162
|
+
if not iconFile:
|
|
163
|
+
iconFile = os.path.join(ui_path, 'te-64x64.ico')
|
|
164
|
+
|
|
165
|
+
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
|
166
|
+
QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
|
|
167
|
+
app = QApplication(sys.argv)
|
|
168
|
+
|
|
169
|
+
app.setWindowIcon(QIcon(iconFile))
|
|
170
|
+
if self.config:
|
|
171
|
+
check_json = json.loads(self.config)
|
|
172
|
+
engine = QQmlApplicationEngine()
|
|
173
|
+
engine.rootContext().setContextProperty("app_model", self)
|
|
174
|
+
if self.config:
|
|
175
|
+
engine.rootContext().setContextProperty("contextKeyValueItemConfig",
|
|
176
|
+
json.dumps(check_json["identification"]["item"]))
|
|
177
|
+
if "controller" in check_json:
|
|
178
|
+
engine.rootContext().setContextProperty("contextFilterSelectorItemConfig",
|
|
179
|
+
json.dumps(check_json["controller"]["filter"]["item"]))
|
|
180
|
+
engine.rootContext().setContextProperty("model_list", self._suiteGroup)
|
|
181
|
+
|
|
182
|
+
engine.load(url)
|
|
183
|
+
|
|
184
|
+
return app.exec_()
|
|
@@ -0,0 +1,314 @@
|
|
|
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 import QtCore
|
|
19
|
+
from PySide6.QtCore import QAbstractListModel
|
|
20
|
+
from PySide6.QtCore import Qt
|
|
21
|
+
from PySide6.QtCore import QModelIndex
|
|
22
|
+
from PySide6.QtCore import Slot
|
|
23
|
+
from PySide6.QtCore import Signal
|
|
24
|
+
from PySide6.QtCore import Property
|
|
25
|
+
from PySide6.QtCore import QObject
|
|
26
|
+
|
|
27
|
+
class Result(QObject):
|
|
28
|
+
|
|
29
|
+
StateRunning = "Running"
|
|
30
|
+
StatePass = "Pass"
|
|
31
|
+
StateFail = "Fail"
|
|
32
|
+
|
|
33
|
+
def __init__(self, name, result=None, feedback=None):
|
|
34
|
+
QObject.__init__(self)
|
|
35
|
+
self.lock = threading.RLock()
|
|
36
|
+
self._name = name
|
|
37
|
+
self._feedback = feedback
|
|
38
|
+
self._result = result
|
|
39
|
+
self._duration = 0
|
|
40
|
+
self._progress = 0
|
|
41
|
+
|
|
42
|
+
def _setfeedback(self, feedback):
|
|
43
|
+
""" Setter for feedback Property """
|
|
44
|
+
changed = False
|
|
45
|
+
with self.lock:
|
|
46
|
+
if self._feedback != feedback:
|
|
47
|
+
self._feedback = feedback
|
|
48
|
+
changed = True
|
|
49
|
+
if changed:
|
|
50
|
+
self.feedback_changed.emit()
|
|
51
|
+
|
|
52
|
+
def _getfeedback(self):
|
|
53
|
+
""" Getter for feedback Property """
|
|
54
|
+
with self.lock:
|
|
55
|
+
fb = self._feedback
|
|
56
|
+
return fb
|
|
57
|
+
|
|
58
|
+
feedback_changed = Signal()
|
|
59
|
+
feedback = Property(str, _getfeedback, _setfeedback, notify=feedback_changed)
|
|
60
|
+
|
|
61
|
+
def _setresult(self, result):
|
|
62
|
+
""" Setter for result Property """
|
|
63
|
+
changed = False
|
|
64
|
+
with self.lock:
|
|
65
|
+
if self._result != result:
|
|
66
|
+
self._result = result
|
|
67
|
+
changed = True
|
|
68
|
+
if changed:
|
|
69
|
+
self.result_changed.emit()
|
|
70
|
+
|
|
71
|
+
def _getresult(self):
|
|
72
|
+
""" Getter for result Property """
|
|
73
|
+
with self.lock:
|
|
74
|
+
r = self._result
|
|
75
|
+
return r
|
|
76
|
+
|
|
77
|
+
result_changed = Signal()
|
|
78
|
+
result = Property(str, _getresult, _setresult, notify=result_changed)
|
|
79
|
+
|
|
80
|
+
def _setduration(self, duration):
|
|
81
|
+
""" Setter for duration Property """
|
|
82
|
+
changed = False
|
|
83
|
+
with self.lock:
|
|
84
|
+
if self._duration != duration:
|
|
85
|
+
self._duration = duration
|
|
86
|
+
changed = True
|
|
87
|
+
if changed:
|
|
88
|
+
self.duration_changed.emit()
|
|
89
|
+
|
|
90
|
+
def _getduration(self):
|
|
91
|
+
""" Getter for duration Property """
|
|
92
|
+
with self.lock:
|
|
93
|
+
dur = self._duration
|
|
94
|
+
return dur
|
|
95
|
+
|
|
96
|
+
duration_changed = Signal()
|
|
97
|
+
duration = Property(int, _getduration, _setduration, notify=duration_changed)
|
|
98
|
+
|
|
99
|
+
def _setname(self, name):
|
|
100
|
+
""" Setter for name Property """
|
|
101
|
+
changed = False
|
|
102
|
+
with self.lock:
|
|
103
|
+
if self._name != name:
|
|
104
|
+
self._name = name
|
|
105
|
+
changed = True
|
|
106
|
+
if changed:
|
|
107
|
+
self.name_changed.emit()
|
|
108
|
+
|
|
109
|
+
def _getname(self):
|
|
110
|
+
""" Getter for name Property """
|
|
111
|
+
with self.lock:
|
|
112
|
+
n = self._name
|
|
113
|
+
return n
|
|
114
|
+
|
|
115
|
+
name_changed = Signal()
|
|
116
|
+
name = Property(str, _getname, _setname, notify=name_changed)
|
|
117
|
+
|
|
118
|
+
def _setprogress(self, progress):
|
|
119
|
+
""" Setter for progress Property """
|
|
120
|
+
changed = False
|
|
121
|
+
with self.lock:
|
|
122
|
+
if self._progress != progress:
|
|
123
|
+
if progress > 1:
|
|
124
|
+
progress = 1
|
|
125
|
+
elif progress < 0:
|
|
126
|
+
progress = 0
|
|
127
|
+
self._progress = progress
|
|
128
|
+
changed = True
|
|
129
|
+
if changed:
|
|
130
|
+
self.progress_changed.emit()
|
|
131
|
+
|
|
132
|
+
def _getprogress(self):
|
|
133
|
+
""" Getter for progress Property """
|
|
134
|
+
return self._progress
|
|
135
|
+
|
|
136
|
+
progress_changed = Signal()
|
|
137
|
+
progress = Property(float, _getprogress, _setprogress, notify=progress_changed)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ResultModel(QAbstractListModel):
|
|
141
|
+
|
|
142
|
+
TestRole = Qt.UserRole
|
|
143
|
+
TestKey = b"test"
|
|
144
|
+
|
|
145
|
+
def __init__(self, parent=None):
|
|
146
|
+
QAbstractListModel.__init__(self, parent)
|
|
147
|
+
self.lock = threading.RLock()
|
|
148
|
+
self._currentTestIndex = -1
|
|
149
|
+
self._data = []
|
|
150
|
+
|
|
151
|
+
def rowCount(self, parent=QModelIndex()):
|
|
152
|
+
return len(self._data)
|
|
153
|
+
|
|
154
|
+
def roleNames(self):
|
|
155
|
+
return {self.TestRole: self.TestKey}
|
|
156
|
+
|
|
157
|
+
def data(self, index, role=None):
|
|
158
|
+
i = index.row()
|
|
159
|
+
if len(self._data) > i:
|
|
160
|
+
d = self._data[i]
|
|
161
|
+
if role == self.TestRole:
|
|
162
|
+
return d[self.TestKey]
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
def setData(self, index, value, role=None):
|
|
166
|
+
i = index.row()
|
|
167
|
+
if len(self._data) > i:
|
|
168
|
+
self._data[index.row()] = value
|
|
169
|
+
self.dataChanged.emit(index, index, self.roleNames())
|
|
170
|
+
|
|
171
|
+
def overallResult(self):
|
|
172
|
+
"""
|
|
173
|
+
Return the test suite result status
|
|
174
|
+
:return: None: State is unknown. Either no test results or error occurred.
|
|
175
|
+
Result.StateRunning: A test is in progress
|
|
176
|
+
Result.StatePass: All test have been run and all passed
|
|
177
|
+
Result.StateFail: One state failed
|
|
178
|
+
"""
|
|
179
|
+
overall_result = None
|
|
180
|
+
if self._data and len(self._data):
|
|
181
|
+
for item in self._data:
|
|
182
|
+
test = item[self.TestKey]
|
|
183
|
+
if test.result == Result.StateFail:
|
|
184
|
+
overall_result = test.result
|
|
185
|
+
break
|
|
186
|
+
elif test.result == Result.StateRunning:
|
|
187
|
+
overall_result = test.result
|
|
188
|
+
break
|
|
189
|
+
if not overall_result:
|
|
190
|
+
overall_result = Result.StatePass
|
|
191
|
+
return overall_result
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@Slot(str, str)
|
|
195
|
+
def add(self, name, result, feedback=None):
|
|
196
|
+
rowCount = self.rowCount()
|
|
197
|
+
self.beginInsertRows(QModelIndex(), rowCount, rowCount)
|
|
198
|
+
test = {b'test': Result(name, result, feedback)}
|
|
199
|
+
self._data.append(test)
|
|
200
|
+
self.endInsertRows()
|
|
201
|
+
return test
|
|
202
|
+
|
|
203
|
+
@Slot(str)
|
|
204
|
+
def start(self, name):
|
|
205
|
+
"""
|
|
206
|
+
Method called to either add a new test, or re-start an existing one.
|
|
207
|
+
If the name-d test is found its 'content' will be cleared and restarted.
|
|
208
|
+
If it is not found the test will be added to the list and executed.
|
|
209
|
+
:param name: unique name of the test
|
|
210
|
+
:return: None
|
|
211
|
+
"""
|
|
212
|
+
existing = False
|
|
213
|
+
for row in range(len(self._data)):
|
|
214
|
+
test = self._data[row][self.TestKey]
|
|
215
|
+
if test.name == name:
|
|
216
|
+
test.result = Result.StateRunning
|
|
217
|
+
ix = self.index(row, 0)
|
|
218
|
+
self._setcurrentTestIndex(row)
|
|
219
|
+
self.dataChanged.emit(ix, ix, self.roleNames())
|
|
220
|
+
existing = True
|
|
221
|
+
break
|
|
222
|
+
if not existing:
|
|
223
|
+
self.add(name, Result.StateRunning)
|
|
224
|
+
self._setcurrentTestIndex(len(self._data) - 1)
|
|
225
|
+
|
|
226
|
+
@Slot(str, str)
|
|
227
|
+
def end(self, name, result):
|
|
228
|
+
"""
|
|
229
|
+
Method called to end a test.
|
|
230
|
+
Only updated if test is found. If no tests with name exist, nothing is done.
|
|
231
|
+
:param name: unique name of the test
|
|
232
|
+
:param result: test result for the suite
|
|
233
|
+
:return: None
|
|
234
|
+
"""
|
|
235
|
+
for row in range(len(self._data)):
|
|
236
|
+
test = self._data[row][self.TestKey]
|
|
237
|
+
if test.name == name:
|
|
238
|
+
test.result = result
|
|
239
|
+
test.progress = 1
|
|
240
|
+
ix = self.index(row, 0)
|
|
241
|
+
self.dataChanged.emit(ix, ix, self.roleNames())
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
@Slot(str, int)
|
|
245
|
+
def progress(self, name, progress):
|
|
246
|
+
"""
|
|
247
|
+
Method called to end a test.
|
|
248
|
+
Only updated if test is found. If no tests with name exist, nothing is done.
|
|
249
|
+
:param name: unique name of the test
|
|
250
|
+
:param progress: 0 to 100 for percentage of test progress.
|
|
251
|
+
:return: None
|
|
252
|
+
"""
|
|
253
|
+
for row in range(len(self._data)):
|
|
254
|
+
test = self._data[row][self.TestKey]
|
|
255
|
+
if test.name == name:
|
|
256
|
+
test.progress = progress / 100
|
|
257
|
+
ix = self.index(row, 0)
|
|
258
|
+
self.dataChanged.emit(ix, ix, self.roleNames())
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
@Slot(str, str)
|
|
262
|
+
def setFeedback(self, key, feedback):
|
|
263
|
+
existing = False
|
|
264
|
+
for row in range(len(self._data)):
|
|
265
|
+
test = self._data[row][self.TestKey]
|
|
266
|
+
if test.name == key:
|
|
267
|
+
test.feedback = feedback
|
|
268
|
+
ix = self.index(row, 0)
|
|
269
|
+
self.dataChanged.emit(ix, ix, self.roleNames())
|
|
270
|
+
existing = True
|
|
271
|
+
break
|
|
272
|
+
if not existing:
|
|
273
|
+
self.add(key, None, feedback)
|
|
274
|
+
|
|
275
|
+
@Slot()
|
|
276
|
+
def clear(self):
|
|
277
|
+
rowCount = self.rowCount(QModelIndex())
|
|
278
|
+
if rowCount:
|
|
279
|
+
self.beginRemoveRows(QModelIndex(), 0, rowCount - 1)
|
|
280
|
+
self._data.clear()
|
|
281
|
+
self.endRemoveRows()
|
|
282
|
+
|
|
283
|
+
@Slot(list, bool)
|
|
284
|
+
def populateTests(self, tests, clear):
|
|
285
|
+
if clear:
|
|
286
|
+
self.removeRows(0, self.rowCount(QModelIndex()))
|
|
287
|
+
if tests:
|
|
288
|
+
for test in tests:
|
|
289
|
+
self.add(test, None)
|
|
290
|
+
|
|
291
|
+
def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
|
|
292
|
+
self.beginRemoveRows(parent, position, position + rows - 1)
|
|
293
|
+
del self._data[position:position + rows]
|
|
294
|
+
self.endRemoveRows()
|
|
295
|
+
return True
|
|
296
|
+
|
|
297
|
+
def _setcurrentTestIndex(self, currentTestIndex):
|
|
298
|
+
""" Setter for currentTestIndex Property """
|
|
299
|
+
changed = False
|
|
300
|
+
with self.lock:
|
|
301
|
+
if self._currentTestIndex != currentTestIndex:
|
|
302
|
+
self._currentTestIndex = currentTestIndex
|
|
303
|
+
changed = True
|
|
304
|
+
if changed:
|
|
305
|
+
self.currentTestIndex_changed.emit()
|
|
306
|
+
|
|
307
|
+
def _getcurrentTestIndex(self):
|
|
308
|
+
""" Getter for currentTestIndex Property """
|
|
309
|
+
with self.lock:
|
|
310
|
+
index = self._currentTestIndex
|
|
311
|
+
return index
|
|
312
|
+
|
|
313
|
+
currentTestIndex_changed = Signal()
|
|
314
|
+
currentTestIndex = Property(int, _getcurrentTestIndex, _setcurrentTestIndex, notify=currentTestIndex_changed)
|
|
@@ -0,0 +1,139 @@
|
|
|
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
|
+
import threading
|
|
17
|
+
from PySide6.QtCore import QObject
|
|
18
|
+
from PySide6.QtCore import Signal, Property, Slot
|
|
19
|
+
from .selector.MultiSelectListModel import MultiSelectListModel
|
|
20
|
+
from testexecutor.model.selector.FilterGroupModel import FilterGroupModel
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestSuiteControlModel(QObject):
|
|
24
|
+
"""
|
|
25
|
+
A Test Suite Control is a model for selecting tests to be executed. The tests can be filtered.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, filtercallback=None, filters=None):
|
|
29
|
+
"""
|
|
30
|
+
The suite control calls the supplied callback when a change occurs in the filters. The callback is to the
|
|
31
|
+
execution test system, and should return a list of suites and associated tests, as well as a new set of
|
|
32
|
+
allowed filters, given the new suites and tests.
|
|
33
|
+
These will be 'merged' with the current suite and tests in the test selector.
|
|
34
|
+
What the filters are and how the filters effect the list of tests is entirely up to the calling test system.
|
|
35
|
+
Once the user has filtered and/or selected the tests, the getTests() method can be used by the test system
|
|
36
|
+
to get the list of suites and tests to execute. If this model is used in conjunction with the TestSuiteModel
|
|
37
|
+
then the test run will be seen on that associated UI.
|
|
38
|
+
:param filtercallback: Callback executed when a filter item changes, including on first creation
|
|
39
|
+
:param filters: A list of filter groups to be displayed
|
|
40
|
+
"""
|
|
41
|
+
QObject.__init__(self)
|
|
42
|
+
self.lock = threading.RLock()
|
|
43
|
+
self._testselector = MultiSelectListModel()
|
|
44
|
+
self._filterchangedcallback = filtercallback
|
|
45
|
+
self._errors = ""
|
|
46
|
+
if filters:
|
|
47
|
+
self._filter_changed_signal = Signal()
|
|
48
|
+
self._filters = FilterGroupModel(filters, onSelectedChanged=self.on_filters_changed)
|
|
49
|
+
|
|
50
|
+
@Slot(str, object)
|
|
51
|
+
def on_filters_changed(self, name, selected):
|
|
52
|
+
try:
|
|
53
|
+
self._filterchangedcallback(name, selected)
|
|
54
|
+
except Exception as err:
|
|
55
|
+
self.errors = str(err)
|
|
56
|
+
|
|
57
|
+
def _setfilters(self, filters):
|
|
58
|
+
""" Setter for filters Property """
|
|
59
|
+
changed = False
|
|
60
|
+
with self.lock:
|
|
61
|
+
if self._filters != filters:
|
|
62
|
+
self._filters = filters
|
|
63
|
+
changed = True
|
|
64
|
+
if changed:
|
|
65
|
+
self.filters_changed.emit()
|
|
66
|
+
|
|
67
|
+
def _getfilters(self):
|
|
68
|
+
"""
|
|
69
|
+
Getter for filters Property
|
|
70
|
+
"""
|
|
71
|
+
filters = None
|
|
72
|
+
with self.lock:
|
|
73
|
+
filters = self._filters
|
|
74
|
+
return filters
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
Filters is a list of possible filter controls that can be used to control the suite filtering.
|
|
78
|
+
Each filter is then another list of items which are the filtering 'fields'
|
|
79
|
+
"""
|
|
80
|
+
filters_changed = Signal()
|
|
81
|
+
filters = Property(QObject, _getfilters, _setfilters, notify=filters_changed)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _settestselector(self, testselector):
|
|
85
|
+
""" Setter for testselector Property """
|
|
86
|
+
changed = False
|
|
87
|
+
with self.lock:
|
|
88
|
+
if self._testselector != testselector:
|
|
89
|
+
self._testselector = testselector
|
|
90
|
+
changed = True
|
|
91
|
+
if changed:
|
|
92
|
+
self.testselector_changed.emit()
|
|
93
|
+
|
|
94
|
+
def _gettestselector(self):
|
|
95
|
+
"""
|
|
96
|
+
Getter for testselector Property
|
|
97
|
+
"""
|
|
98
|
+
testselector = None
|
|
99
|
+
with self.lock:
|
|
100
|
+
testselector = self._testselector
|
|
101
|
+
return testselector
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
testselector is a tree of suites and associated tests which can be selected to create a subset of tests
|
|
105
|
+
to be executed.
|
|
106
|
+
Should be of type TreeSelectorModel
|
|
107
|
+
"""
|
|
108
|
+
testselector_changed = Signal()
|
|
109
|
+
testselector = Property(QObject, _gettestselector, _settestselector, notify=testselector_changed)
|
|
110
|
+
|
|
111
|
+
def _seterrors(self, errors):
|
|
112
|
+
""" Setter for errors Property """
|
|
113
|
+
changed = False
|
|
114
|
+
with self.lock:
|
|
115
|
+
if self._errors != errors:
|
|
116
|
+
self._errors = errors
|
|
117
|
+
changed = True
|
|
118
|
+
if changed:
|
|
119
|
+
self.errors_changed.emit()
|
|
120
|
+
|
|
121
|
+
def _geterrors(self):
|
|
122
|
+
"""
|
|
123
|
+
Getter for errors Property
|
|
124
|
+
"""
|
|
125
|
+
errors = None
|
|
126
|
+
with self.lock:
|
|
127
|
+
errors = self._errors
|
|
128
|
+
return errors
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
errors is a tree of suites and associated tests which can be selected to create a subset of tests
|
|
132
|
+
to be executed.
|
|
133
|
+
Should be of type TreeSelectorModel
|
|
134
|
+
"""
|
|
135
|
+
errors_changed = Signal()
|
|
136
|
+
errors = Property(str, _geterrors, _seterrors, notify=errors_changed)
|
|
137
|
+
|
|
138
|
+
def selectedItems(self):
|
|
139
|
+
return self._testselector.selectedItems()
|