1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: testresults.py 98103 2023-01-17 14:15:46Z vboxsync $
|
---|
3 | # pylint: disable=too-many-lines
|
---|
4 |
|
---|
5 | ## @todo Rename this file to testresult.py!
|
---|
6 |
|
---|
7 | """
|
---|
8 | Test Manager - Fetch test results.
|
---|
9 | """
|
---|
10 |
|
---|
11 | __copyright__ = \
|
---|
12 | """
|
---|
13 | Copyright (C) 2012-2023 Oracle and/or its affiliates.
|
---|
14 |
|
---|
15 | This file is part of VirtualBox base platform packages, as
|
---|
16 | available from https://www.virtualbox.org.
|
---|
17 |
|
---|
18 | This program is free software; you can redistribute it and/or
|
---|
19 | modify it under the terms of the GNU General Public License
|
---|
20 | as published by the Free Software Foundation, in version 3 of the
|
---|
21 | License.
|
---|
22 |
|
---|
23 | This program is distributed in the hope that it will be useful, but
|
---|
24 | WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
25 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
26 | General Public License for more details.
|
---|
27 |
|
---|
28 | You should have received a copy of the GNU General Public License
|
---|
29 | along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
30 |
|
---|
31 | The contents of this file may alternatively be used under the terms
|
---|
32 | of the Common Development and Distribution License Version 1.0
|
---|
33 | (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
|
---|
34 | in the VirtualBox distribution, in which case the provisions of the
|
---|
35 | CDDL are applicable instead of those of the GPL.
|
---|
36 |
|
---|
37 | You may elect to license modified versions of this file under the
|
---|
38 | terms and conditions of either the GPL or the CDDL or both.
|
---|
39 |
|
---|
40 | SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
|
---|
41 | """
|
---|
42 | __version__ = "$Revision: 98103 $"
|
---|
43 |
|
---|
44 |
|
---|
45 | # Standard python imports.
|
---|
46 | import sys;
|
---|
47 | import unittest;
|
---|
48 |
|
---|
49 | # Validation Kit imports.
|
---|
50 | from common import constants;
|
---|
51 | from testmanager import config;
|
---|
52 | from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, ModelFilterBase, \
|
---|
53 | FilterCriterion, FilterCriterionValueAndDescription, \
|
---|
54 | TMExceptionBase, TMTooManyRows, TMRowNotFound;
|
---|
55 | from testmanager.core.testgroup import TestGroupData;
|
---|
56 | from testmanager.core.build import BuildDataEx, BuildCategoryData;
|
---|
57 | from testmanager.core.failurereason import FailureReasonLogic;
|
---|
58 | from testmanager.core.testbox import TestBoxData, TestBoxLogic;
|
---|
59 | from testmanager.core.testcase import TestCaseData;
|
---|
60 | from testmanager.core.schedgroup import SchedGroupData, SchedGroupLogic;
|
---|
61 | from testmanager.core.systemlog import SystemLogData, SystemLogLogic;
|
---|
62 | from testmanager.core.testresultfailures import TestResultFailureDataEx;
|
---|
63 | from testmanager.core.useraccount import UserAccountLogic;
|
---|
64 |
|
---|
65 | # Python 3 hacks:
|
---|
66 | if sys.version_info[0] >= 3:
|
---|
67 | long = int; # pylint: disable=redefined-builtin,invalid-name
|
---|
68 |
|
---|
69 |
|
---|
70 | class TestResultData(ModelDataBase):
|
---|
71 | """
|
---|
72 | Test case execution result data
|
---|
73 | """
|
---|
74 |
|
---|
75 | ## @name TestStatus_T
|
---|
76 | # @{
|
---|
77 | ksTestStatus_Running = 'running';
|
---|
78 | ksTestStatus_Success = 'success';
|
---|
79 | ksTestStatus_Skipped = 'skipped';
|
---|
80 | ksTestStatus_BadTestBox = 'bad-testbox';
|
---|
81 | ksTestStatus_Aborted = 'aborted';
|
---|
82 | ksTestStatus_Failure = 'failure';
|
---|
83 | ksTestStatus_TimedOut = 'timed-out';
|
---|
84 | ksTestStatus_Rebooted = 'rebooted';
|
---|
85 | ## @}
|
---|
86 |
|
---|
87 | ## List of relatively harmless (to testgroup/case) statuses.
|
---|
88 | kasHarmlessTestStatuses = [ ksTestStatus_Skipped, ksTestStatus_BadTestBox, ksTestStatus_Aborted, ];
|
---|
89 | ## List of bad statuses.
|
---|
90 | kasBadTestStatuses = [ ksTestStatus_Failure, ksTestStatus_TimedOut, ksTestStatus_Rebooted, ];
|
---|
91 |
|
---|
92 |
|
---|
93 | ksIdAttr = 'idTestResult';
|
---|
94 |
|
---|
95 | ksParam_idTestResult = 'TestResultData_idTestResult';
|
---|
96 | ksParam_idTestResultParent = 'TestResultData_idTestResultParent';
|
---|
97 | ksParam_idTestSet = 'TestResultData_idTestSet';
|
---|
98 | ksParam_tsCreated = 'TestResultData_tsCreated';
|
---|
99 | ksParam_tsElapsed = 'TestResultData_tsElapsed';
|
---|
100 | ksParam_idStrName = 'TestResultData_idStrName';
|
---|
101 | ksParam_cErrors = 'TestResultData_cErrors';
|
---|
102 | ksParam_enmStatus = 'TestResultData_enmStatus';
|
---|
103 | ksParam_iNestingDepth = 'TestResultData_iNestingDepth';
|
---|
104 | kasValidValues_enmStatus = [
|
---|
105 | ksTestStatus_Running,
|
---|
106 | ksTestStatus_Success,
|
---|
107 | ksTestStatus_Skipped,
|
---|
108 | ksTestStatus_BadTestBox,
|
---|
109 | ksTestStatus_Aborted,
|
---|
110 | ksTestStatus_Failure,
|
---|
111 | ksTestStatus_TimedOut,
|
---|
112 | ksTestStatus_Rebooted
|
---|
113 | ];
|
---|
114 |
|
---|
115 |
|
---|
116 | def __init__(self):
|
---|
117 | ModelDataBase.__init__(self)
|
---|
118 | self.idTestResult = None
|
---|
119 | self.idTestResultParent = None
|
---|
120 | self.idTestSet = None
|
---|
121 | self.tsCreated = None
|
---|
122 | self.tsElapsed = None
|
---|
123 | self.idStrName = None
|
---|
124 | self.cErrors = 0;
|
---|
125 | self.enmStatus = None
|
---|
126 | self.iNestingDepth = None
|
---|
127 |
|
---|
128 | def initFromDbRow(self, aoRow):
|
---|
129 | """
|
---|
130 | Reinitialize from a SELECT * FROM TestResults.
|
---|
131 | Return self. Raises exception if no row.
|
---|
132 | """
|
---|
133 | if aoRow is None:
|
---|
134 | raise TMRowNotFound('Test result record not found.')
|
---|
135 |
|
---|
136 | self.idTestResult = aoRow[0]
|
---|
137 | self.idTestResultParent = aoRow[1]
|
---|
138 | self.idTestSet = aoRow[2]
|
---|
139 | self.tsCreated = aoRow[3]
|
---|
140 | self.tsElapsed = aoRow[4]
|
---|
141 | self.idStrName = aoRow[5]
|
---|
142 | self.cErrors = aoRow[6]
|
---|
143 | self.enmStatus = aoRow[7]
|
---|
144 | self.iNestingDepth = aoRow[8]
|
---|
145 | return self;
|
---|
146 |
|
---|
147 | def initFromDbWithId(self, oDb, idTestResult, tsNow = None, sPeriodBack = None):
|
---|
148 | """
|
---|
149 | Initialize from the database, given the ID of a row.
|
---|
150 | """
|
---|
151 | _ = tsNow;
|
---|
152 | _ = sPeriodBack;
|
---|
153 | oDb.execute('SELECT *\n'
|
---|
154 | 'FROM TestResults\n'
|
---|
155 | 'WHERE idTestResult = %s\n'
|
---|
156 | , ( idTestResult,));
|
---|
157 | aoRow = oDb.fetchOne()
|
---|
158 | if aoRow is None:
|
---|
159 | raise TMRowNotFound('idTestResult=%s not found' % (idTestResult,));
|
---|
160 | return self.initFromDbRow(aoRow);
|
---|
161 |
|
---|
162 | def isFailure(self):
|
---|
163 | """ Check if it's a real failure. """
|
---|
164 | return self.enmStatus in self.kasBadTestStatuses;
|
---|
165 |
|
---|
166 |
|
---|
167 | class TestResultDataEx(TestResultData):
|
---|
168 | """
|
---|
169 | Extended test result data class.
|
---|
170 |
|
---|
171 | This is intended for use as a node in a result tree. This is not intended
|
---|
172 | for serialization to parameters or vice versa. Use TestResultLogic to
|
---|
173 | construct the tree.
|
---|
174 | """
|
---|
175 |
|
---|
176 | def __init__(self):
|
---|
177 | TestResultData.__init__(self)
|
---|
178 | self.sName = None; # idStrName resolved.
|
---|
179 | self.oParent = None; # idTestResultParent within the tree.
|
---|
180 |
|
---|
181 | self.aoChildren = []; # TestResultDataEx;
|
---|
182 | self.aoValues = []; # TestResultValueDataEx;
|
---|
183 | self.aoMsgs = []; # TestResultMsgDataEx;
|
---|
184 | self.aoFiles = []; # TestResultFileDataEx;
|
---|
185 | self.oReason = None; # TestResultReasonDataEx;
|
---|
186 |
|
---|
187 | def initFromDbRow(self, aoRow):
|
---|
188 | """
|
---|
189 | Initialize from a query like this:
|
---|
190 | SELECT TestResults.*, TestResultStrTab.sValue
|
---|
191 | FROM TestResults, TestResultStrTab
|
---|
192 | WHERE TestResultStrTab.idStr = TestResults.idStrName
|
---|
193 |
|
---|
194 | Note! The caller is expected to fetch children, values, failure
|
---|
195 | details, and files.
|
---|
196 | """
|
---|
197 | self.sName = None;
|
---|
198 | self.oParent = None;
|
---|
199 | self.aoChildren = [];
|
---|
200 | self.aoValues = [];
|
---|
201 | self.aoMsgs = [];
|
---|
202 | self.aoFiles = [];
|
---|
203 | self.oReason = None;
|
---|
204 |
|
---|
205 | TestResultData.initFromDbRow(self, aoRow);
|
---|
206 |
|
---|
207 | self.sName = aoRow[9];
|
---|
208 | return self;
|
---|
209 |
|
---|
210 | def deepCountErrorContributers(self):
|
---|
211 | """
|
---|
212 | Counts how many test result instances actually contributed to cErrors.
|
---|
213 | """
|
---|
214 |
|
---|
215 | # Check each child (if any).
|
---|
216 | cChanges = 0;
|
---|
217 | cChildErrors = 0;
|
---|
218 | for oChild in self.aoChildren:
|
---|
219 | if oChild.cErrors > 0:
|
---|
220 | cChildErrors += oChild.cErrors;
|
---|
221 | cChanges += oChild.deepCountErrorContributers();
|
---|
222 |
|
---|
223 | # Did we contribute as well?
|
---|
224 | if self.cErrors > cChildErrors:
|
---|
225 | cChanges += 1;
|
---|
226 | return cChanges;
|
---|
227 |
|
---|
228 | def getListOfFailures(self):
|
---|
229 | """
|
---|
230 | Get a list of test results instances actually contributing to cErrors.
|
---|
231 |
|
---|
232 | Returns a list of TestResultDataEx instances from this tree. (shared!)
|
---|
233 | """
|
---|
234 | # Check each child (if any).
|
---|
235 | aoRet = [];
|
---|
236 | cChildErrors = 0;
|
---|
237 | for oChild in self.aoChildren:
|
---|
238 | if oChild.cErrors > 0:
|
---|
239 | cChildErrors += oChild.cErrors;
|
---|
240 | aoRet.extend(oChild.getListOfFailures());
|
---|
241 |
|
---|
242 | # Did we contribute as well?
|
---|
243 | if self.cErrors > cChildErrors:
|
---|
244 | aoRet.append(self);
|
---|
245 |
|
---|
246 | return aoRet;
|
---|
247 |
|
---|
248 | def getListOfLogFilesByKind(self, asKinds):
|
---|
249 | """
|
---|
250 | Get a list of test results instances actually contributing to cErrors.
|
---|
251 |
|
---|
252 | Returns a list of TestResultFileDataEx instances from this tree. (shared!)
|
---|
253 | """
|
---|
254 | aoRet = [];
|
---|
255 |
|
---|
256 | # Check the children first.
|
---|
257 | for oChild in self.aoChildren:
|
---|
258 | aoRet.extend(oChild.getListOfLogFilesByKind(asKinds));
|
---|
259 |
|
---|
260 | # Check our own files next.
|
---|
261 | for oFile in self.aoFiles:
|
---|
262 | if oFile.sKind in asKinds:
|
---|
263 | aoRet.append(oFile);
|
---|
264 |
|
---|
265 | return aoRet;
|
---|
266 |
|
---|
267 | def getFullName(self):
|
---|
268 | """ Constructs the full name of this test result. """
|
---|
269 | if self.oParent is None:
|
---|
270 | return self.sName;
|
---|
271 | return self.oParent.getFullName() + ' / ' + self.sName;
|
---|
272 |
|
---|
273 |
|
---|
274 |
|
---|
275 | class TestResultValueData(ModelDataBase):
|
---|
276 | """
|
---|
277 | Test result value data.
|
---|
278 | """
|
---|
279 |
|
---|
280 | ksIdAttr = 'idTestResultValue';
|
---|
281 |
|
---|
282 | ksParam_idTestResultValue = 'TestResultValue_idTestResultValue';
|
---|
283 | ksParam_idTestResult = 'TestResultValue_idTestResult';
|
---|
284 | ksParam_idTestSet = 'TestResultValue_idTestSet';
|
---|
285 | ksParam_tsCreated = 'TestResultValue_tsCreated';
|
---|
286 | ksParam_idStrName = 'TestResultValue_idStrName';
|
---|
287 | ksParam_lValue = 'TestResultValue_lValue';
|
---|
288 | ksParam_iUnit = 'TestResultValue_iUnit';
|
---|
289 |
|
---|
290 | kasAllowNullAttributes = [ 'idTestSet', ];
|
---|
291 |
|
---|
292 | def __init__(self):
|
---|
293 | ModelDataBase.__init__(self)
|
---|
294 | self.idTestResultValue = None;
|
---|
295 | self.idTestResult = None;
|
---|
296 | self.idTestSet = None;
|
---|
297 | self.tsCreated = None;
|
---|
298 | self.idStrName = None;
|
---|
299 | self.lValue = None;
|
---|
300 | self.iUnit = 0;
|
---|
301 |
|
---|
302 | def initFromDbRow(self, aoRow):
|
---|
303 | """
|
---|
304 | Reinitialize from a SELECT * FROM TestResultValues.
|
---|
305 | Return self. Raises exception if no row.
|
---|
306 | """
|
---|
307 | if aoRow is None:
|
---|
308 | raise TMRowNotFound('Test result value record not found.')
|
---|
309 |
|
---|
310 | self.idTestResultValue = aoRow[0];
|
---|
311 | self.idTestResult = aoRow[1];
|
---|
312 | self.idTestSet = aoRow[2];
|
---|
313 | self.tsCreated = aoRow[3];
|
---|
314 | self.idStrName = aoRow[4];
|
---|
315 | self.lValue = aoRow[5];
|
---|
316 | self.iUnit = aoRow[6];
|
---|
317 | return self;
|
---|
318 |
|
---|
319 |
|
---|
320 | class TestResultValueDataEx(TestResultValueData):
|
---|
321 | """
|
---|
322 | Extends TestResultValue by resolving the value name and unit string.
|
---|
323 | """
|
---|
324 |
|
---|
325 | def __init__(self):
|
---|
326 | TestResultValueData.__init__(self)
|
---|
327 | self.sName = None;
|
---|
328 | self.sUnit = '';
|
---|
329 |
|
---|
330 | def initFromDbRow(self, aoRow):
|
---|
331 | """
|
---|
332 | Reinitialize from a query like this:
|
---|
333 | SELECT TestResultValues.*, TestResultStrTab.sValue
|
---|
334 | FROM TestResultValues, TestResultStrTab
|
---|
335 | WHERE TestResultStrTab.idStr = TestResultValues.idStrName
|
---|
336 |
|
---|
337 | Return self. Raises exception if no row.
|
---|
338 | """
|
---|
339 | TestResultValueData.initFromDbRow(self, aoRow);
|
---|
340 | self.sName = aoRow[7];
|
---|
341 | if self.iUnit < len(constants.valueunit.g_asNames):
|
---|
342 | self.sUnit = constants.valueunit.g_asNames[self.iUnit];
|
---|
343 | else:
|
---|
344 | self.sUnit = '<%d>' % (self.iUnit,);
|
---|
345 | return self;
|
---|
346 |
|
---|
347 | class TestResultMsgData(ModelDataBase):
|
---|
348 | """
|
---|
349 | Test result message data.
|
---|
350 | """
|
---|
351 |
|
---|
352 | ksIdAttr = 'idTestResultMsg';
|
---|
353 |
|
---|
354 | ksParam_idTestResultMsg = 'TestResultValue_idTestResultMsg';
|
---|
355 | ksParam_idTestResult = 'TestResultValue_idTestResult';
|
---|
356 | ksParam_idTestSet = 'TestResultValue_idTestSet';
|
---|
357 | ksParam_tsCreated = 'TestResultValue_tsCreated';
|
---|
358 | ksParam_idStrMsg = 'TestResultValue_idStrMsg';
|
---|
359 | ksParam_enmLevel = 'TestResultValue_enmLevel';
|
---|
360 |
|
---|
361 | kasAllowNullAttributes = [ 'idTestSet', ];
|
---|
362 |
|
---|
363 | kcDbColumns = 6
|
---|
364 |
|
---|
365 | def __init__(self):
|
---|
366 | ModelDataBase.__init__(self)
|
---|
367 | self.idTestResultMsg = None;
|
---|
368 | self.idTestResult = None;
|
---|
369 | self.idTestSet = None;
|
---|
370 | self.tsCreated = None;
|
---|
371 | self.idStrMsg = None;
|
---|
372 | self.enmLevel = None;
|
---|
373 |
|
---|
374 | def initFromDbRow(self, aoRow):
|
---|
375 | """
|
---|
376 | Reinitialize from a SELECT * FROM TestResultMsgs.
|
---|
377 | Return self. Raises exception if no row.
|
---|
378 | """
|
---|
379 | if aoRow is None:
|
---|
380 | raise TMRowNotFound('Test result value record not found.')
|
---|
381 |
|
---|
382 | self.idTestResultMsg = aoRow[0];
|
---|
383 | self.idTestResult = aoRow[1];
|
---|
384 | self.idTestSet = aoRow[2];
|
---|
385 | self.tsCreated = aoRow[3];
|
---|
386 | self.idStrMsg = aoRow[4];
|
---|
387 | self.enmLevel = aoRow[5];
|
---|
388 | return self;
|
---|
389 |
|
---|
390 | class TestResultMsgDataEx(TestResultMsgData):
|
---|
391 | """
|
---|
392 | Extends TestResultMsg by resolving the message string.
|
---|
393 | """
|
---|
394 |
|
---|
395 | def __init__(self):
|
---|
396 | TestResultMsgData.__init__(self)
|
---|
397 | self.sMsg = None;
|
---|
398 |
|
---|
399 | def initFromDbRow(self, aoRow):
|
---|
400 | """
|
---|
401 | Reinitialize from a query like this:
|
---|
402 | SELECT TestResultMsg.*, TestResultStrTab.sValue
|
---|
403 | FROM TestResultMsg, TestResultStrTab
|
---|
404 | WHERE TestResultStrTab.idStr = TestResultMsgs.idStrName
|
---|
405 |
|
---|
406 | Return self. Raises exception if no row.
|
---|
407 | """
|
---|
408 | TestResultMsgData.initFromDbRow(self, aoRow);
|
---|
409 | self.sMsg = aoRow[self.kcDbColumns];
|
---|
410 | return self;
|
---|
411 |
|
---|
412 |
|
---|
413 | class TestResultFileData(ModelDataBase):
|
---|
414 | """
|
---|
415 | Test result message data.
|
---|
416 | """
|
---|
417 |
|
---|
418 | ksIdAttr = 'idTestResultFile';
|
---|
419 |
|
---|
420 | ksParam_idTestResultFile = 'TestResultFile_idTestResultFile';
|
---|
421 | ksParam_idTestResult = 'TestResultFile_idTestResult';
|
---|
422 | ksParam_tsCreated = 'TestResultFile_tsCreated';
|
---|
423 | ksParam_idStrFile = 'TestResultFile_idStrFile';
|
---|
424 | ksParam_idStrDescription = 'TestResultFile_idStrDescription';
|
---|
425 | ksParam_idStrKind = 'TestResultFile_idStrKind';
|
---|
426 | ksParam_idStrMime = 'TestResultFile_idStrMime';
|
---|
427 |
|
---|
428 | ## @name Kind of files.
|
---|
429 | ## @{
|
---|
430 | ksKind_LogReleaseVm = 'log/release/vm';
|
---|
431 | ksKind_LogDebugVm = 'log/debug/vm';
|
---|
432 | ksKind_LogReleaseSvc = 'log/release/svc';
|
---|
433 | ksKind_LogDebugSvc = 'log/debug/svc';
|
---|
434 | ksKind_LogReleaseClient = 'log/release/client';
|
---|
435 | ksKind_LogDebugClient = 'log/debug/client';
|
---|
436 | ksKind_LogInstaller = 'log/installer';
|
---|
437 | ksKind_LogUninstaller = 'log/uninstaller';
|
---|
438 | ksKind_LogGuestKernel = 'log/guest/kernel';
|
---|
439 | ksKind_ProcessReportVm = 'process/report/vm';
|
---|
440 | ksKind_CrashReportVm = 'crash/report/vm';
|
---|
441 | ksKind_CrashDumpVm = 'crash/dump/vm';
|
---|
442 | ksKind_CrashReportSvc = 'crash/report/svc';
|
---|
443 | ksKind_CrashDumpSvc = 'crash/dump/svc';
|
---|
444 | ksKind_CrashReportClient = 'crash/report/client';
|
---|
445 | ksKind_CrashDumpClient = 'crash/dump/client';
|
---|
446 | ksKind_InfoCollection = 'info/collection';
|
---|
447 | ksKind_InfoVgaText = 'info/vgatext';
|
---|
448 | ksKind_MiscOther = 'misc/other';
|
---|
449 | ksKind_ScreenshotFailure = 'screenshot/failure';
|
---|
450 | ksKind_ScreenshotSuccesss = 'screenshot/success';
|
---|
451 | ksKind_ScreenRecordingFailure = 'screenrecording/failure';
|
---|
452 | ksKind_ScreenRecordingSuccess = 'screenrecording/success';
|
---|
453 | ## @}
|
---|
454 |
|
---|
455 | kasKinds = [
|
---|
456 | ksKind_LogReleaseVm,
|
---|
457 | ksKind_LogDebugVm,
|
---|
458 | ksKind_LogReleaseSvc,
|
---|
459 | ksKind_LogDebugSvc,
|
---|
460 | ksKind_LogReleaseClient,
|
---|
461 | ksKind_LogDebugClient,
|
---|
462 | ksKind_LogInstaller,
|
---|
463 | ksKind_LogUninstaller,
|
---|
464 | ksKind_LogGuestKernel,
|
---|
465 | ksKind_ProcessReportVm,
|
---|
466 | ksKind_CrashReportVm,
|
---|
467 | ksKind_CrashDumpVm,
|
---|
468 | ksKind_CrashReportSvc,
|
---|
469 | ksKind_CrashDumpSvc,
|
---|
470 | ksKind_CrashReportClient,
|
---|
471 | ksKind_CrashDumpClient,
|
---|
472 | ksKind_InfoCollection,
|
---|
473 | ksKind_InfoVgaText,
|
---|
474 | ksKind_MiscOther,
|
---|
475 | ksKind_ScreenshotFailure,
|
---|
476 | ksKind_ScreenshotSuccesss,
|
---|
477 | ksKind_ScreenRecordingFailure,
|
---|
478 | ksKind_ScreenRecordingSuccess,
|
---|
479 | ];
|
---|
480 |
|
---|
481 | kasAllowNullAttributes = [ 'idTestSet', ];
|
---|
482 |
|
---|
483 | kcDbColumns = 8
|
---|
484 |
|
---|
485 | def __init__(self):
|
---|
486 | ModelDataBase.__init__(self)
|
---|
487 | self.idTestResultFile = None;
|
---|
488 | self.idTestResult = None;
|
---|
489 | self.idTestSet = None;
|
---|
490 | self.tsCreated = None;
|
---|
491 | self.idStrFile = None;
|
---|
492 | self.idStrDescription = None;
|
---|
493 | self.idStrKind = None;
|
---|
494 | self.idStrMime = None;
|
---|
495 |
|
---|
496 | def initFromDbRow(self, aoRow):
|
---|
497 | """
|
---|
498 | Reinitialize from a SELECT * FROM TestResultFiles.
|
---|
499 | Return self. Raises exception if no row.
|
---|
500 | """
|
---|
501 | if aoRow is None:
|
---|
502 | raise TMRowNotFound('Test result file record not found.')
|
---|
503 |
|
---|
504 | self.idTestResultFile = aoRow[0];
|
---|
505 | self.idTestResult = aoRow[1];
|
---|
506 | self.idTestSet = aoRow[2];
|
---|
507 | self.tsCreated = aoRow[3];
|
---|
508 | self.idStrFile = aoRow[4];
|
---|
509 | self.idStrDescription = aoRow[5];
|
---|
510 | self.idStrKind = aoRow[6];
|
---|
511 | self.idStrMime = aoRow[7];
|
---|
512 | return self;
|
---|
513 |
|
---|
514 | class TestResultFileDataEx(TestResultFileData):
|
---|
515 | """
|
---|
516 | Extends TestResultFile by resolving the strings.
|
---|
517 | """
|
---|
518 |
|
---|
519 | def __init__(self):
|
---|
520 | TestResultFileData.__init__(self)
|
---|
521 | self.sFile = None;
|
---|
522 | self.sDescription = None;
|
---|
523 | self.sKind = None;
|
---|
524 | self.sMime = None;
|
---|
525 |
|
---|
526 | def initFromDbRow(self, aoRow):
|
---|
527 | """
|
---|
528 | Reinitialize from a query like this:
|
---|
529 | SELECT TestResultFiles.*,
|
---|
530 | StrTabFile.sValue AS sFile,
|
---|
531 | StrTabDesc.sValue AS sDescription
|
---|
532 | StrTabKind.sValue AS sKind,
|
---|
533 | StrTabMime.sValue AS sMime,
|
---|
534 | FROM ...
|
---|
535 |
|
---|
536 | Return self. Raises exception if no row.
|
---|
537 | """
|
---|
538 | TestResultFileData.initFromDbRow(self, aoRow);
|
---|
539 | self.sFile = aoRow[self.kcDbColumns];
|
---|
540 | self.sDescription = aoRow[self.kcDbColumns + 1];
|
---|
541 | self.sKind = aoRow[self.kcDbColumns + 2];
|
---|
542 | self.sMime = aoRow[self.kcDbColumns + 3];
|
---|
543 | return self;
|
---|
544 |
|
---|
545 | def initFakeMainLog(self, oTestSet):
|
---|
546 | """
|
---|
547 | Reinitializes to represent the main.log object (not in DB).
|
---|
548 |
|
---|
549 | Returns self.
|
---|
550 | """
|
---|
551 | self.idTestResultFile = 0;
|
---|
552 | self.idTestResult = oTestSet.idTestResult;
|
---|
553 | self.tsCreated = oTestSet.tsCreated;
|
---|
554 | self.idStrFile = None;
|
---|
555 | self.idStrDescription = None;
|
---|
556 | self.idStrKind = None;
|
---|
557 | self.idStrMime = None;
|
---|
558 |
|
---|
559 | self.sFile = 'main.log';
|
---|
560 | self.sDescription = '';
|
---|
561 | self.sKind = 'log/main';
|
---|
562 | self.sMime = 'text/plain';
|
---|
563 | return self;
|
---|
564 |
|
---|
565 | def isProbablyUtf8Encoded(self):
|
---|
566 | """
|
---|
567 | Checks if the file is likely to be UTF-8 encoded.
|
---|
568 | """
|
---|
569 | if self.sMime in [ 'text/plain', 'text/html' ]:
|
---|
570 | return True;
|
---|
571 | return False;
|
---|
572 |
|
---|
573 | def getMimeWithEncoding(self):
|
---|
574 | """
|
---|
575 | Gets the MIME type with encoding if likely to be UTF-8.
|
---|
576 | """
|
---|
577 | if self.isProbablyUtf8Encoded():
|
---|
578 | return '%s; charset=utf-8' % (self.sMime,);
|
---|
579 | return self.sMime;
|
---|
580 |
|
---|
581 |
|
---|
582 |
|
---|
583 | class TestResultListingData(ModelDataBase): # pylint: disable=too-many-instance-attributes
|
---|
584 | """
|
---|
585 | Test case result data representation for table listing
|
---|
586 | """
|
---|
587 |
|
---|
588 | class FailureReasonListingData(object):
|
---|
589 | """ Failure reason listing data """
|
---|
590 | def __init__(self):
|
---|
591 | self.oFailureReason = None;
|
---|
592 | self.oFailureReasonAssigner = None;
|
---|
593 | self.tsFailureReasonAssigned = None;
|
---|
594 | self.sFailureReasonComment = None;
|
---|
595 |
|
---|
596 | def __init__(self):
|
---|
597 | """Initialize"""
|
---|
598 | ModelDataBase.__init__(self)
|
---|
599 |
|
---|
600 | self.idTestSet = None
|
---|
601 |
|
---|
602 | self.idBuildCategory = None;
|
---|
603 | self.sProduct = None
|
---|
604 | self.sRepository = None;
|
---|
605 | self.sBranch = None
|
---|
606 | self.sType = None
|
---|
607 | self.idBuild = None;
|
---|
608 | self.sVersion = None;
|
---|
609 | self.iRevision = None
|
---|
610 |
|
---|
611 | self.sOs = None;
|
---|
612 | self.sOsVersion = None;
|
---|
613 | self.sArch = None;
|
---|
614 | self.sCpuVendor = None;
|
---|
615 | self.sCpuName = None;
|
---|
616 | self.cCpus = None;
|
---|
617 | self.fCpuHwVirt = None;
|
---|
618 | self.fCpuNestedPaging = None;
|
---|
619 | self.fCpu64BitGuest = None;
|
---|
620 | self.idTestBox = None
|
---|
621 | self.sTestBoxName = None
|
---|
622 |
|
---|
623 | self.tsCreated = None
|
---|
624 | self.tsElapsed = None
|
---|
625 | self.enmStatus = None
|
---|
626 | self.cErrors = None;
|
---|
627 |
|
---|
628 | self.idTestCase = None
|
---|
629 | self.sTestCaseName = None
|
---|
630 | self.sBaseCmd = None
|
---|
631 | self.sArgs = None
|
---|
632 | self.sSubName = None;
|
---|
633 |
|
---|
634 | self.idBuildTestSuite = None;
|
---|
635 | self.iRevisionTestSuite = None;
|
---|
636 |
|
---|
637 | self.aoFailureReasons = [];
|
---|
638 |
|
---|
639 | def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic):
|
---|
640 | """
|
---|
641 | Reinitialize from a database query.
|
---|
642 | Return self. Raises exception if no row.
|
---|
643 | """
|
---|
644 | if aoRow is None:
|
---|
645 | raise TMRowNotFound('Test result record not found.')
|
---|
646 |
|
---|
647 | self.idTestSet = aoRow[0];
|
---|
648 |
|
---|
649 | self.idBuildCategory = aoRow[1];
|
---|
650 | self.sProduct = aoRow[2];
|
---|
651 | self.sRepository = aoRow[3];
|
---|
652 | self.sBranch = aoRow[4];
|
---|
653 | self.sType = aoRow[5];
|
---|
654 | self.idBuild = aoRow[6];
|
---|
655 | self.sVersion = aoRow[7];
|
---|
656 | self.iRevision = aoRow[8];
|
---|
657 |
|
---|
658 | self.sOs = aoRow[9];
|
---|
659 | self.sOsVersion = aoRow[10];
|
---|
660 | self.sArch = aoRow[11];
|
---|
661 | self.sCpuVendor = aoRow[12];
|
---|
662 | self.sCpuName = aoRow[13];
|
---|
663 | self.cCpus = aoRow[14];
|
---|
664 | self.fCpuHwVirt = aoRow[15];
|
---|
665 | self.fCpuNestedPaging = aoRow[16];
|
---|
666 | self.fCpu64BitGuest = aoRow[17];
|
---|
667 | self.idTestBox = aoRow[18];
|
---|
668 | self.sTestBoxName = aoRow[19];
|
---|
669 |
|
---|
670 | self.tsCreated = aoRow[20];
|
---|
671 | self.tsElapsed = aoRow[21];
|
---|
672 | self.enmStatus = aoRow[22];
|
---|
673 | self.cErrors = aoRow[23];
|
---|
674 |
|
---|
675 | self.idTestCase = aoRow[24];
|
---|
676 | self.sTestCaseName = aoRow[25];
|
---|
677 | self.sBaseCmd = aoRow[26];
|
---|
678 | self.sArgs = aoRow[27];
|
---|
679 | self.sSubName = aoRow[28];
|
---|
680 |
|
---|
681 | self.idBuildTestSuite = aoRow[29];
|
---|
682 | self.iRevisionTestSuite = aoRow[30];
|
---|
683 |
|
---|
684 | self.aoFailureReasons = [];
|
---|
685 | for i, _ in enumerate(aoRow[31]):
|
---|
686 | if aoRow[31][i] is not None \
|
---|
687 | or aoRow[32][i] is not None \
|
---|
688 | or aoRow[33][i] is not None \
|
---|
689 | or aoRow[34][i] is not None:
|
---|
690 | oReason = self.FailureReasonListingData();
|
---|
691 | if aoRow[31][i] is not None:
|
---|
692 | oReason.oFailureReason = oFailureReasonLogic.cachedLookup(aoRow[31][i]);
|
---|
693 | if aoRow[32][i] is not None:
|
---|
694 | oReason.oFailureReasonAssigner = oUserAccountLogic.cachedLookup(aoRow[32][i]);
|
---|
695 | oReason.tsFailureReasonAssigned = aoRow[33][i];
|
---|
696 | oReason.sFailureReasonComment = aoRow[34][i];
|
---|
697 | self.aoFailureReasons.append(oReason);
|
---|
698 |
|
---|
699 | return self
|
---|
700 |
|
---|
701 |
|
---|
702 | class TestResultHangingOffence(TMExceptionBase):
|
---|
703 | """Hanging offence committed by test case."""
|
---|
704 | pass; # pylint: disable=unnecessary-pass
|
---|
705 |
|
---|
706 |
|
---|
707 | class TestResultFilter(ModelFilterBase):
|
---|
708 | """
|
---|
709 | Test result filter.
|
---|
710 | """
|
---|
711 |
|
---|
712 | kiTestStatus = 0;
|
---|
713 | kiErrorCounts = 1;
|
---|
714 | kiBranches = 2;
|
---|
715 | kiBuildTypes = 3;
|
---|
716 | kiRevisions = 4;
|
---|
717 | kiRevisionRange = 5;
|
---|
718 | kiFailReasons = 6;
|
---|
719 | kiTestCases = 7;
|
---|
720 | kiTestCaseMisc = 8;
|
---|
721 | kiTestBoxes = 9;
|
---|
722 | kiOses = 10;
|
---|
723 | kiCpuArches = 11;
|
---|
724 | kiCpuVendors = 12;
|
---|
725 | kiCpuCounts = 13;
|
---|
726 | kiMemory = 14;
|
---|
727 | kiTestboxMisc = 15;
|
---|
728 | kiPythonVersions = 16;
|
---|
729 | kiSchedGroups = 17;
|
---|
730 |
|
---|
731 | ## Misc test case / variation name filters.
|
---|
732 | ## Presented in table order. The first sub element is the presistent ID.
|
---|
733 | kaTcMisc = (
|
---|
734 | ( 1, 'x86', ),
|
---|
735 | ( 2, 'amd64', ),
|
---|
736 | ( 3, 'uni', ),
|
---|
737 | ( 4, 'smp', ),
|
---|
738 | ( 5, 'raw', ),
|
---|
739 | ( 6, 'hw', ),
|
---|
740 | ( 7, 'np', ),
|
---|
741 | ( 8, 'Install', ),
|
---|
742 | ( 20, 'UInstall', ), # NB. out of order.
|
---|
743 | ( 9, 'Benchmark', ),
|
---|
744 | ( 18, 'smoke', ), # NB. out of order.
|
---|
745 | ( 19, 'unit', ), # NB. out of order.
|
---|
746 | ( 10, 'USB', ),
|
---|
747 | ( 11, 'Debian', ),
|
---|
748 | ( 12, 'Fedora', ),
|
---|
749 | ( 13, 'Oracle', ),
|
---|
750 | ( 14, 'RHEL', ),
|
---|
751 | ( 15, 'SUSE', ),
|
---|
752 | ( 16, 'Ubuntu', ),
|
---|
753 | ( 17, 'Win', ),
|
---|
754 | );
|
---|
755 |
|
---|
756 | kiTbMisc_NestedPaging = 0;
|
---|
757 | kiTbMisc_NoNestedPaging = 1;
|
---|
758 | kiTbMisc_RawMode = 2;
|
---|
759 | kiTbMisc_NoRawMode = 3;
|
---|
760 | kiTbMisc_64BitGuest = 4;
|
---|
761 | kiTbMisc_No64BitGuest = 5;
|
---|
762 | kiTbMisc_HwVirt = 6;
|
---|
763 | kiTbMisc_NoHwVirt = 7;
|
---|
764 | kiTbMisc_IoMmu = 8;
|
---|
765 | kiTbMisc_NoIoMmu = 9;
|
---|
766 |
|
---|
767 | def __init__(self):
|
---|
768 | ModelFilterBase.__init__(self);
|
---|
769 |
|
---|
770 | # Test statuses
|
---|
771 | oCrit = FilterCriterion('Test statuses', sVarNm = 'ts', sType = FilterCriterion.ksType_String,
|
---|
772 | sTable = 'TestSets', sColumn = 'enmStatus');
|
---|
773 | self.aCriteria.append(oCrit);
|
---|
774 | assert self.aCriteria[self.kiTestStatus] is oCrit;
|
---|
775 |
|
---|
776 | # Error counts
|
---|
777 | oCrit = FilterCriterion('Error counts', sVarNm = 'ec', sTable = 'TestResults', sColumn = 'cErrors');
|
---|
778 | self.aCriteria.append(oCrit);
|
---|
779 | assert self.aCriteria[self.kiErrorCounts] is oCrit;
|
---|
780 |
|
---|
781 | # Branches
|
---|
782 | oCrit = FilterCriterion('Branches', sVarNm = 'br', sType = FilterCriterion.ksType_String,
|
---|
783 | sTable = 'BuildCategories', sColumn = 'sBranch');
|
---|
784 | self.aCriteria.append(oCrit);
|
---|
785 | assert self.aCriteria[self.kiBranches] is oCrit;
|
---|
786 |
|
---|
787 | # Build types
|
---|
788 | oCrit = FilterCriterion('Build types', sVarNm = 'bt', sType = FilterCriterion.ksType_String,
|
---|
789 | sTable = 'BuildCategories', sColumn = 'sType');
|
---|
790 | self.aCriteria.append(oCrit);
|
---|
791 | assert self.aCriteria[self.kiBuildTypes] is oCrit;
|
---|
792 |
|
---|
793 | # Revisions
|
---|
794 | oCrit = FilterCriterion('Revisions', sVarNm = 'rv', sTable = 'Builds', sColumn = 'iRevision');
|
---|
795 | self.aCriteria.append(oCrit);
|
---|
796 | assert self.aCriteria[self.kiRevisions] is oCrit;
|
---|
797 |
|
---|
798 | # Revision Range
|
---|
799 | oCrit = FilterCriterion('Revision Range', sVarNm = 'rr', sType = FilterCriterion.ksType_Ranges,
|
---|
800 | sKind = FilterCriterion.ksKind_ElementOfOrNot, sTable = 'Builds', sColumn = 'iRevision');
|
---|
801 | self.aCriteria.append(oCrit);
|
---|
802 | assert self.aCriteria[self.kiRevisionRange] is oCrit;
|
---|
803 |
|
---|
804 | # Failure reasons
|
---|
805 | oCrit = FilterCriterion('Failure reasons', sVarNm = 'fr', sType = FilterCriterion.ksType_UIntNil,
|
---|
806 | sTable = 'TestResultFailures', sColumn = 'idFailureReason');
|
---|
807 | self.aCriteria.append(oCrit);
|
---|
808 | assert self.aCriteria[self.kiFailReasons] is oCrit;
|
---|
809 |
|
---|
810 | # Test cases and variations.
|
---|
811 | oCrit = FilterCriterion('Test case / var', sVarNm = 'tc', sTable = 'TestSets', sColumn = 'idTestCase',
|
---|
812 | oSub = FilterCriterion('Test variations', sVarNm = 'tv',
|
---|
813 | sTable = 'TestSets', sColumn = 'idTestCaseArgs'));
|
---|
814 | self.aCriteria.append(oCrit);
|
---|
815 | assert self.aCriteria[self.kiTestCases] is oCrit;
|
---|
816 |
|
---|
817 | # Special test case and varation name sub string matching.
|
---|
818 | oCrit = FilterCriterion('Test case name', sVarNm = 'cm', sKind = FilterCriterion.ksKind_Special,
|
---|
819 | asTables = ('TestCases', 'TestCaseArgs'));
|
---|
820 | oCrit.aoPossible = [
|
---|
821 | FilterCriterionValueAndDescription(aoCur[0], 'Include %s' % (aoCur[1],)) for aoCur in self.kaTcMisc
|
---|
822 | ];
|
---|
823 | oCrit.aoPossible.extend([
|
---|
824 | FilterCriterionValueAndDescription(aoCur[0] + 32, 'Exclude %s' % (aoCur[1],)) for aoCur in self.kaTcMisc
|
---|
825 | ]);
|
---|
826 | self.aCriteria.append(oCrit);
|
---|
827 | assert self.aCriteria[self.kiTestCaseMisc] is oCrit;
|
---|
828 |
|
---|
829 | # Testboxes
|
---|
830 | oCrit = FilterCriterion('Testboxes', sVarNm = 'tb', sTable = 'TestSets', sColumn = 'idTestBox');
|
---|
831 | self.aCriteria.append(oCrit);
|
---|
832 | assert self.aCriteria[self.kiTestBoxes] is oCrit;
|
---|
833 |
|
---|
834 | # Testbox OS and OS version.
|
---|
835 | oCrit = FilterCriterion('OS / version', sVarNm = 'os', sTable = 'TestBoxesWithStrings', sColumn = 'idStrOs',
|
---|
836 | oSub = FilterCriterion('OS Versions', sVarNm = 'ov',
|
---|
837 | sTable = 'TestBoxesWithStrings', sColumn = 'idStrOsVersion'));
|
---|
838 | self.aCriteria.append(oCrit);
|
---|
839 | assert self.aCriteria[self.kiOses] is oCrit;
|
---|
840 |
|
---|
841 | # Testbox CPU architectures.
|
---|
842 | oCrit = FilterCriterion('CPU arches', sVarNm = 'ca', sTable = 'TestBoxesWithStrings', sColumn = 'idStrCpuArch');
|
---|
843 | self.aCriteria.append(oCrit);
|
---|
844 | assert self.aCriteria[self.kiCpuArches] is oCrit;
|
---|
845 |
|
---|
846 | # Testbox CPU vendors and revisions.
|
---|
847 | oCrit = FilterCriterion('CPU vendor / rev', sVarNm = 'cv', sTable = 'TestBoxesWithStrings', sColumn = 'idStrCpuVendor',
|
---|
848 | oSub = FilterCriterion('CPU revisions', sVarNm = 'cr',
|
---|
849 | sTable = 'TestBoxesWithStrings', sColumn = 'lCpuRevision'));
|
---|
850 | self.aCriteria.append(oCrit);
|
---|
851 | assert self.aCriteria[self.kiCpuVendors] is oCrit;
|
---|
852 |
|
---|
853 | # Testbox CPU (thread) count
|
---|
854 | oCrit = FilterCriterion('CPU counts', sVarNm = 'cc', sTable = 'TestBoxesWithStrings', sColumn = 'cCpus');
|
---|
855 | self.aCriteria.append(oCrit);
|
---|
856 | assert self.aCriteria[self.kiCpuCounts] is oCrit;
|
---|
857 |
|
---|
858 | # Testbox memory sizes.
|
---|
859 | oCrit = FilterCriterion('Memory', sVarNm = 'mb', sTable = 'TestBoxesWithStrings', sColumn = 'cMbMemory');
|
---|
860 | self.aCriteria.append(oCrit);
|
---|
861 | assert self.aCriteria[self.kiMemory] is oCrit;
|
---|
862 |
|
---|
863 | # Testbox features.
|
---|
864 | oCrit = FilterCriterion('Testbox features', sVarNm = 'tm', sKind = FilterCriterion.ksKind_Special,
|
---|
865 | sTable = 'TestBoxesWithStrings');
|
---|
866 | oCrit.aoPossible = [
|
---|
867 | FilterCriterionValueAndDescription(self.kiTbMisc_NestedPaging, "req nested paging"),
|
---|
868 | FilterCriterionValueAndDescription(self.kiTbMisc_NoNestedPaging, "w/o nested paging"),
|
---|
869 | #FilterCriterionValueAndDescription(self.kiTbMisc_RawMode, "req raw-mode"), - not implemented yet.
|
---|
870 | #FilterCriterionValueAndDescription(self.kiTbMisc_NoRawMode, "w/o raw-mode"), - not implemented yet.
|
---|
871 | FilterCriterionValueAndDescription(self.kiTbMisc_64BitGuest, "req 64-bit guests"),
|
---|
872 | FilterCriterionValueAndDescription(self.kiTbMisc_No64BitGuest, "w/o 64-bit guests"),
|
---|
873 | FilterCriterionValueAndDescription(self.kiTbMisc_HwVirt, "req VT-x / AMD-V"),
|
---|
874 | FilterCriterionValueAndDescription(self.kiTbMisc_NoHwVirt, "w/o VT-x / AMD-V"),
|
---|
875 | #FilterCriterionValueAndDescription(self.kiTbMisc_IoMmu, "req I/O MMU"), - not implemented yet.
|
---|
876 | #FilterCriterionValueAndDescription(self.kiTbMisc_NoIoMmu, "w/o I/O MMU"), - not implemented yet.
|
---|
877 | ];
|
---|
878 | self.aCriteria.append(oCrit);
|
---|
879 | assert self.aCriteria[self.kiTestboxMisc] is oCrit;
|
---|
880 |
|
---|
881 | # Testbox python versions.
|
---|
882 | oCrit = FilterCriterion('Python', sVarNm = 'py', sTable = 'TestBoxesWithStrings', sColumn = 'iPythonHexVersion');
|
---|
883 | self.aCriteria.append(oCrit);
|
---|
884 | assert self.aCriteria[self.kiPythonVersions] is oCrit;
|
---|
885 |
|
---|
886 | # Scheduling groups.
|
---|
887 | oCrit = FilterCriterion('Sched groups', sVarNm = 'sg', sTable = 'TestSets', sColumn = 'idSchedGroup');
|
---|
888 | self.aCriteria.append(oCrit);
|
---|
889 | assert self.aCriteria[self.kiSchedGroups] is oCrit;
|
---|
890 |
|
---|
891 |
|
---|
892 | kdTbMiscConditions = {
|
---|
893 | kiTbMisc_NestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging IS TRUE',
|
---|
894 | kiTbMisc_NoNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging IS FALSE',
|
---|
895 | kiTbMisc_RawMode: 'TestBoxesWithStrings.fRawMode IS TRUE',
|
---|
896 | kiTbMisc_NoRawMode: 'TestBoxesWithStrings.fRawMode IS NOT TRUE',
|
---|
897 | kiTbMisc_64BitGuest: 'TestBoxesWithStrings.fCpu64BitGuest IS TRUE',
|
---|
898 | kiTbMisc_No64BitGuest: 'TestBoxesWithStrings.fCpu64BitGuest IS FALSE',
|
---|
899 | kiTbMisc_HwVirt: 'TestBoxesWithStrings.fCpuHwVirt IS TRUE',
|
---|
900 | kiTbMisc_NoHwVirt: 'TestBoxesWithStrings.fCpuHwVirt IS FALSE',
|
---|
901 | kiTbMisc_IoMmu: 'TestBoxesWithStrings.fChipsetIoMmu IS TRUE',
|
---|
902 | kiTbMisc_NoIoMmu: 'TestBoxesWithStrings.fChipsetIoMmu IS FALSE',
|
---|
903 | };
|
---|
904 |
|
---|
905 | def _getWhereWorker(self, iCrit, oCrit, sExtraIndent, iOmit):
|
---|
906 | """ Formats one - main or sub. """
|
---|
907 | sQuery = '';
|
---|
908 | if oCrit.sState == FilterCriterion.ksState_Selected and iCrit != iOmit:
|
---|
909 | if iCrit == self.kiTestCaseMisc:
|
---|
910 | for iValue, sLike in self.kaTcMisc:
|
---|
911 | if iValue in oCrit.aoSelected: sNot = '';
|
---|
912 | elif iValue + 32 in oCrit.aoSelected: sNot = 'NOT ';
|
---|
913 | else: continue;
|
---|
914 | sQuery += '%s AND %s (' % (sExtraIndent, sNot,);
|
---|
915 | if len(sLike) <= 3: # do word matching for small substrings (hw, np, smp, uni, ++).
|
---|
916 | sQuery += 'TestCases.sName ~ \'.*\\y%s\\y.*\' ' \
|
---|
917 | 'OR COALESCE(TestCaseArgs.sSubName, \'\') ~ \'.*\\y%s\\y.*\')\n' \
|
---|
918 | % ( sLike, sLike,);
|
---|
919 | else:
|
---|
920 | sQuery += 'TestCases.sName LIKE \'%%%s%%\' ' \
|
---|
921 | 'OR COALESCE(TestCaseArgs.sSubName, \'\') LIKE \'%%%s%%\')\n' \
|
---|
922 | % ( sLike, sLike,);
|
---|
923 | elif iCrit == self.kiTestboxMisc:
|
---|
924 | dConditions = self.kdTbMiscConditions;
|
---|
925 | for iValue in oCrit.aoSelected:
|
---|
926 | if iValue in dConditions:
|
---|
927 | sQuery += '%s AND %s\n' % (sExtraIndent, dConditions[iValue],);
|
---|
928 | elif oCrit.sType == FilterCriterion.ksType_Ranges:
|
---|
929 | assert not oCrit.aoPossible;
|
---|
930 | if oCrit.aoSelected:
|
---|
931 | asConditions = [];
|
---|
932 | for tRange in oCrit.aoSelected:
|
---|
933 | if tRange[0] == tRange[1]:
|
---|
934 | asConditions.append('%s.%s = %s' % (oCrit.asTables[0], oCrit.sColumn, tRange[0]));
|
---|
935 | elif tRange[1] is None: # 9999-
|
---|
936 | asConditions.append('%s.%s >= %s' % (oCrit.asTables[0], oCrit.sColumn, tRange[0]));
|
---|
937 | elif tRange[0] is None: # -9999
|
---|
938 | asConditions.append('%s.%s <= %s' % (oCrit.asTables[0], oCrit.sColumn, tRange[1]));
|
---|
939 | else:
|
---|
940 | asConditions.append('%s.%s BETWEEN %s AND %s' % (oCrit.asTables[0], oCrit.sColumn,
|
---|
941 | tRange[0], tRange[1]));
|
---|
942 | if not oCrit.fInverted:
|
---|
943 | sQuery += '%s AND (%s)\n' % (sExtraIndent, ' OR '.join(asConditions));
|
---|
944 | else:
|
---|
945 | sQuery += '%s AND NOT (%s)\n' % (sExtraIndent, ' OR '.join(asConditions));
|
---|
946 | else:
|
---|
947 | assert len(oCrit.asTables) == 1;
|
---|
948 | sQuery += '%s AND (' % (sExtraIndent,);
|
---|
949 |
|
---|
950 | if oCrit.sType != FilterCriterion.ksType_UIntNil or max(oCrit.aoSelected) != -1:
|
---|
951 | if iCrit == self.kiMemory:
|
---|
952 | sQuery += '(%s.%s / 1024)' % (oCrit.asTables[0], oCrit.sColumn,);
|
---|
953 | else:
|
---|
954 | sQuery += '%s.%s' % (oCrit.asTables[0], oCrit.sColumn,);
|
---|
955 | if not oCrit.fInverted:
|
---|
956 | sQuery += ' IN (';
|
---|
957 | else:
|
---|
958 | sQuery += ' NOT IN (';
|
---|
959 | if oCrit.sType == FilterCriterion.ksType_String:
|
---|
960 | sQuery += ', '.join('\'%s\'' % (sValue,) for sValue in oCrit.aoSelected) + ')';
|
---|
961 | else:
|
---|
962 | sQuery += ', '.join(str(iValue) for iValue in oCrit.aoSelected if iValue != -1) + ')';
|
---|
963 |
|
---|
964 | if oCrit.sType == FilterCriterion.ksType_UIntNil \
|
---|
965 | and -1 in oCrit.aoSelected:
|
---|
966 | if sQuery[-1] != '(': sQuery += ' OR ';
|
---|
967 | sQuery += '%s.%s IS NULL' % (oCrit.asTables[0], oCrit.sColumn,);
|
---|
968 |
|
---|
969 | if iCrit == self.kiFailReasons:
|
---|
970 | if oCrit.fInverted:
|
---|
971 | sQuery += '%s OR TestResultFailures.idFailureReason IS NULL\n' % (sExtraIndent,);
|
---|
972 | else:
|
---|
973 | sQuery += '%s AND TestSets.enmStatus >= \'failure\'::TestStatus_T\n' % (sExtraIndent,);
|
---|
974 | sQuery += ')\n';
|
---|
975 | if oCrit.oSub is not None:
|
---|
976 | sQuery += self._getWhereWorker(iCrit | (((iCrit >> 8) + 1) << 8), oCrit.oSub, sExtraIndent, iOmit);
|
---|
977 | return sQuery;
|
---|
978 |
|
---|
979 | def getWhereConditions(self, sExtraIndent = '', iOmit = -1):
|
---|
980 | """
|
---|
981 | Construct the WHERE conditions for the filter, optionally omitting one
|
---|
982 | criterion.
|
---|
983 | """
|
---|
984 | sQuery = '';
|
---|
985 | for iCrit, oCrit in enumerate(self.aCriteria):
|
---|
986 | sQuery += self._getWhereWorker(iCrit, oCrit, sExtraIndent, iOmit);
|
---|
987 | return sQuery;
|
---|
988 |
|
---|
989 | def getTableJoins(self, sExtraIndent = '', iOmit = -1, dOmitTables = None):
|
---|
990 | """
|
---|
991 | Construct the WHERE conditions for the filter, optionally omitting one
|
---|
992 | criterion.
|
---|
993 | """
|
---|
994 | afDone = { 'TestSets': True, };
|
---|
995 | if dOmitTables is not None:
|
---|
996 | afDone.update(dOmitTables);
|
---|
997 |
|
---|
998 | sQuery = '';
|
---|
999 | for iCrit, oCrit in enumerate(self.aCriteria):
|
---|
1000 | if oCrit.sState == FilterCriterion.ksState_Selected \
|
---|
1001 | and iCrit != iOmit:
|
---|
1002 | for sTable in oCrit.asTables:
|
---|
1003 | if sTable not in afDone:
|
---|
1004 | afDone[sTable] = True;
|
---|
1005 | if sTable == 'Builds':
|
---|
1006 | sQuery += '%sINNER JOIN Builds\n' \
|
---|
1007 | '%s ON Builds.idBuild = TestSets.idBuild\n' \
|
---|
1008 | '%s AND Builds.tsExpire > TestSets.tsCreated\n' \
|
---|
1009 | '%s AND Builds.tsEffective <= TestSets.tsCreated\n' \
|
---|
1010 | % ( sExtraIndent, sExtraIndent, sExtraIndent, sExtraIndent, );
|
---|
1011 | elif sTable == 'BuildCategories':
|
---|
1012 | sQuery += '%sINNER JOIN BuildCategories\n' \
|
---|
1013 | '%s ON BuildCategories.idBuildCategory = TestSets.idBuildCategory\n' \
|
---|
1014 | % ( sExtraIndent, sExtraIndent, );
|
---|
1015 | elif sTable == 'TestBoxesWithStrings':
|
---|
1016 | sQuery += '%sLEFT OUTER JOIN TestBoxesWithStrings\n' \
|
---|
1017 | '%s ON TestBoxesWithStrings.idGenTestBox = TestSets.idGenTestBox\n' \
|
---|
1018 | % ( sExtraIndent, sExtraIndent, );
|
---|
1019 | elif sTable == 'TestCases':
|
---|
1020 | sQuery += '%sINNER JOIN TestCases\n' \
|
---|
1021 | '%s ON TestCases.idGenTestCase = TestSets.idGenTestCase\n' \
|
---|
1022 | % ( sExtraIndent, sExtraIndent, );
|
---|
1023 | elif sTable == 'TestCaseArgs':
|
---|
1024 | sQuery += '%sINNER JOIN TestCaseArgs\n' \
|
---|
1025 | '%s ON TestCaseArgs.idGenTestCaseArgs = TestSets.idGenTestCaseArgs\n' \
|
---|
1026 | % ( sExtraIndent, sExtraIndent, );
|
---|
1027 | elif sTable == 'TestResults':
|
---|
1028 | sQuery += '%sINNER JOIN TestResults\n' \
|
---|
1029 | '%s ON TestResults.idTestResult = TestSets.idTestResult\n' \
|
---|
1030 | % ( sExtraIndent, sExtraIndent, );
|
---|
1031 | elif sTable == 'TestResultFailures':
|
---|
1032 | sQuery += '%sLEFT OUTER JOIN TestResultFailures\n' \
|
---|
1033 | '%s ON TestResultFailures.idTestSet = TestSets.idTestSet\n' \
|
---|
1034 | '%s AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' \
|
---|
1035 | % ( sExtraIndent, sExtraIndent, sExtraIndent, );
|
---|
1036 | else:
|
---|
1037 | assert False, sTable;
|
---|
1038 | return sQuery;
|
---|
1039 |
|
---|
1040 | def isJoiningWithTable(self, sTable):
|
---|
1041 | """ Checks whether getTableJoins already joins with TestResultFailures. """
|
---|
1042 | for oCrit in self.aCriteria:
|
---|
1043 | if oCrit.sState == FilterCriterion.ksState_Selected and sTable in oCrit.asTables:
|
---|
1044 | return True;
|
---|
1045 | return False
|
---|
1046 |
|
---|
1047 |
|
---|
1048 |
|
---|
1049 | class TestResultLogic(ModelLogicBase): # pylint: disable=too-few-public-methods
|
---|
1050 | """
|
---|
1051 | Results grouped by scheduling group.
|
---|
1052 | """
|
---|
1053 |
|
---|
1054 | #
|
---|
1055 | # Result grinding for displaying in the WUI.
|
---|
1056 | #
|
---|
1057 |
|
---|
1058 | ksResultsGroupingTypeNone = 'ResultsGroupingTypeNone';
|
---|
1059 | ksResultsGroupingTypeTestGroup = 'ResultsGroupingTypeTestGroup';
|
---|
1060 | ksResultsGroupingTypeBuildCat = 'ResultsGroupingTypeBuildCat';
|
---|
1061 | ksResultsGroupingTypeBuildRev = 'ResultsGroupingTypeBuildRev';
|
---|
1062 | ksResultsGroupingTypeTestBox = 'ResultsGroupingTypeTestBox';
|
---|
1063 | ksResultsGroupingTypeTestCase = 'ResultsGroupingTypeTestCase';
|
---|
1064 | ksResultsGroupingTypeOS = 'ResultsGroupingTypeOS';
|
---|
1065 | ksResultsGroupingTypeArch = 'ResultsGroupingTypeArch';
|
---|
1066 | ksResultsGroupingTypeSchedGroup = 'ResultsGroupingTypeSchedGroup';
|
---|
1067 |
|
---|
1068 | ## @name Result sorting options.
|
---|
1069 | ## @{
|
---|
1070 | ksResultsSortByRunningAndStart = 'ResultsSortByRunningAndStart'; ##< Default
|
---|
1071 | ksResultsSortByBuildRevision = 'ResultsSortByBuildRevision';
|
---|
1072 | ksResultsSortByTestBoxName = 'ResultsSortByTestBoxName';
|
---|
1073 | ksResultsSortByTestBoxOs = 'ResultsSortByTestBoxOs';
|
---|
1074 | ksResultsSortByTestBoxOsVersion = 'ResultsSortByTestBoxOsVersion';
|
---|
1075 | ksResultsSortByTestBoxOsArch = 'ResultsSortByTestBoxOsArch';
|
---|
1076 | ksResultsSortByTestBoxArch = 'ResultsSortByTestBoxArch';
|
---|
1077 | ksResultsSortByTestBoxCpuVendor = 'ResultsSortByTestBoxCpuVendor';
|
---|
1078 | ksResultsSortByTestBoxCpuName = 'ResultsSortByTestBoxCpuName';
|
---|
1079 | ksResultsSortByTestBoxCpuRev = 'ResultsSortByTestBoxCpuRev';
|
---|
1080 | ksResultsSortByTestBoxCpuFeatures = 'ResultsSortByTestBoxCpuFeatures';
|
---|
1081 | ksResultsSortByTestCaseName = 'ResultsSortByTestCaseName';
|
---|
1082 | ksResultsSortByFailureReason = 'ResultsSortByFailureReason';
|
---|
1083 | kasResultsSortBy = {
|
---|
1084 | ksResultsSortByRunningAndStart,
|
---|
1085 | ksResultsSortByBuildRevision,
|
---|
1086 | ksResultsSortByTestBoxName,
|
---|
1087 | ksResultsSortByTestBoxOs,
|
---|
1088 | ksResultsSortByTestBoxOsVersion,
|
---|
1089 | ksResultsSortByTestBoxOsArch,
|
---|
1090 | ksResultsSortByTestBoxArch,
|
---|
1091 | ksResultsSortByTestBoxCpuVendor,
|
---|
1092 | ksResultsSortByTestBoxCpuName,
|
---|
1093 | ksResultsSortByTestBoxCpuRev,
|
---|
1094 | ksResultsSortByTestBoxCpuFeatures,
|
---|
1095 | ksResultsSortByTestCaseName,
|
---|
1096 | ksResultsSortByFailureReason,
|
---|
1097 | };
|
---|
1098 | ## Used by the WUI for generating the drop down.
|
---|
1099 | kaasResultsSortByTitles = (
|
---|
1100 | ( ksResultsSortByRunningAndStart, 'Running & Start TS' ),
|
---|
1101 | ( ksResultsSortByBuildRevision, 'Build Revision' ),
|
---|
1102 | ( ksResultsSortByTestBoxName, 'TestBox Name' ),
|
---|
1103 | ( ksResultsSortByTestBoxOs, 'O/S' ),
|
---|
1104 | ( ksResultsSortByTestBoxOsVersion, 'O/S Version' ),
|
---|
1105 | ( ksResultsSortByTestBoxOsArch, 'O/S & Architecture' ),
|
---|
1106 | ( ksResultsSortByTestBoxArch, 'Architecture' ),
|
---|
1107 | ( ksResultsSortByTestBoxCpuVendor, 'CPU Vendor' ),
|
---|
1108 | ( ksResultsSortByTestBoxCpuName, 'CPU Vendor & Name' ),
|
---|
1109 | ( ksResultsSortByTestBoxCpuRev, 'CPU Vendor & Revision' ),
|
---|
1110 | ( ksResultsSortByTestBoxCpuFeatures, 'CPU Features' ),
|
---|
1111 | ( ksResultsSortByTestCaseName, 'Test Case Name' ),
|
---|
1112 | ( ksResultsSortByFailureReason, 'Failure Reason' ),
|
---|
1113 | );
|
---|
1114 | ## @}
|
---|
1115 |
|
---|
1116 | ## Default sort by map.
|
---|
1117 | kdResultSortByMap = {
|
---|
1118 | ksResultsSortByRunningAndStart: ( (), None, None, '', '' ),
|
---|
1119 | ksResultsSortByBuildRevision: (
|
---|
1120 | # Sorting tables.
|
---|
1121 | ('Builds',),
|
---|
1122 | # Sorting table join(s).
|
---|
1123 | ' AND TestSets.idBuild = Builds.idBuild'
|
---|
1124 | ' AND Builds.tsExpire >= TestSets.tsCreated'
|
---|
1125 | ' AND Builds.tsEffective <= TestSets.tsCreated',
|
---|
1126 | # Start of ORDER BY statement.
|
---|
1127 | ' Builds.iRevision DESC',
|
---|
1128 | # Extra columns to fetch for the above ORDER BY to work in a SELECT DISTINCT statement.
|
---|
1129 | '',
|
---|
1130 | # Columns for the GROUP BY
|
---|
1131 | ''),
|
---|
1132 | ksResultsSortByTestBoxName: (
|
---|
1133 | ('TestBoxes',),
|
---|
1134 | ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
|
---|
1135 | ' TestBoxes.sName DESC',
|
---|
1136 | '', '' ),
|
---|
1137 | ksResultsSortByTestBoxOsArch: (
|
---|
1138 | ('TestBoxesWithStrings',),
|
---|
1139 | ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
|
---|
1140 | ' TestBoxesWithStrings.sOs, TestBoxesWithStrings.sCpuArch',
|
---|
1141 | '', '' ),
|
---|
1142 | ksResultsSortByTestBoxOs: (
|
---|
1143 | ('TestBoxesWithStrings',),
|
---|
1144 | ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
|
---|
1145 | ' TestBoxesWithStrings.sOs',
|
---|
1146 | '', '' ),
|
---|
1147 | ksResultsSortByTestBoxOsVersion: (
|
---|
1148 | ('TestBoxesWithStrings',),
|
---|
1149 | ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
|
---|
1150 | ' TestBoxesWithStrings.sOs, TestBoxesWithStrings.sOsVersion DESC',
|
---|
1151 | '', '' ),
|
---|
1152 | ksResultsSortByTestBoxArch: (
|
---|
1153 | ('TestBoxesWithStrings',),
|
---|
1154 | ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
|
---|
1155 | ' TestBoxesWithStrings.sCpuArch',
|
---|
1156 | '', '' ),
|
---|
1157 | ksResultsSortByTestBoxCpuVendor: (
|
---|
1158 | ('TestBoxesWithStrings',),
|
---|
1159 | ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
|
---|
1160 | ' TestBoxesWithStrings.sCpuVendor',
|
---|
1161 | '', '' ),
|
---|
1162 | ksResultsSortByTestBoxCpuName: (
|
---|
1163 | ('TestBoxesWithStrings',),
|
---|
1164 | ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
|
---|
1165 | ' TestBoxesWithStrings.sCpuVendor, TestBoxesWithStrings.sCpuName',
|
---|
1166 | '', '' ),
|
---|
1167 | ksResultsSortByTestBoxCpuRev: (
|
---|
1168 | ('TestBoxesWithStrings',),
|
---|
1169 | ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
|
---|
1170 | ' TestBoxesWithStrings.sCpuVendor, TestBoxesWithStrings.lCpuRevision DESC',
|
---|
1171 | ', TestBoxesWithStrings.lCpuRevision',
|
---|
1172 | ', TestBoxesWithStrings.lCpuRevision' ),
|
---|
1173 | ksResultsSortByTestBoxCpuFeatures: (
|
---|
1174 | ('TestBoxes',),
|
---|
1175 | ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
|
---|
1176 | ' TestBoxes.fCpuHwVirt DESC, TestBoxes.fCpuNestedPaging DESC, TestBoxes.fCpu64BitGuest DESC, TestBoxes.cCpus DESC',
|
---|
1177 | '',
|
---|
1178 | '' ),
|
---|
1179 | ksResultsSortByTestCaseName: (
|
---|
1180 | ('TestCases',),
|
---|
1181 | ' AND TestSets.idGenTestCase = TestCases.idGenTestCase',
|
---|
1182 | ' TestCases.sName',
|
---|
1183 | '', '' ),
|
---|
1184 | ksResultsSortByFailureReason: (
|
---|
1185 | (), '',
|
---|
1186 | 'asSortByFailureReason ASC',
|
---|
1187 | ', array_agg(FailureReasons.sShort ORDER BY TestResultFailures.idTestResult) AS asSortByFailureReason',
|
---|
1188 | '' ),
|
---|
1189 | };
|
---|
1190 |
|
---|
1191 | kdResultGroupingMap = {
|
---|
1192 | ksResultsGroupingTypeNone: (
|
---|
1193 | # Grouping tables;
|
---|
1194 | (),
|
---|
1195 | # Grouping field;
|
---|
1196 | None,
|
---|
1197 | # Grouping where addition.
|
---|
1198 | None,
|
---|
1199 | # Sort by overrides.
|
---|
1200 | {},
|
---|
1201 | ),
|
---|
1202 | ksResultsGroupingTypeTestGroup: ('', 'TestSets.idTestGroup', None, {},),
|
---|
1203 | ksResultsGroupingTypeTestBox: ('', 'TestSets.idTestBox', None, {},),
|
---|
1204 | ksResultsGroupingTypeTestCase: ('', 'TestSets.idTestCase', None, {},),
|
---|
1205 | ksResultsGroupingTypeOS: (
|
---|
1206 | ('TestBoxes',),
|
---|
1207 | 'TestBoxes.idStrOs',
|
---|
1208 | ' AND TestBoxes.idGenTestBox = TestSets.idGenTestBox',
|
---|
1209 | {},
|
---|
1210 | ),
|
---|
1211 | ksResultsGroupingTypeArch: (
|
---|
1212 | ('TestBoxes',),
|
---|
1213 | 'TestBoxes.idStrCpuArch',
|
---|
1214 | ' AND TestBoxes.idGenTestBox = TestSets.idGenTestBox',
|
---|
1215 | {},
|
---|
1216 | ),
|
---|
1217 | ksResultsGroupingTypeBuildCat: ('', 'TestSets.idBuildCategory', None, {},),
|
---|
1218 | ksResultsGroupingTypeBuildRev: (
|
---|
1219 | ('Builds',),
|
---|
1220 | 'Builds.iRevision',
|
---|
1221 | ' AND Builds.idBuild = TestSets.idBuild'
|
---|
1222 | ' AND Builds.tsExpire > TestSets.tsCreated'
|
---|
1223 | ' AND Builds.tsEffective <= TestSets.tsCreated',
|
---|
1224 | { ksResultsSortByBuildRevision: ( (), None, ' Builds.iRevision DESC' ), }
|
---|
1225 | ),
|
---|
1226 | ksResultsGroupingTypeSchedGroup: ( '', 'TestSets.idSchedGroup', None, {},),
|
---|
1227 | };
|
---|
1228 |
|
---|
1229 |
|
---|
1230 | def __init__(self, oDb):
|
---|
1231 | ModelLogicBase.__init__(self, oDb)
|
---|
1232 | self.oFailureReasonLogic = None;
|
---|
1233 | self.oUserAccountLogic = None;
|
---|
1234 |
|
---|
1235 | def _getTimePeriodQueryPart(self, tsNow, sInterval, sExtraIndent = ''):
|
---|
1236 | """
|
---|
1237 | Get part of SQL query responsible for SELECT data within
|
---|
1238 | specified period of time.
|
---|
1239 | """
|
---|
1240 | assert sInterval is not None; # too many rows.
|
---|
1241 |
|
---|
1242 | cMonthsMourningPeriod = 2; # Stop reminding everyone about testboxes after 2 months. (May also speed up the query.)
|
---|
1243 | if tsNow is None:
|
---|
1244 | sRet = '(TestSets.tsDone IS NULL OR TestSets.tsDone >= (CURRENT_TIMESTAMP - \'%s\'::interval))\n' \
|
---|
1245 | '%s AND TestSets.tsCreated >= (CURRENT_TIMESTAMP - \'%s\'::interval - \'%u months\'::interval)\n' \
|
---|
1246 | % ( sInterval,
|
---|
1247 | sExtraIndent, sInterval, cMonthsMourningPeriod);
|
---|
1248 | else:
|
---|
1249 | sTsNow = '\'%s\'::TIMESTAMP' % (tsNow,); # It's actually a string already. duh.
|
---|
1250 | sRet = 'TestSets.tsCreated <= %s\n' \
|
---|
1251 | '%s AND TestSets.tsCreated >= (%s - \'%s\'::interval - \'%u months\'::interval)\n' \
|
---|
1252 | '%s AND (TestSets.tsDone IS NULL OR TestSets.tsDone >= (%s - \'%s\'::interval))\n' \
|
---|
1253 | % ( sTsNow,
|
---|
1254 | sExtraIndent, sTsNow, sInterval, cMonthsMourningPeriod,
|
---|
1255 | sExtraIndent, sTsNow, sInterval );
|
---|
1256 | return sRet
|
---|
1257 |
|
---|
1258 | def fetchResultsForListing(self, iStart, cMaxRows, tsNow, sInterval, oFilter, enmResultSortBy, # pylint: disable=too-many-arguments
|
---|
1259 | enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures, fOnlyNeedingReason):
|
---|
1260 | """
|
---|
1261 | Fetches TestResults table content.
|
---|
1262 |
|
---|
1263 | If @param enmResultsGroupingType and @param iResultsGroupingValue
|
---|
1264 | are not None, then resulting (returned) list contains only records
|
---|
1265 | that match specified @param enmResultsGroupingType.
|
---|
1266 |
|
---|
1267 | If @param enmResultsGroupingType is None, then
|
---|
1268 | @param iResultsGroupingValue is ignored.
|
---|
1269 |
|
---|
1270 | Returns an array (list) of TestResultData items, empty list if none.
|
---|
1271 | Raises exception on error.
|
---|
1272 | """
|
---|
1273 |
|
---|
1274 | _ = oFilter;
|
---|
1275 |
|
---|
1276 | #
|
---|
1277 | # Get SQL query parameters
|
---|
1278 | #
|
---|
1279 | if enmResultsGroupingType is None or enmResultsGroupingType not in self.kdResultGroupingMap:
|
---|
1280 | raise TMExceptionBase('Unknown grouping type');
|
---|
1281 | if enmResultSortBy is None or enmResultSortBy not in self.kasResultsSortBy:
|
---|
1282 | raise TMExceptionBase('Unknown sorting');
|
---|
1283 | asGroupingTables, sGroupingField, sGroupingCondition, dSortOverrides = self.kdResultGroupingMap[enmResultsGroupingType];
|
---|
1284 | if enmResultSortBy in dSortOverrides:
|
---|
1285 | asSortTables, sSortWhere, sSortOrderBy, sSortColumns, sSortGroupBy = dSortOverrides[enmResultSortBy];
|
---|
1286 | else:
|
---|
1287 | asSortTables, sSortWhere, sSortOrderBy, sSortColumns, sSortGroupBy = self.kdResultSortByMap[enmResultSortBy];
|
---|
1288 |
|
---|
1289 | #
|
---|
1290 | # Construct the query.
|
---|
1291 | #
|
---|
1292 | sQuery = 'SELECT DISTINCT TestSets.idTestSet,\n' \
|
---|
1293 | ' BuildCategories.idBuildCategory,\n' \
|
---|
1294 | ' BuildCategories.sProduct,\n' \
|
---|
1295 | ' BuildCategories.sRepository,\n' \
|
---|
1296 | ' BuildCategories.sBranch,\n' \
|
---|
1297 | ' BuildCategories.sType,\n' \
|
---|
1298 | ' Builds.idBuild,\n' \
|
---|
1299 | ' Builds.sVersion,\n' \
|
---|
1300 | ' Builds.iRevision,\n' \
|
---|
1301 | ' TestBoxesWithStrings.sOs,\n' \
|
---|
1302 | ' TestBoxesWithStrings.sOsVersion,\n' \
|
---|
1303 | ' TestBoxesWithStrings.sCpuArch,\n' \
|
---|
1304 | ' TestBoxesWithStrings.sCpuVendor,\n' \
|
---|
1305 | ' TestBoxesWithStrings.sCpuName,\n' \
|
---|
1306 | ' TestBoxesWithStrings.cCpus,\n' \
|
---|
1307 | ' TestBoxesWithStrings.fCpuHwVirt,\n' \
|
---|
1308 | ' TestBoxesWithStrings.fCpuNestedPaging,\n' \
|
---|
1309 | ' TestBoxesWithStrings.fCpu64BitGuest,\n' \
|
---|
1310 | ' TestBoxesWithStrings.idTestBox,\n' \
|
---|
1311 | ' TestBoxesWithStrings.sName,\n' \
|
---|
1312 | ' TestResults.tsCreated,\n' \
|
---|
1313 | ' COALESCE(TestResults.tsElapsed, CURRENT_TIMESTAMP - TestResults.tsCreated) AS tsElapsedTestResult,\n' \
|
---|
1314 | ' TestSets.enmStatus,\n' \
|
---|
1315 | ' TestResults.cErrors,\n' \
|
---|
1316 | ' TestCases.idTestCase,\n' \
|
---|
1317 | ' TestCases.sName,\n' \
|
---|
1318 | ' TestCases.sBaseCmd,\n' \
|
---|
1319 | ' TestCaseArgs.sArgs,\n' \
|
---|
1320 | ' TestCaseArgs.sSubName,\n' \
|
---|
1321 | ' TestSuiteBits.idBuild AS idBuildTestSuite,\n' \
|
---|
1322 | ' TestSuiteBits.iRevision AS iRevisionTestSuite,\n' \
|
---|
1323 | ' array_agg(TestResultFailures.idFailureReason ORDER BY TestResultFailures.idTestResult),\n' \
|
---|
1324 | ' array_agg(TestResultFailures.uidAuthor ORDER BY TestResultFailures.idTestResult),\n' \
|
---|
1325 | ' array_agg(TestResultFailures.tsEffective ORDER BY TestResultFailures.idTestResult),\n' \
|
---|
1326 | ' array_agg(TestResultFailures.sComment ORDER BY TestResultFailures.idTestResult),\n' \
|
---|
1327 | ' (TestSets.tsDone IS NULL) SortRunningFirst' + sSortColumns + '\n' \
|
---|
1328 | 'FROM ( SELECT TestSets.idTestSet AS idTestSet,\n' \
|
---|
1329 | ' TestSets.tsDone AS tsDone,\n' \
|
---|
1330 | ' TestSets.tsCreated AS tsCreated,\n' \
|
---|
1331 | ' TestSets.enmStatus AS enmStatus,\n' \
|
---|
1332 | ' TestSets.idBuild AS idBuild,\n' \
|
---|
1333 | ' TestSets.idBuildTestSuite AS idBuildTestSuite,\n' \
|
---|
1334 | ' TestSets.idGenTestBox AS idGenTestBox,\n' \
|
---|
1335 | ' TestSets.idGenTestCase AS idGenTestCase,\n' \
|
---|
1336 | ' TestSets.idGenTestCaseArgs AS idGenTestCaseArgs\n' \
|
---|
1337 | ' FROM TestSets\n';
|
---|
1338 | sQuery += oFilter.getTableJoins(' ');
|
---|
1339 | if fOnlyNeedingReason and not oFilter.isJoiningWithTable('TestResultFailures'):
|
---|
1340 | sQuery += '\n' \
|
---|
1341 | ' LEFT OUTER JOIN TestResultFailures\n' \
|
---|
1342 | ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \
|
---|
1343 | ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP';
|
---|
1344 | for asTables in [asGroupingTables, asSortTables]:
|
---|
1345 | for sTable in asTables:
|
---|
1346 | if not oFilter.isJoiningWithTable(sTable):
|
---|
1347 | sQuery = sQuery[:-1] + ',\n ' + sTable + '\n';
|
---|
1348 |
|
---|
1349 | sQuery += ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval, ' ') + \
|
---|
1350 | oFilter.getWhereConditions(' ');
|
---|
1351 | if fOnlyFailures or fOnlyNeedingReason:
|
---|
1352 | sQuery += ' AND TestSets.enmStatus != \'success\'::TestStatus_T\n' \
|
---|
1353 | ' AND TestSets.enmStatus != \'running\'::TestStatus_T\n';
|
---|
1354 | if fOnlyNeedingReason:
|
---|
1355 | sQuery += ' AND TestResultFailures.idTestSet IS NULL\n';
|
---|
1356 | if sGroupingField is not None:
|
---|
1357 | sQuery += ' AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,);
|
---|
1358 | if sGroupingCondition is not None:
|
---|
1359 | sQuery += sGroupingCondition.replace(' AND ', ' AND ');
|
---|
1360 | if sSortWhere is not None:
|
---|
1361 | sQuery += sSortWhere.replace(' AND ', ' AND ');
|
---|
1362 | sQuery += ' ORDER BY ';
|
---|
1363 | if sSortOrderBy is not None and sSortOrderBy.find('FailureReason') < 0:
|
---|
1364 | sQuery += sSortOrderBy + ',\n ';
|
---|
1365 | sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n' \
|
---|
1366 | ' LIMIT %s OFFSET %s\n' % (cMaxRows, iStart,);
|
---|
1367 |
|
---|
1368 | # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
|
---|
1369 | # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster.
|
---|
1370 | sQuery += ' ) AS TestSets\n' \
|
---|
1371 | ' LEFT OUTER JOIN TestBoxesWithStrings\n' \
|
---|
1372 | ' ON TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox' \
|
---|
1373 | ' LEFT OUTER JOIN Builds AS TestSuiteBits\n' \
|
---|
1374 | ' ON TestSuiteBits.idBuild = TestSets.idBuildTestSuite\n' \
|
---|
1375 | ' AND TestSuiteBits.tsExpire > TestSets.tsCreated\n' \
|
---|
1376 | ' AND TestSuiteBits.tsEffective <= TestSets.tsCreated\n' \
|
---|
1377 | ' LEFT OUTER JOIN TestResultFailures\n' \
|
---|
1378 | ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \
|
---|
1379 | ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP';
|
---|
1380 | if sSortOrderBy is not None and sSortOrderBy.find('FailureReason') >= 0:
|
---|
1381 | sQuery += '\n' \
|
---|
1382 | ' LEFT OUTER JOIN FailureReasons\n' \
|
---|
1383 | ' ON TestResultFailures.idFailureReason = FailureReasons.idFailureReason\n' \
|
---|
1384 | ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP';
|
---|
1385 | sQuery += ',\n' \
|
---|
1386 | ' BuildCategories,\n' \
|
---|
1387 | ' Builds,\n' \
|
---|
1388 | ' TestResults,\n' \
|
---|
1389 | ' TestCases,\n' \
|
---|
1390 | ' TestCaseArgs\n';
|
---|
1391 | sQuery += 'WHERE TestSets.idTestSet = TestResults.idTestSet\n' \
|
---|
1392 | ' AND TestResults.idTestResultParent is NULL\n' \
|
---|
1393 | ' AND TestSets.idBuild = Builds.idBuild\n' \
|
---|
1394 | ' AND Builds.tsExpire > TestSets.tsCreated\n' \
|
---|
1395 | ' AND Builds.tsEffective <= TestSets.tsCreated\n' \
|
---|
1396 | ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' \
|
---|
1397 | ' AND TestSets.idGenTestCase = TestCases.idGenTestCase\n' \
|
---|
1398 | ' AND TestSets.idGenTestCaseArgs = TestCaseArgs.idGenTestCaseArgs\n';
|
---|
1399 | sQuery += 'GROUP BY TestSets.idTestSet,\n' \
|
---|
1400 | ' BuildCategories.idBuildCategory,\n' \
|
---|
1401 | ' BuildCategories.sProduct,\n' \
|
---|
1402 | ' BuildCategories.sRepository,\n' \
|
---|
1403 | ' BuildCategories.sBranch,\n' \
|
---|
1404 | ' BuildCategories.sType,\n' \
|
---|
1405 | ' Builds.idBuild,\n' \
|
---|
1406 | ' Builds.sVersion,\n' \
|
---|
1407 | ' Builds.iRevision,\n' \
|
---|
1408 | ' TestBoxesWithStrings.sOs,\n' \
|
---|
1409 | ' TestBoxesWithStrings.sOsVersion,\n' \
|
---|
1410 | ' TestBoxesWithStrings.sCpuArch,\n' \
|
---|
1411 | ' TestBoxesWithStrings.sCpuVendor,\n' \
|
---|
1412 | ' TestBoxesWithStrings.sCpuName,\n' \
|
---|
1413 | ' TestBoxesWithStrings.cCpus,\n' \
|
---|
1414 | ' TestBoxesWithStrings.fCpuHwVirt,\n' \
|
---|
1415 | ' TestBoxesWithStrings.fCpuNestedPaging,\n' \
|
---|
1416 | ' TestBoxesWithStrings.fCpu64BitGuest,\n' \
|
---|
1417 | ' TestBoxesWithStrings.idTestBox,\n' \
|
---|
1418 | ' TestBoxesWithStrings.sName,\n' \
|
---|
1419 | ' TestResults.tsCreated,\n' \
|
---|
1420 | ' tsElapsedTestResult,\n' \
|
---|
1421 | ' TestSets.enmStatus,\n' \
|
---|
1422 | ' TestResults.cErrors,\n' \
|
---|
1423 | ' TestCases.idTestCase,\n' \
|
---|
1424 | ' TestCases.sName,\n' \
|
---|
1425 | ' TestCases.sBaseCmd,\n' \
|
---|
1426 | ' TestCaseArgs.sArgs,\n' \
|
---|
1427 | ' TestCaseArgs.sSubName,\n' \
|
---|
1428 | ' TestSuiteBits.idBuild,\n' \
|
---|
1429 | ' TestSuiteBits.iRevision,\n' \
|
---|
1430 | ' SortRunningFirst' + sSortGroupBy + '\n';
|
---|
1431 | sQuery += 'ORDER BY ';
|
---|
1432 | if sSortOrderBy is not None:
|
---|
1433 | sQuery += sSortOrderBy.replace('TestBoxes.', 'TestBoxesWithStrings.') + ',\n ';
|
---|
1434 | sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n';
|
---|
1435 |
|
---|
1436 | #
|
---|
1437 | # Execute the query and return the wrapped results.
|
---|
1438 | #
|
---|
1439 | self._oDb.execute(sQuery);
|
---|
1440 |
|
---|
1441 | if self.oFailureReasonLogic is None:
|
---|
1442 | self.oFailureReasonLogic = FailureReasonLogic(self._oDb);
|
---|
1443 | if self.oUserAccountLogic is None:
|
---|
1444 | self.oUserAccountLogic = UserAccountLogic(self._oDb);
|
---|
1445 |
|
---|
1446 | aoRows = [];
|
---|
1447 | for aoRow in self._oDb.fetchAll():
|
---|
1448 | aoRows.append(TestResultListingData().initFromDbRowEx(aoRow, self.oFailureReasonLogic, self.oUserAccountLogic));
|
---|
1449 |
|
---|
1450 | return aoRows
|
---|
1451 |
|
---|
1452 |
|
---|
1453 | def fetchTimestampsForLogViewer(self, idTestSet):
|
---|
1454 | """
|
---|
1455 | Returns an ordered list with all the test result timestamps, both start
|
---|
1456 | and end.
|
---|
1457 |
|
---|
1458 | The log viewer create anchors in the log text so we can jump directly to
|
---|
1459 | the log lines relevant for a test event.
|
---|
1460 | """
|
---|
1461 | self._oDb.execute('(\n'
|
---|
1462 | 'SELECT tsCreated\n'
|
---|
1463 | 'FROM TestResults\n'
|
---|
1464 | 'WHERE idTestSet = %s\n'
|
---|
1465 | ') UNION (\n'
|
---|
1466 | 'SELECT tsCreated + tsElapsed\n'
|
---|
1467 | 'FROM TestResults\n'
|
---|
1468 | 'WHERE idTestSet = %s\n'
|
---|
1469 | ' AND tsElapsed IS NOT NULL\n'
|
---|
1470 | ') UNION (\n'
|
---|
1471 | 'SELECT TestResultFiles.tsCreated\n'
|
---|
1472 | 'FROM TestResultFiles\n'
|
---|
1473 | 'WHERE idTestSet = %s\n'
|
---|
1474 | ') UNION (\n'
|
---|
1475 | 'SELECT tsCreated\n'
|
---|
1476 | 'FROM TestResultValues\n'
|
---|
1477 | 'WHERE idTestSet = %s\n'
|
---|
1478 | ') UNION (\n'
|
---|
1479 | 'SELECT TestResultMsgs.tsCreated\n'
|
---|
1480 | 'FROM TestResultMsgs\n'
|
---|
1481 | 'WHERE idTestSet = %s\n'
|
---|
1482 | ') ORDER by 1'
|
---|
1483 | , ( idTestSet, idTestSet, idTestSet, idTestSet, idTestSet, ));
|
---|
1484 | return [aoRow[0] for aoRow in self._oDb.fetchAll()];
|
---|
1485 |
|
---|
1486 |
|
---|
1487 | def getEntriesCount(self, tsNow, sInterval, oFilter, enmResultsGroupingType, iResultsGroupingValue,
|
---|
1488 | fOnlyFailures, fOnlyNeedingReason):
|
---|
1489 | """
|
---|
1490 | Get number of table records.
|
---|
1491 |
|
---|
1492 | If @param enmResultsGroupingType and @param iResultsGroupingValue
|
---|
1493 | are not None, then we count only only those records
|
---|
1494 | that match specified @param enmResultsGroupingType.
|
---|
1495 |
|
---|
1496 | If @param enmResultsGroupingType is None, then
|
---|
1497 | @param iResultsGroupingValue is ignored.
|
---|
1498 | """
|
---|
1499 | _ = oFilter;
|
---|
1500 |
|
---|
1501 | #
|
---|
1502 | # Get SQL query parameters
|
---|
1503 | #
|
---|
1504 | if enmResultsGroupingType is None:
|
---|
1505 | raise TMExceptionBase('Unknown grouping type')
|
---|
1506 |
|
---|
1507 | if enmResultsGroupingType not in self.kdResultGroupingMap:
|
---|
1508 | raise TMExceptionBase('Unknown grouping type')
|
---|
1509 | asGroupingTables, sGroupingField, sGroupingCondition, _ = self.kdResultGroupingMap[enmResultsGroupingType];
|
---|
1510 |
|
---|
1511 | #
|
---|
1512 | # Construct the query.
|
---|
1513 | #
|
---|
1514 | sQuery = 'SELECT COUNT(TestSets.idTestSet)\n' \
|
---|
1515 | 'FROM TestSets\n';
|
---|
1516 | sQuery += oFilter.getTableJoins();
|
---|
1517 | if fOnlyNeedingReason and not oFilter.isJoiningWithTable('TestResultFailures'):
|
---|
1518 | sQuery += ' LEFT OUTER JOIN TestResultFailures\n' \
|
---|
1519 | ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \
|
---|
1520 | ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n';
|
---|
1521 | for sTable in asGroupingTables:
|
---|
1522 | if not oFilter.isJoiningWithTable(sTable):
|
---|
1523 | sQuery = sQuery[:-1] + ',\n ' + sTable + '\n';
|
---|
1524 | sQuery += 'WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval) + \
|
---|
1525 | oFilter.getWhereConditions();
|
---|
1526 | if fOnlyFailures or fOnlyNeedingReason:
|
---|
1527 | sQuery += ' AND TestSets.enmStatus != \'success\'::TestStatus_T\n' \
|
---|
1528 | ' AND TestSets.enmStatus != \'running\'::TestStatus_T\n';
|
---|
1529 | if fOnlyNeedingReason:
|
---|
1530 | sQuery += ' AND TestResultFailures.idTestSet IS NULL\n';
|
---|
1531 | if sGroupingField is not None:
|
---|
1532 | sQuery += ' AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,);
|
---|
1533 | if sGroupingCondition is not None:
|
---|
1534 | sQuery += sGroupingCondition.replace(' AND ', ' AND ');
|
---|
1535 |
|
---|
1536 | #
|
---|
1537 | # Execute the query and return the result.
|
---|
1538 | #
|
---|
1539 | self._oDb.execute(sQuery)
|
---|
1540 | return self._oDb.fetchOne()[0]
|
---|
1541 |
|
---|
1542 | def getTestGroups(self, tsNow, sPeriod):
|
---|
1543 | """
|
---|
1544 | Get list of uniq TestGroupData objects which
|
---|
1545 | found in all test results.
|
---|
1546 | """
|
---|
1547 |
|
---|
1548 | self._oDb.execute('SELECT DISTINCT TestGroups.*\n'
|
---|
1549 | 'FROM TestGroups, TestSets\n'
|
---|
1550 | 'WHERE TestSets.idTestGroup = TestGroups.idTestGroup\n'
|
---|
1551 | ' AND TestGroups.tsExpire > TestSets.tsCreated\n'
|
---|
1552 | ' AND TestGroups.tsEffective <= TestSets.tsCreated'
|
---|
1553 | ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
|
---|
1554 | aaoRows = self._oDb.fetchAll()
|
---|
1555 | aoRet = []
|
---|
1556 | for aoRow in aaoRows:
|
---|
1557 | aoRet.append(TestGroupData().initFromDbRow(aoRow))
|
---|
1558 | return aoRet
|
---|
1559 |
|
---|
1560 | def getBuilds(self, tsNow, sPeriod):
|
---|
1561 | """
|
---|
1562 | Get list of uniq BuildDataEx objects which
|
---|
1563 | found in all test results.
|
---|
1564 | """
|
---|
1565 |
|
---|
1566 | self._oDb.execute('SELECT DISTINCT Builds.*, BuildCategories.*\n'
|
---|
1567 | 'FROM Builds, BuildCategories, TestSets\n'
|
---|
1568 | 'WHERE TestSets.idBuild = Builds.idBuild\n'
|
---|
1569 | ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
|
---|
1570 | ' AND Builds.tsExpire > TestSets.tsCreated\n'
|
---|
1571 | ' AND Builds.tsEffective <= TestSets.tsCreated'
|
---|
1572 | ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
|
---|
1573 | aaoRows = self._oDb.fetchAll()
|
---|
1574 | aoRet = []
|
---|
1575 | for aoRow in aaoRows:
|
---|
1576 | aoRet.append(BuildDataEx().initFromDbRow(aoRow))
|
---|
1577 | return aoRet
|
---|
1578 |
|
---|
1579 | def getTestBoxes(self, tsNow, sPeriod):
|
---|
1580 | """
|
---|
1581 | Get list of uniq TestBoxData objects which
|
---|
1582 | found in all test results.
|
---|
1583 | """
|
---|
1584 | # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
|
---|
1585 | # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster.
|
---|
1586 | self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
|
---|
1587 | 'FROM ( SELECT idTestBox AS idTestBox,\n'
|
---|
1588 | ' MAX(idGenTestBox) AS idGenTestBox\n'
|
---|
1589 | ' FROM TestSets\n'
|
---|
1590 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1591 | ' GROUP BY idTestBox\n'
|
---|
1592 | ' ) AS TestBoxIDs\n'
|
---|
1593 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1594 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
|
---|
1595 | 'ORDER BY TestBoxesWithStrings.sName\n' );
|
---|
1596 | aoRet = []
|
---|
1597 | for aoRow in self._oDb.fetchAll():
|
---|
1598 | aoRet.append(TestBoxData().initFromDbRow(aoRow));
|
---|
1599 | return aoRet
|
---|
1600 |
|
---|
1601 | def getTestCases(self, tsNow, sPeriod):
|
---|
1602 | """
|
---|
1603 | Get a list of unique TestCaseData objects which is appears in the test
|
---|
1604 | specified result period.
|
---|
1605 | """
|
---|
1606 |
|
---|
1607 | # Using LEFT OUTER JOIN instead of INNER JOIN in case it performs better, doesn't matter for the result.
|
---|
1608 | self._oDb.execute('SELECT TestCases.*\n'
|
---|
1609 | 'FROM ( SELECT idTestCase AS idTestCase,\n'
|
---|
1610 | ' MAX(idGenTestCase) AS idGenTestCase\n'
|
---|
1611 | ' FROM TestSets\n'
|
---|
1612 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1613 | ' GROUP BY idTestCase\n'
|
---|
1614 | ' ) AS TestCasesIDs\n'
|
---|
1615 | ' LEFT OUTER JOIN TestCases ON TestCases.idGenTestCase = TestCasesIDs.idGenTestCase\n'
|
---|
1616 | 'ORDER BY TestCases.sName\n' );
|
---|
1617 |
|
---|
1618 | aoRet = [];
|
---|
1619 | for aoRow in self._oDb.fetchAll():
|
---|
1620 | aoRet.append(TestCaseData().initFromDbRow(aoRow));
|
---|
1621 | return aoRet
|
---|
1622 |
|
---|
1623 | def getOSes(self, tsNow, sPeriod):
|
---|
1624 | """
|
---|
1625 | Get a list of [idStrOs, sOs] tuples of the OSes that appears in the specified result period.
|
---|
1626 | """
|
---|
1627 |
|
---|
1628 | # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
|
---|
1629 | # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster.
|
---|
1630 | self._oDb.execute('SELECT DISTINCT TestBoxesWithStrings.idStrOs, TestBoxesWithStrings.sOs\n'
|
---|
1631 | 'FROM ( SELECT idTestBox AS idTestBox,\n'
|
---|
1632 | ' MAX(idGenTestBox) AS idGenTestBox\n'
|
---|
1633 | ' FROM TestSets\n'
|
---|
1634 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1635 | ' GROUP BY idTestBox\n'
|
---|
1636 | ' ) AS TestBoxIDs\n'
|
---|
1637 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1638 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
|
---|
1639 | 'ORDER BY TestBoxesWithStrings.sOs\n' );
|
---|
1640 | return self._oDb.fetchAll();
|
---|
1641 |
|
---|
1642 | def getArchitectures(self, tsNow, sPeriod):
|
---|
1643 | """
|
---|
1644 | Get a list of [idStrCpuArch, sCpuArch] tuples of the architecutres
|
---|
1645 | that appears in the specified result period.
|
---|
1646 | """
|
---|
1647 |
|
---|
1648 | # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
|
---|
1649 | # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster.
|
---|
1650 | self._oDb.execute('SELECT DISTINCT TestBoxesWithStrings.idStrCpuArch, TestBoxesWithStrings.sCpuArch\n'
|
---|
1651 | 'FROM ( SELECT idTestBox AS idTestBox,\n'
|
---|
1652 | ' MAX(idGenTestBox) AS idGenTestBox\n'
|
---|
1653 | ' FROM TestSets\n'
|
---|
1654 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1655 | ' GROUP BY idTestBox\n'
|
---|
1656 | ' ) AS TestBoxIDs\n'
|
---|
1657 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1658 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
|
---|
1659 | 'ORDER BY TestBoxesWithStrings.sCpuArch\n' );
|
---|
1660 | return self._oDb.fetchAll();
|
---|
1661 |
|
---|
1662 | def getBuildCategories(self, tsNow, sPeriod):
|
---|
1663 | """
|
---|
1664 | Get a list of BuildCategoryData that appears in the specified result period.
|
---|
1665 | """
|
---|
1666 |
|
---|
1667 | self._oDb.execute('SELECT DISTINCT BuildCategories.*\n'
|
---|
1668 | 'FROM ( SELECT DISTINCT idBuildCategory AS idBuildCategory\n'
|
---|
1669 | ' FROM TestSets\n'
|
---|
1670 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1671 | ' ) AS BuildCategoryIDs\n'
|
---|
1672 | ' LEFT OUTER JOIN BuildCategories\n'
|
---|
1673 | ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n'
|
---|
1674 | 'ORDER BY BuildCategories.sProduct, BuildCategories.sBranch, BuildCategories.sType\n');
|
---|
1675 | aoRet = [];
|
---|
1676 | for aoRow in self._oDb.fetchAll():
|
---|
1677 | aoRet.append(BuildCategoryData().initFromDbRow(aoRow));
|
---|
1678 | return aoRet;
|
---|
1679 |
|
---|
1680 | def getSchedGroups(self, tsNow, sPeriod):
|
---|
1681 | """
|
---|
1682 | Get list of uniq SchedGroupData objects which
|
---|
1683 | found in all test results.
|
---|
1684 | """
|
---|
1685 |
|
---|
1686 | self._oDb.execute('SELECT SchedGroups.*\n'
|
---|
1687 | 'FROM ( SELECT idSchedGroup,\n'
|
---|
1688 | ' MAX(TestSets.tsCreated) AS tsNow\n'
|
---|
1689 | ' FROM TestSets\n'
|
---|
1690 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1691 | ' GROUP BY idSchedGroup\n'
|
---|
1692 | ' ) AS SchedGroupIDs\n'
|
---|
1693 | ' INNER JOIN SchedGroups\n'
|
---|
1694 | ' ON SchedGroups.idSchedGroup = SchedGroupIDs.idSchedGroup\n'
|
---|
1695 | ' AND SchedGroups.tsExpire > SchedGroupIDs.tsNow\n'
|
---|
1696 | ' AND SchedGroups.tsEffective <= SchedGroupIDs.tsNow\n'
|
---|
1697 | 'ORDER BY SchedGroups.sName\n' );
|
---|
1698 | aoRet = []
|
---|
1699 | for aoRow in self._oDb.fetchAll():
|
---|
1700 | aoRet.append(SchedGroupData().initFromDbRow(aoRow));
|
---|
1701 | return aoRet
|
---|
1702 |
|
---|
1703 | def getById(self, idTestResult):
|
---|
1704 | """
|
---|
1705 | Get build record by its id
|
---|
1706 | """
|
---|
1707 | self._oDb.execute('SELECT *\n'
|
---|
1708 | 'FROM TestResults\n'
|
---|
1709 | 'WHERE idTestResult = %s\n',
|
---|
1710 | (idTestResult,))
|
---|
1711 |
|
---|
1712 | aRows = self._oDb.fetchAll()
|
---|
1713 | if len(aRows) not in (0, 1):
|
---|
1714 | raise TMTooManyRows('Found more than one test result with the same credentials. Database structure is corrupted.')
|
---|
1715 | try:
|
---|
1716 | return TestResultData().initFromDbRow(aRows[0])
|
---|
1717 | except IndexError:
|
---|
1718 | return None
|
---|
1719 |
|
---|
1720 | def fetchPossibleFilterOptions(self, oFilter, tsNow, sPeriod, oReportModel = None):
|
---|
1721 | """
|
---|
1722 | Fetches the available filter criteria, given the current filtering.
|
---|
1723 |
|
---|
1724 | Returns oFilter.
|
---|
1725 | """
|
---|
1726 | assert isinstance(oFilter, TestResultFilter);
|
---|
1727 |
|
---|
1728 | # Hack to avoid lot's of conditionals or duplicate this code.
|
---|
1729 | if oReportModel is None:
|
---|
1730 | class DummyReportModel(object):
|
---|
1731 | """ Dummy """
|
---|
1732 | def getExtraSubjectTables(self):
|
---|
1733 | """ Dummy """
|
---|
1734 | return [];
|
---|
1735 | def getExtraSubjectWhereExpr(self):
|
---|
1736 | """ Dummy """
|
---|
1737 | return '';
|
---|
1738 | oReportModel = DummyReportModel();
|
---|
1739 |
|
---|
1740 | def workerDoFetch(oMissingLogicType, sNameAttr = 'sName', fIdIsName = False, idxHover = -1,
|
---|
1741 | idNull = -1, sNullDesc = '<NULL>'):
|
---|
1742 | """ Does the tedious result fetching and handling of missing bits. """
|
---|
1743 | dLeft = { oValue: 1 for oValue in oCrit.aoSelected };
|
---|
1744 | oCrit.aoPossible = [];
|
---|
1745 | for aoRow in self._oDb.fetchAll():
|
---|
1746 | oCrit.aoPossible.append(FilterCriterionValueAndDescription(aoRow[0] if aoRow[0] is not None else idNull,
|
---|
1747 | aoRow[1] if aoRow[1] is not None else sNullDesc,
|
---|
1748 | aoRow[2],
|
---|
1749 | aoRow[idxHover] if idxHover >= 0 else None));
|
---|
1750 | if aoRow[0] in dLeft:
|
---|
1751 | del dLeft[aoRow[0]];
|
---|
1752 | if dLeft:
|
---|
1753 | if fIdIsName:
|
---|
1754 | for idMissing in dLeft:
|
---|
1755 | oCrit.aoPossible.append(FilterCriterionValueAndDescription(idMissing, str(idMissing),
|
---|
1756 | fIrrelevant = True));
|
---|
1757 | else:
|
---|
1758 | oMissingLogic = oMissingLogicType(self._oDb);
|
---|
1759 | for idMissing in dLeft:
|
---|
1760 | oMissing = oMissingLogic.cachedLookup(idMissing);
|
---|
1761 | if oMissing is not None:
|
---|
1762 | oCrit.aoPossible.append(FilterCriterionValueAndDescription(idMissing,
|
---|
1763 | getattr(oMissing, sNameAttr),
|
---|
1764 | fIrrelevant = True));
|
---|
1765 |
|
---|
1766 | def workerDoFetchNested():
|
---|
1767 | """ Does the tedious result fetching and handling of missing bits. """
|
---|
1768 | oCrit.aoPossible = [];
|
---|
1769 | oCrit.oSub.aoPossible = [];
|
---|
1770 | dLeft = { oValue: 1 for oValue in oCrit.aoSelected };
|
---|
1771 | dSubLeft = { oValue: 1 for oValue in oCrit.oSub.aoSelected };
|
---|
1772 | oMain = None;
|
---|
1773 | for aoRow in self._oDb.fetchAll():
|
---|
1774 | if oMain is None or oMain.oValue != aoRow[0]:
|
---|
1775 | oMain = FilterCriterionValueAndDescription(aoRow[0], aoRow[1], 0);
|
---|
1776 | oCrit.aoPossible.append(oMain);
|
---|
1777 | if aoRow[0] in dLeft:
|
---|
1778 | del dLeft[aoRow[0]];
|
---|
1779 | oCurSub = FilterCriterionValueAndDescription(aoRow[2], aoRow[3], aoRow[4]);
|
---|
1780 | oCrit.oSub.aoPossible.append(oCurSub);
|
---|
1781 | if aoRow[2] in dSubLeft:
|
---|
1782 | del dSubLeft[aoRow[2]];
|
---|
1783 |
|
---|
1784 | oMain.aoSubs.append(oCurSub);
|
---|
1785 | oMain.cTimes += aoRow[4];
|
---|
1786 |
|
---|
1787 | if dLeft:
|
---|
1788 | pass; ## @todo
|
---|
1789 |
|
---|
1790 | # Statuses.
|
---|
1791 | oCrit = oFilter.aCriteria[TestResultFilter.kiTestStatus];
|
---|
1792 | self._oDb.execute('SELECT TestSets.enmStatus, TestSets.enmStatus, COUNT(TestSets.idTestSet)\n'
|
---|
1793 | 'FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestStatus) +
|
---|
1794 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1795 | 'WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod) +
|
---|
1796 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestStatus) +
|
---|
1797 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1798 | 'GROUP BY TestSets.enmStatus\n'
|
---|
1799 | 'ORDER BY TestSets.enmStatus\n');
|
---|
1800 | workerDoFetch(None, fIdIsName = True);
|
---|
1801 |
|
---|
1802 | # Scheduling groups (see getSchedGroups).
|
---|
1803 | oCrit = oFilter.aCriteria[TestResultFilter.kiSchedGroups];
|
---|
1804 | self._oDb.execute('SELECT SchedGroups.idSchedGroup, SchedGroups.sName, SchedGroupIDs.cTimes\n'
|
---|
1805 | 'FROM ( SELECT TestSets.idSchedGroup,\n'
|
---|
1806 | ' MAX(TestSets.tsCreated) AS tsNow,\n'
|
---|
1807 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
1808 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiSchedGroups) +
|
---|
1809 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1810 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1811 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiSchedGroups) +
|
---|
1812 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1813 | ' GROUP BY TestSets.idSchedGroup\n'
|
---|
1814 | ' ) AS SchedGroupIDs\n'
|
---|
1815 | ' INNER JOIN SchedGroups\n'
|
---|
1816 | ' ON SchedGroups.idSchedGroup = SchedGroupIDs.idSchedGroup\n'
|
---|
1817 | ' AND SchedGroups.tsExpire > SchedGroupIDs.tsNow\n'
|
---|
1818 | ' AND SchedGroups.tsEffective <= SchedGroupIDs.tsNow\n'
|
---|
1819 | 'ORDER BY SchedGroups.sName\n' );
|
---|
1820 | workerDoFetch(SchedGroupLogic);
|
---|
1821 |
|
---|
1822 | # Testboxes (see getTestBoxes).
|
---|
1823 | oCrit = oFilter.aCriteria[TestResultFilter.kiTestBoxes];
|
---|
1824 | self._oDb.execute('SELECT TestBoxesWithStrings.idTestBox,\n'
|
---|
1825 | ' TestBoxesWithStrings.sName,\n'
|
---|
1826 | ' TestBoxIDs.cTimes\n'
|
---|
1827 | 'FROM ( SELECT TestSets.idTestBox AS idTestBox,\n'
|
---|
1828 | ' MAX(TestSets.idGenTestBox) AS idGenTestBox,\n'
|
---|
1829 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
1830 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestBoxes) +
|
---|
1831 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1832 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1833 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestBoxes) +
|
---|
1834 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1835 | ' GROUP BY TestSets.idTestBox\n'
|
---|
1836 | ' ) AS TestBoxIDs\n'
|
---|
1837 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1838 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
|
---|
1839 | 'ORDER BY TestBoxesWithStrings.sName\n' );
|
---|
1840 | workerDoFetch(TestBoxLogic);
|
---|
1841 |
|
---|
1842 | # Testbox OSes and versions.
|
---|
1843 | oCrit = oFilter.aCriteria[TestResultFilter.kiOses];
|
---|
1844 | self._oDb.execute('SELECT TestBoxesWithStrings.idStrOs,\n'
|
---|
1845 | ' TestBoxesWithStrings.sOs,\n'
|
---|
1846 | ' TestBoxesWithStrings.idStrOsVersion,\n'
|
---|
1847 | ' TestBoxesWithStrings.sOsVersion,\n'
|
---|
1848 | ' SUM(TestBoxGenIDs.cTimes)\n'
|
---|
1849 | 'FROM ( SELECT TestSets.idGenTestBox,\n'
|
---|
1850 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
1851 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiOses) +
|
---|
1852 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1853 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1854 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiOses) +
|
---|
1855 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1856 | ' GROUP BY TestSets.idGenTestBox\n'
|
---|
1857 | ' ) AS TestBoxGenIDs\n'
|
---|
1858 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1859 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
|
---|
1860 | 'GROUP BY TestBoxesWithStrings.idStrOs,\n'
|
---|
1861 | ' TestBoxesWithStrings.sOs,\n'
|
---|
1862 | ' TestBoxesWithStrings.idStrOsVersion,\n'
|
---|
1863 | ' TestBoxesWithStrings.sOsVersion\n'
|
---|
1864 | 'ORDER BY TestBoxesWithStrings.sOs,\n'
|
---|
1865 | ' TestBoxesWithStrings.sOs = \'win\' AND TestBoxesWithStrings.sOsVersion = \'10\' DESC,\n'
|
---|
1866 | ' TestBoxesWithStrings.sOsVersion DESC\n'
|
---|
1867 | );
|
---|
1868 | workerDoFetchNested();
|
---|
1869 |
|
---|
1870 | # Testbox CPU(/OS) architectures.
|
---|
1871 | oCrit = oFilter.aCriteria[TestResultFilter.kiCpuArches];
|
---|
1872 | self._oDb.execute('SELECT TestBoxesWithStrings.idStrCpuArch,\n'
|
---|
1873 | ' TestBoxesWithStrings.sCpuArch,\n'
|
---|
1874 | ' SUM(TestBoxGenIDs.cTimes)\n'
|
---|
1875 | 'FROM ( SELECT TestSets.idGenTestBox,\n'
|
---|
1876 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
1877 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuArches) +
|
---|
1878 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1879 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1880 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuArches) +
|
---|
1881 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1882 | ' GROUP BY TestSets.idGenTestBox\n'
|
---|
1883 | ' ) AS TestBoxGenIDs\n'
|
---|
1884 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1885 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
|
---|
1886 | 'GROUP BY TestBoxesWithStrings.idStrCpuArch, TestBoxesWithStrings.sCpuArch\n'
|
---|
1887 | 'ORDER BY TestBoxesWithStrings.sCpuArch\n' );
|
---|
1888 | workerDoFetch(None, fIdIsName = True);
|
---|
1889 |
|
---|
1890 | # Testbox CPU revisions.
|
---|
1891 | oCrit = oFilter.aCriteria[TestResultFilter.kiCpuVendors];
|
---|
1892 | self._oDb.execute('SELECT TestBoxesWithStrings.idStrCpuVendor,\n'
|
---|
1893 | ' TestBoxesWithStrings.sCpuVendor,\n'
|
---|
1894 | ' TestBoxesWithStrings.lCpuRevision,\n'
|
---|
1895 | ' TestBoxesWithStrings.sCpuVendor,\n'
|
---|
1896 | ' SUM(TestBoxGenIDs.cTimes)\n'
|
---|
1897 | 'FROM ( SELECT TestSets.idGenTestBox,\n'
|
---|
1898 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
1899 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuVendors) +
|
---|
1900 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1901 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1902 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuVendors) +
|
---|
1903 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1904 | ' GROUP BY TestSets.idGenTestBox'
|
---|
1905 | ' ) AS TestBoxGenIDs\n'
|
---|
1906 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1907 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
|
---|
1908 | 'GROUP BY TestBoxesWithStrings.idStrCpuVendor,\n'
|
---|
1909 | ' TestBoxesWithStrings.sCpuVendor,\n'
|
---|
1910 | ' TestBoxesWithStrings.lCpuRevision,\n'
|
---|
1911 | ' TestBoxesWithStrings.sCpuVendor\n'
|
---|
1912 | 'ORDER BY TestBoxesWithStrings.sCpuVendor DESC,\n'
|
---|
1913 | ' TestBoxesWithStrings.sCpuVendor = \'GenuineIntel\'\n'
|
---|
1914 | ' AND (TestBoxesWithStrings.lCpuRevision >> 24) = 15,\n' # P4 at the bottom is a start...
|
---|
1915 | ' TestBoxesWithStrings.lCpuRevision DESC\n'
|
---|
1916 | );
|
---|
1917 | workerDoFetchNested();
|
---|
1918 | for oCur in oCrit.oSub.aoPossible:
|
---|
1919 | oCur.sDesc = TestBoxData.getPrettyCpuVersionEx(oCur.oValue, oCur.sDesc).replace('_', ' ');
|
---|
1920 |
|
---|
1921 | # Testbox CPU core/thread counts.
|
---|
1922 | oCrit = oFilter.aCriteria[TestResultFilter.kiCpuCounts];
|
---|
1923 | self._oDb.execute('SELECT TestBoxesWithStrings.cCpus,\n'
|
---|
1924 | ' CAST(TestBoxesWithStrings.cCpus AS TEXT),\n'
|
---|
1925 | ' SUM(TestBoxGenIDs.cTimes)\n'
|
---|
1926 | 'FROM ( SELECT TestSets.idGenTestBox,\n'
|
---|
1927 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
1928 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuCounts) +
|
---|
1929 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1930 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1931 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuCounts) +
|
---|
1932 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1933 | ' GROUP BY TestSets.idGenTestBox'
|
---|
1934 | ' ) AS TestBoxGenIDs\n'
|
---|
1935 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1936 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
|
---|
1937 | 'GROUP BY TestBoxesWithStrings.cCpus\n'
|
---|
1938 | 'ORDER BY TestBoxesWithStrings.cCpus\n' );
|
---|
1939 | workerDoFetch(None, fIdIsName = True);
|
---|
1940 |
|
---|
1941 | # Testbox memory.
|
---|
1942 | oCrit = oFilter.aCriteria[TestResultFilter.kiMemory];
|
---|
1943 | self._oDb.execute('SELECT TestBoxesWithStrings.cMbMemory / 1024,\n'
|
---|
1944 | ' NULL,\n'
|
---|
1945 | ' SUM(TestBoxGenIDs.cTimes)\n'
|
---|
1946 | 'FROM ( SELECT TestSets.idGenTestBox,\n'
|
---|
1947 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
1948 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiMemory) +
|
---|
1949 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1950 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1951 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiMemory) +
|
---|
1952 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1953 | ' GROUP BY TestSets.idGenTestBox'
|
---|
1954 | ' ) AS TestBoxGenIDs\n'
|
---|
1955 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1956 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
|
---|
1957 | 'GROUP BY TestBoxesWithStrings.cMbMemory / 1024\n'
|
---|
1958 | 'ORDER BY 1\n' );
|
---|
1959 | workerDoFetch(None, fIdIsName = True);
|
---|
1960 | for oCur in oCrit.aoPossible:
|
---|
1961 | oCur.sDesc = '%u GB' % (oCur.oValue,);
|
---|
1962 |
|
---|
1963 | # Testbox python versions .
|
---|
1964 | oCrit = oFilter.aCriteria[TestResultFilter.kiPythonVersions];
|
---|
1965 | self._oDb.execute('SELECT TestBoxesWithStrings.iPythonHexVersion,\n'
|
---|
1966 | ' NULL,\n'
|
---|
1967 | ' SUM(TestBoxGenIDs.cTimes)\n'
|
---|
1968 | 'FROM ( SELECT TestSets.idGenTestBox AS idGenTestBox,\n'
|
---|
1969 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
1970 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiPythonVersions) +
|
---|
1971 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
1972 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
1973 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiPythonVersions) +
|
---|
1974 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
1975 | ' GROUP BY TestSets.idGenTestBox\n'
|
---|
1976 | ' ) AS TestBoxGenIDs\n'
|
---|
1977 | ' LEFT OUTER JOIN TestBoxesWithStrings\n'
|
---|
1978 | ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
|
---|
1979 | 'GROUP BY TestBoxesWithStrings.iPythonHexVersion\n'
|
---|
1980 | 'ORDER BY TestBoxesWithStrings.iPythonHexVersion\n' );
|
---|
1981 | workerDoFetch(None, fIdIsName = True);
|
---|
1982 | for oCur in oCrit.aoPossible:
|
---|
1983 | oCur.sDesc = TestBoxData.formatPythonVersionEx(oCur.oValue); # pylint: disable=redefined-variable-type
|
---|
1984 |
|
---|
1985 | # Testcase with variation.
|
---|
1986 | oCrit = oFilter.aCriteria[TestResultFilter.kiTestCases];
|
---|
1987 | self._oDb.execute('SELECT TestCaseArgsIDs.idTestCase,\n'
|
---|
1988 | ' TestCases.sName,\n'
|
---|
1989 | ' TestCaseArgsIDs.idTestCaseArgs,\n'
|
---|
1990 | ' CASE WHEN TestCaseArgs.sSubName IS NULL OR TestCaseArgs.sSubName = \'\' THEN\n'
|
---|
1991 | ' CONCAT(\'/ #\', TestCaseArgs.idTestCaseArgs)\n'
|
---|
1992 | ' ELSE\n'
|
---|
1993 | ' TestCaseArgs.sSubName\n'
|
---|
1994 | ' END,'
|
---|
1995 | ' TestCaseArgsIDs.cTimes\n'
|
---|
1996 | 'FROM ( SELECT TestSets.idTestCase AS idTestCase,\n'
|
---|
1997 | ' TestSets.idTestCaseArgs AS idTestCaseArgs,\n'
|
---|
1998 | ' MAX(TestSets.idGenTestCase) AS idGenTestCase,\n'
|
---|
1999 | ' MAX(TestSets.idGenTestCaseArgs) AS idGenTestCaseArgs,\n'
|
---|
2000 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
2001 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestCases) +
|
---|
2002 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
2003 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
2004 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestCases) +
|
---|
2005 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
2006 | ' GROUP BY TestSets.idTestCase, TestSets.idTestCaseArgs\n'
|
---|
2007 | ' ) AS TestCaseArgsIDs\n'
|
---|
2008 | ' LEFT OUTER JOIN TestCases ON TestCases.idGenTestCase = TestCaseArgsIDs.idGenTestCase\n'
|
---|
2009 | ' LEFT OUTER JOIN TestCaseArgs\n'
|
---|
2010 | ' ON TestCaseArgs.idGenTestCaseArgs = TestCaseArgsIDs.idGenTestCaseArgs\n'
|
---|
2011 | 'ORDER BY TestCases.sName, 4\n' );
|
---|
2012 | workerDoFetchNested();
|
---|
2013 |
|
---|
2014 | # Build revisions.
|
---|
2015 | oCrit = oFilter.aCriteria[TestResultFilter.kiRevisions];
|
---|
2016 | self._oDb.execute('SELECT Builds.iRevision, CONCAT(\'r\', Builds.iRevision), SUM(BuildIDs.cTimes)\n'
|
---|
2017 | 'FROM ( SELECT TestSets.idBuild AS idBuild,\n'
|
---|
2018 | ' MAX(TestSets.tsCreated) AS tsNow,\n'
|
---|
2019 | ' COUNT(TestSets.idBuild) AS cTimes\n'
|
---|
2020 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiRevisions) +
|
---|
2021 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
2022 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
2023 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiRevisions) +
|
---|
2024 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
2025 | ' GROUP BY TestSets.idBuild\n'
|
---|
2026 | ' ) AS BuildIDs\n'
|
---|
2027 | ' INNER JOIN Builds\n'
|
---|
2028 | ' ON Builds.idBuild = BuildIDs.idBuild\n'
|
---|
2029 | ' AND Builds.tsExpire > BuildIDs.tsNow\n'
|
---|
2030 | ' AND Builds.tsEffective <= BuildIDs.tsNow\n'
|
---|
2031 | 'GROUP BY Builds.iRevision\n'
|
---|
2032 | 'ORDER BY Builds.iRevision DESC\n' );
|
---|
2033 | workerDoFetch(None, fIdIsName = True);
|
---|
2034 |
|
---|
2035 | # Build branches.
|
---|
2036 | oCrit = oFilter.aCriteria[TestResultFilter.kiBranches];
|
---|
2037 | self._oDb.execute('SELECT BuildCategories.sBranch, BuildCategories.sBranch, SUM(BuildCategoryIDs.cTimes)\n'
|
---|
2038 | 'FROM ( SELECT TestSets.idBuildCategory,\n'
|
---|
2039 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
2040 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiBranches) +
|
---|
2041 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
2042 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
2043 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiBranches) +
|
---|
2044 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
2045 | ' GROUP BY TestSets.idBuildCategory\n'
|
---|
2046 | ' ) AS BuildCategoryIDs\n'
|
---|
2047 | ' INNER JOIN BuildCategories\n'
|
---|
2048 | ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n'
|
---|
2049 | 'GROUP BY BuildCategories.sBranch\n'
|
---|
2050 | 'ORDER BY BuildCategories.sBranch DESC\n' );
|
---|
2051 | workerDoFetch(None, fIdIsName = True);
|
---|
2052 |
|
---|
2053 | # Build types.
|
---|
2054 | oCrit = oFilter.aCriteria[TestResultFilter.kiBuildTypes];
|
---|
2055 | self._oDb.execute('SELECT BuildCategories.sType, BuildCategories.sType, SUM(BuildCategoryIDs.cTimes)\n'
|
---|
2056 | 'FROM ( SELECT TestSets.idBuildCategory,\n'
|
---|
2057 | ' COUNT(TestSets.idTestSet) AS cTimes\n'
|
---|
2058 | ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiBuildTypes) +
|
---|
2059 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
2060 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
2061 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiBuildTypes) +
|
---|
2062 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
2063 | ' GROUP BY TestSets.idBuildCategory\n'
|
---|
2064 | ' ) AS BuildCategoryIDs\n'
|
---|
2065 | ' INNER JOIN BuildCategories\n'
|
---|
2066 | ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n'
|
---|
2067 | 'GROUP BY BuildCategories.sType\n'
|
---|
2068 | 'ORDER BY BuildCategories.sType DESC\n' );
|
---|
2069 | workerDoFetch(None, fIdIsName = True);
|
---|
2070 |
|
---|
2071 | # Failure reasons.
|
---|
2072 | oCrit = oFilter.aCriteria[TestResultFilter.kiFailReasons];
|
---|
2073 | self._oDb.execute('SELECT FailureReasons.idFailureReason, FailureReasons.sShort, FailureReasonIDs.cTimes\n'
|
---|
2074 | 'FROM ( SELECT TestResultFailures.idFailureReason,\n'
|
---|
2075 | ' COUNT(TestSets.idTestSet) as cTimes\n'
|
---|
2076 | ' FROM TestSets\n'
|
---|
2077 | ' LEFT OUTER JOIN TestResultFailures\n'
|
---|
2078 | ' ON TestResultFailures.idTestSet = TestSets.idTestSet\n'
|
---|
2079 | ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' +
|
---|
2080 | oFilter.getTableJoins(iOmit = TestResultFilter.kiFailReasons) +
|
---|
2081 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
2082 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
2083 | ' AND TestSets.enmStatus >= \'failure\'::TestStatus_T\n' +
|
---|
2084 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiFailReasons) +
|
---|
2085 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
2086 | ' GROUP BY TestResultFailures.idFailureReason\n'
|
---|
2087 | ' ) AS FailureReasonIDs\n'
|
---|
2088 | ' LEFT OUTER JOIN FailureReasons\n'
|
---|
2089 | ' ON FailureReasons.idFailureReason = FailureReasonIDs.idFailureReason\n'
|
---|
2090 | ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP\n'
|
---|
2091 | 'ORDER BY FailureReasons.idFailureReason IS NULL DESC,\n'
|
---|
2092 | ' FailureReasons.sShort\n' );
|
---|
2093 | workerDoFetch(FailureReasonLogic, 'sShort', sNullDesc = 'Not given');
|
---|
2094 |
|
---|
2095 | # Error counts.
|
---|
2096 | oCrit = oFilter.aCriteria[TestResultFilter.kiErrorCounts];
|
---|
2097 | self._oDb.execute('SELECT TestResults.cErrors, CAST(TestResults.cErrors AS TEXT), COUNT(TestResults.idTestResult)\n'
|
---|
2098 | 'FROM ( SELECT TestSets.idTestResult AS idTestResult\n'
|
---|
2099 | ' FROM TestSets\n' +
|
---|
2100 | oFilter.getTableJoins(iOmit = TestResultFilter.kiFailReasons) +
|
---|
2101 | ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
|
---|
2102 | ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
|
---|
2103 | oFilter.getWhereConditions(iOmit = TestResultFilter.kiFailReasons) +
|
---|
2104 | oReportModel.getExtraSubjectWhereExpr() +
|
---|
2105 | ' ) AS TestSetIDs\n'
|
---|
2106 | ' INNER JOIN TestResults\n'
|
---|
2107 | ' ON TestResults.idTestResult = TestSetIDs.idTestResult\n'
|
---|
2108 | 'GROUP BY TestResults.cErrors\n'
|
---|
2109 | 'ORDER BY TestResults.cErrors\n');
|
---|
2110 |
|
---|
2111 | workerDoFetch(None, fIdIsName = True);
|
---|
2112 |
|
---|
2113 | return oFilter;
|
---|
2114 |
|
---|
2115 |
|
---|
2116 | #
|
---|
2117 | # Details view and interface.
|
---|
2118 | #
|
---|
2119 |
|
---|
2120 | def fetchResultTree(self, idTestSet, cMaxDepth = None):
|
---|
2121 | """
|
---|
2122 | Fetches the result tree for the given test set.
|
---|
2123 |
|
---|
2124 | Returns a tree of TestResultDataEx nodes.
|
---|
2125 | Raises exception on invalid input and database issues.
|
---|
2126 | """
|
---|
2127 | # Depth first, i.e. just like the XML added them.
|
---|
2128 | ## @todo this still isn't performing extremely well, consider optimizations.
|
---|
2129 | sQuery = self._oDb.formatBindArgs(
|
---|
2130 | 'SELECT TestResults.*,\n'
|
---|
2131 | ' TestResultStrTab.sValue,\n'
|
---|
2132 | ' EXISTS ( SELECT idTestResultValue\n'
|
---|
2133 | ' FROM TestResultValues\n'
|
---|
2134 | ' WHERE TestResultValues.idTestResult = TestResults.idTestResult ) AS fHasValues,\n'
|
---|
2135 | ' EXISTS ( SELECT idTestResultMsg\n'
|
---|
2136 | ' FROM TestResultMsgs\n'
|
---|
2137 | ' WHERE TestResultMsgs.idTestResult = TestResults.idTestResult ) AS fHasMsgs,\n'
|
---|
2138 | ' EXISTS ( SELECT idTestResultFile\n'
|
---|
2139 | ' FROM TestResultFiles\n'
|
---|
2140 | ' WHERE TestResultFiles.idTestResult = TestResults.idTestResult ) AS fHasFiles,\n'
|
---|
2141 | ' EXISTS ( SELECT idTestResult\n'
|
---|
2142 | ' FROM TestResultFailures\n'
|
---|
2143 | ' WHERE TestResultFailures.idTestResult = TestResults.idTestResult ) AS fHasReasons\n'
|
---|
2144 | 'FROM TestResults, TestResultStrTab\n'
|
---|
2145 | 'WHERE TestResults.idTestSet = %s\n'
|
---|
2146 | ' AND TestResults.idStrName = TestResultStrTab.idStr\n'
|
---|
2147 | , ( idTestSet, ));
|
---|
2148 | if cMaxDepth is not None:
|
---|
2149 | sQuery += self._oDb.formatBindArgs(' AND TestResults.iNestingDepth <= %s\n', (cMaxDepth,));
|
---|
2150 | sQuery += 'ORDER BY idTestResult ASC\n'
|
---|
2151 |
|
---|
2152 | self._oDb.execute(sQuery);
|
---|
2153 | cRows = self._oDb.getRowCount();
|
---|
2154 | if cRows > 65536:
|
---|
2155 | raise TMTooManyRows('Too many rows returned for idTestSet=%d: %d' % (idTestSet, cRows,));
|
---|
2156 |
|
---|
2157 | aaoRows = self._oDb.fetchAll();
|
---|
2158 | if not aaoRows:
|
---|
2159 | raise TMRowNotFound('No test results for idTestSet=%d.' % (idTestSet,));
|
---|
2160 |
|
---|
2161 | # Set up the root node first.
|
---|
2162 | aoRow = aaoRows[0];
|
---|
2163 | oRoot = TestResultDataEx().initFromDbRow(aoRow);
|
---|
2164 | if oRoot.idTestResultParent is not None:
|
---|
2165 | raise self._oDb.integrityException('The root TestResult (#%s) has a parent (#%s)!'
|
---|
2166 | % (oRoot.idTestResult, oRoot.idTestResultParent));
|
---|
2167 | self._fetchResultTreeNodeExtras(oRoot, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]);
|
---|
2168 |
|
---|
2169 | # The children (if any).
|
---|
2170 | dLookup = { oRoot.idTestResult: oRoot };
|
---|
2171 | oParent = oRoot;
|
---|
2172 | for iRow in range(1, len(aaoRows)):
|
---|
2173 | aoRow = aaoRows[iRow];
|
---|
2174 | oCur = TestResultDataEx().initFromDbRow(aoRow);
|
---|
2175 | self._fetchResultTreeNodeExtras(oCur, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]);
|
---|
2176 |
|
---|
2177 | # Figure out and vet the parent.
|
---|
2178 | if oParent.idTestResult != oCur.idTestResultParent:
|
---|
2179 | oParent = dLookup.get(oCur.idTestResultParent, None);
|
---|
2180 | if oParent is None:
|
---|
2181 | raise self._oDb.integrityException('TestResult #%d is orphaned from its parent #%s.'
|
---|
2182 | % (oCur.idTestResult, oCur.idTestResultParent,));
|
---|
2183 | if oParent.iNestingDepth + 1 != oCur.iNestingDepth:
|
---|
2184 | raise self._oDb.integrityException('TestResult #%d has incorrect nesting depth (%d instead of %d)'
|
---|
2185 | % (oCur.idTestResult, oCur.iNestingDepth, oParent.iNestingDepth + 1,));
|
---|
2186 |
|
---|
2187 | # Link it up.
|
---|
2188 | oCur.oParent = oParent;
|
---|
2189 | oParent.aoChildren.append(oCur);
|
---|
2190 | dLookup[oCur.idTestResult] = oCur;
|
---|
2191 |
|
---|
2192 | return (oRoot, dLookup);
|
---|
2193 |
|
---|
2194 | def _fetchResultTreeNodeExtras(self, oCurNode, fHasValues, fHasMsgs, fHasFiles, fHasReasons):
|
---|
2195 | """
|
---|
2196 | fetchResultTree worker that fetches values, message and files for the
|
---|
2197 | specified node.
|
---|
2198 | """
|
---|
2199 | assert(oCurNode.aoValues == []);
|
---|
2200 | assert(oCurNode.aoMsgs == []);
|
---|
2201 | assert(oCurNode.aoFiles == []);
|
---|
2202 | assert(oCurNode.oReason is None);
|
---|
2203 |
|
---|
2204 | if fHasValues:
|
---|
2205 | self._oDb.execute('SELECT TestResultValues.*,\n'
|
---|
2206 | ' TestResultStrTab.sValue\n'
|
---|
2207 | 'FROM TestResultValues, TestResultStrTab\n'
|
---|
2208 | 'WHERE TestResultValues.idTestResult = %s\n'
|
---|
2209 | ' AND TestResultValues.idStrName = TestResultStrTab.idStr\n'
|
---|
2210 | 'ORDER BY idTestResultValue ASC\n'
|
---|
2211 | , ( oCurNode.idTestResult, ));
|
---|
2212 | for aoRow in self._oDb.fetchAll():
|
---|
2213 | oCurNode.aoValues.append(TestResultValueDataEx().initFromDbRow(aoRow));
|
---|
2214 |
|
---|
2215 | if fHasMsgs:
|
---|
2216 | self._oDb.execute('SELECT TestResultMsgs.*,\n'
|
---|
2217 | ' TestResultStrTab.sValue\n'
|
---|
2218 | 'FROM TestResultMsgs, TestResultStrTab\n'
|
---|
2219 | 'WHERE TestResultMsgs.idTestResult = %s\n'
|
---|
2220 | ' AND TestResultMsgs.idStrMsg = TestResultStrTab.idStr\n'
|
---|
2221 | 'ORDER BY idTestResultMsg ASC\n'
|
---|
2222 | , ( oCurNode.idTestResult, ));
|
---|
2223 | for aoRow in self._oDb.fetchAll():
|
---|
2224 | oCurNode.aoMsgs.append(TestResultMsgDataEx().initFromDbRow(aoRow));
|
---|
2225 |
|
---|
2226 | if fHasFiles:
|
---|
2227 | self._oDb.execute('SELECT TestResultFiles.*,\n'
|
---|
2228 | ' StrTabFile.sValue AS sFile,\n'
|
---|
2229 | ' StrTabDesc.sValue AS sDescription,\n'
|
---|
2230 | ' StrTabKind.sValue AS sKind,\n'
|
---|
2231 | ' StrTabMime.sValue AS sMime\n'
|
---|
2232 | 'FROM TestResultFiles,\n'
|
---|
2233 | ' TestResultStrTab AS StrTabFile,\n'
|
---|
2234 | ' TestResultStrTab AS StrTabDesc,\n'
|
---|
2235 | ' TestResultStrTab AS StrTabKind,\n'
|
---|
2236 | ' TestResultStrTab AS StrTabMime\n'
|
---|
2237 | 'WHERE TestResultFiles.idTestResult = %s\n'
|
---|
2238 | ' AND TestResultFiles.idStrFile = StrTabFile.idStr\n'
|
---|
2239 | ' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n'
|
---|
2240 | ' AND TestResultFiles.idStrKind = StrTabKind.idStr\n'
|
---|
2241 | ' AND TestResultFiles.idStrMime = StrTabMime.idStr\n'
|
---|
2242 | 'ORDER BY idTestResultFile ASC\n'
|
---|
2243 | , ( oCurNode.idTestResult, ));
|
---|
2244 | for aoRow in self._oDb.fetchAll():
|
---|
2245 | oCurNode.aoFiles.append(TestResultFileDataEx().initFromDbRow(aoRow));
|
---|
2246 |
|
---|
2247 | if fHasReasons:
|
---|
2248 | if self.oFailureReasonLogic is None:
|
---|
2249 | self.oFailureReasonLogic = FailureReasonLogic(self._oDb);
|
---|
2250 | if self.oUserAccountLogic is None:
|
---|
2251 | self.oUserAccountLogic = UserAccountLogic(self._oDb);
|
---|
2252 | self._oDb.execute('SELECT *\n'
|
---|
2253 | 'FROM TestResultFailures\n'
|
---|
2254 | 'WHERE idTestResult = %s\n'
|
---|
2255 | ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
|
---|
2256 | , ( oCurNode.idTestResult, ));
|
---|
2257 | if self._oDb.getRowCount() > 0:
|
---|
2258 | oCurNode.oReason = TestResultFailureDataEx().initFromDbRowEx(self._oDb.fetchOne(), self.oFailureReasonLogic,
|
---|
2259 | self.oUserAccountLogic);
|
---|
2260 |
|
---|
2261 | return True;
|
---|
2262 |
|
---|
2263 |
|
---|
2264 |
|
---|
2265 | #
|
---|
2266 | # TestBoxController interface(s).
|
---|
2267 | #
|
---|
2268 |
|
---|
2269 | def _inhumeTestResults(self, aoStack, idTestSet, sError):
|
---|
2270 | """
|
---|
2271 | The test produces too much output, kill and bury it.
|
---|
2272 |
|
---|
2273 | Note! We leave the test set open, only the test result records are
|
---|
2274 | completed. Thus, _getResultStack will return an empty stack and
|
---|
2275 | cause XML processing to fail immediately, while we can still
|
---|
2276 | record when it actually completed in the test set the normal way.
|
---|
2277 | """
|
---|
2278 | self._oDb.dprint('** _inhumeTestResults: idTestSet=%d\n%s' % (idTestSet, self._stringifyStack(aoStack),));
|
---|
2279 |
|
---|
2280 | #
|
---|
2281 | # First add a message.
|
---|
2282 | #
|
---|
2283 | self._newFailureDetails(aoStack[0].idTestResult, idTestSet, sError, None);
|
---|
2284 |
|
---|
2285 | #
|
---|
2286 | # The complete all open test results.
|
---|
2287 | #
|
---|
2288 | for oTestResult in aoStack:
|
---|
2289 | oTestResult.cErrors += 1;
|
---|
2290 | self._completeTestResults(oTestResult, None, TestResultData.ksTestStatus_Failure, oTestResult.cErrors);
|
---|
2291 |
|
---|
2292 | # A bit of paranoia.
|
---|
2293 | self._oDb.execute('UPDATE TestResults\n'
|
---|
2294 | 'SET cErrors = cErrors + 1,\n'
|
---|
2295 | ' enmStatus = \'failure\'::TestStatus_T,\n'
|
---|
2296 | ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
|
---|
2297 | 'WHERE idTestSet = %s\n'
|
---|
2298 | ' AND enmStatus = \'running\'::TestStatus_T\n'
|
---|
2299 | , ( idTestSet, ));
|
---|
2300 | self._oDb.commit();
|
---|
2301 |
|
---|
2302 | return None;
|
---|
2303 |
|
---|
2304 | def strTabString(self, sString, fCommit = False):
|
---|
2305 | """
|
---|
2306 | Gets the string table id for the given string, adding it if new.
|
---|
2307 |
|
---|
2308 | Note! A copy of this code is also in TestSetLogic.
|
---|
2309 | """
|
---|
2310 | ## @todo move this and make a stored procedure for it.
|
---|
2311 | self._oDb.execute('SELECT idStr\n'
|
---|
2312 | 'FROM TestResultStrTab\n'
|
---|
2313 | 'WHERE sValue = %s'
|
---|
2314 | , (sString,));
|
---|
2315 | if self._oDb.getRowCount() == 0:
|
---|
2316 | self._oDb.execute('INSERT INTO TestResultStrTab (sValue)\n'
|
---|
2317 | 'VALUES (%s)\n'
|
---|
2318 | 'RETURNING idStr\n'
|
---|
2319 | , (sString,));
|
---|
2320 | if fCommit:
|
---|
2321 | self._oDb.commit();
|
---|
2322 | return self._oDb.fetchOne()[0];
|
---|
2323 |
|
---|
2324 | @staticmethod
|
---|
2325 | def _stringifyStack(aoStack):
|
---|
2326 | """Returns a string rep of the stack."""
|
---|
2327 | sRet = '';
|
---|
2328 | for i, _ in enumerate(aoStack):
|
---|
2329 | sRet += 'aoStack[%d]=%s\n' % (i, aoStack[i]);
|
---|
2330 | return sRet;
|
---|
2331 |
|
---|
2332 | def _getResultStack(self, idTestSet):
|
---|
2333 | """
|
---|
2334 | Gets the current stack of result sets.
|
---|
2335 | """
|
---|
2336 | self._oDb.execute('SELECT *\n'
|
---|
2337 | 'FROM TestResults\n'
|
---|
2338 | 'WHERE idTestSet = %s\n'
|
---|
2339 | ' AND enmStatus = \'running\'::TestStatus_T\n'
|
---|
2340 | 'ORDER BY idTestResult DESC'
|
---|
2341 | , ( idTestSet, ));
|
---|
2342 | aoStack = [];
|
---|
2343 | for aoRow in self._oDb.fetchAll():
|
---|
2344 | aoStack.append(TestResultData().initFromDbRow(aoRow));
|
---|
2345 |
|
---|
2346 | for i, _ in enumerate(aoStack):
|
---|
2347 | assert aoStack[i].iNestingDepth == len(aoStack) - i - 1, self._stringifyStack(aoStack);
|
---|
2348 |
|
---|
2349 | return aoStack;
|
---|
2350 |
|
---|
2351 | def _newTestResult(self, idTestResultParent, idTestSet, iNestingDepth, tsCreated, sName, dCounts, fCommit = False):
|
---|
2352 | """
|
---|
2353 | Creates a new test result.
|
---|
2354 | Returns the TestResultData object for the new record.
|
---|
2355 | May raise exception on database error.
|
---|
2356 | """
|
---|
2357 | assert idTestResultParent is not None;
|
---|
2358 | assert idTestResultParent > 1;
|
---|
2359 |
|
---|
2360 | #
|
---|
2361 | # This isn't necessarily very efficient, but it's necessary to prevent
|
---|
2362 | # a wild test or testbox from filling up the database.
|
---|
2363 | #
|
---|
2364 | sCountName = 'cTestResults';
|
---|
2365 | if sCountName not in dCounts:
|
---|
2366 | self._oDb.execute('SELECT COUNT(idTestResult)\n'
|
---|
2367 | 'FROM TestResults\n'
|
---|
2368 | 'WHERE idTestSet = %s\n'
|
---|
2369 | , ( idTestSet,));
|
---|
2370 | dCounts[sCountName] = self._oDb.fetchOne()[0];
|
---|
2371 | dCounts[sCountName] += 1;
|
---|
2372 | if dCounts[sCountName] > config.g_kcMaxTestResultsPerTS:
|
---|
2373 | raise TestResultHangingOffence('Too many sub-tests in total!');
|
---|
2374 |
|
---|
2375 | sCountName = 'cTestResultsIn%d' % (idTestResultParent,);
|
---|
2376 | if sCountName not in dCounts:
|
---|
2377 | self._oDb.execute('SELECT COUNT(idTestResult)\n'
|
---|
2378 | 'FROM TestResults\n'
|
---|
2379 | 'WHERE idTestResultParent = %s\n'
|
---|
2380 | , ( idTestResultParent,));
|
---|
2381 | dCounts[sCountName] = self._oDb.fetchOne()[0];
|
---|
2382 | dCounts[sCountName] += 1;
|
---|
2383 | if dCounts[sCountName] > config.g_kcMaxTestResultsPerTR:
|
---|
2384 | raise TestResultHangingOffence('Too many immediate sub-tests!');
|
---|
2385 |
|
---|
2386 | # This is also a hanging offence.
|
---|
2387 | if iNestingDepth > config.g_kcMaxTestResultDepth:
|
---|
2388 | raise TestResultHangingOffence('To deep sub-test nesting!');
|
---|
2389 |
|
---|
2390 | # Ditto.
|
---|
2391 | if len(sName) > config.g_kcchMaxTestResultName:
|
---|
2392 | raise TestResultHangingOffence('Test name is too long: %d chars - "%s"' % (len(sName), sName));
|
---|
2393 |
|
---|
2394 | #
|
---|
2395 | # Within bounds, do the job.
|
---|
2396 | #
|
---|
2397 | idStrName = self.strTabString(sName, fCommit);
|
---|
2398 | self._oDb.execute('INSERT INTO TestResults (\n'
|
---|
2399 | ' idTestResultParent,\n'
|
---|
2400 | ' idTestSet,\n'
|
---|
2401 | ' tsCreated,\n'
|
---|
2402 | ' idStrName,\n'
|
---|
2403 | ' iNestingDepth )\n'
|
---|
2404 | 'VALUES (%s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
|
---|
2405 | 'RETURNING *\n'
|
---|
2406 | , ( idTestResultParent, idTestSet, tsCreated, idStrName, iNestingDepth) )
|
---|
2407 | oData = TestResultData().initFromDbRow(self._oDb.fetchOne());
|
---|
2408 |
|
---|
2409 | self._oDb.maybeCommit(fCommit);
|
---|
2410 | return oData;
|
---|
2411 |
|
---|
2412 | def _newTestValue(self, idTestResult, idTestSet, sName, lValue, sUnit, dCounts, tsCreated = None, fCommit = False):
|
---|
2413 | """
|
---|
2414 | Creates a test value.
|
---|
2415 | May raise exception on database error.
|
---|
2416 | """
|
---|
2417 |
|
---|
2418 | #
|
---|
2419 | # Bounds checking.
|
---|
2420 | #
|
---|
2421 | sCountName = 'cTestValues';
|
---|
2422 | if sCountName not in dCounts:
|
---|
2423 | self._oDb.execute('SELECT COUNT(idTestResultValue)\n'
|
---|
2424 | 'FROM TestResultValues, TestResults\n'
|
---|
2425 | 'WHERE TestResultValues.idTestResult = TestResults.idTestResult\n'
|
---|
2426 | ' AND TestResults.idTestSet = %s\n'
|
---|
2427 | , ( idTestSet,));
|
---|
2428 | dCounts[sCountName] = self._oDb.fetchOne()[0];
|
---|
2429 | dCounts[sCountName] += 1;
|
---|
2430 | if dCounts[sCountName] > config.g_kcMaxTestValuesPerTS:
|
---|
2431 | raise TestResultHangingOffence('Too many values in total!');
|
---|
2432 |
|
---|
2433 | sCountName = 'cTestValuesIn%d' % (idTestResult,);
|
---|
2434 | if sCountName not in dCounts:
|
---|
2435 | self._oDb.execute('SELECT COUNT(idTestResultValue)\n'
|
---|
2436 | 'FROM TestResultValues\n'
|
---|
2437 | 'WHERE idTestResult = %s\n'
|
---|
2438 | , ( idTestResult,));
|
---|
2439 | dCounts[sCountName] = self._oDb.fetchOne()[0];
|
---|
2440 | dCounts[sCountName] += 1;
|
---|
2441 | if dCounts[sCountName] > config.g_kcMaxTestValuesPerTR:
|
---|
2442 | raise TestResultHangingOffence('Too many immediate values for one test result!');
|
---|
2443 |
|
---|
2444 | if len(sName) > config.g_kcchMaxTestValueName:
|
---|
2445 | raise TestResultHangingOffence('Value name is too long: %d chars - "%s"' % (len(sName), sName));
|
---|
2446 |
|
---|
2447 | #
|
---|
2448 | # Do the job.
|
---|
2449 | #
|
---|
2450 | iUnit = constants.valueunit.g_kdNameToConst.get(sUnit, constants.valueunit.NONE);
|
---|
2451 |
|
---|
2452 | idStrName = self.strTabString(sName, fCommit);
|
---|
2453 | if tsCreated is None:
|
---|
2454 | self._oDb.execute('INSERT INTO TestResultValues (\n'
|
---|
2455 | ' idTestResult,\n'
|
---|
2456 | ' idTestSet,\n'
|
---|
2457 | ' idStrName,\n'
|
---|
2458 | ' lValue,\n'
|
---|
2459 | ' iUnit)\n'
|
---|
2460 | 'VALUES ( %s, %s, %s, %s, %s )\n'
|
---|
2461 | , ( idTestResult, idTestSet, idStrName, lValue, iUnit,) );
|
---|
2462 | else:
|
---|
2463 | self._oDb.execute('INSERT INTO TestResultValues (\n'
|
---|
2464 | ' idTestResult,\n'
|
---|
2465 | ' idTestSet,\n'
|
---|
2466 | ' tsCreated,\n'
|
---|
2467 | ' idStrName,\n'
|
---|
2468 | ' lValue,\n'
|
---|
2469 | ' iUnit)\n'
|
---|
2470 | 'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s, %s )\n'
|
---|
2471 | , ( idTestResult, idTestSet, tsCreated, idStrName, lValue, iUnit,) );
|
---|
2472 | self._oDb.maybeCommit(fCommit);
|
---|
2473 | return True;
|
---|
2474 |
|
---|
2475 | def _newFailureDetails(self, idTestResult, idTestSet, sText, dCounts, tsCreated = None, fCommit = False):
|
---|
2476 | """
|
---|
2477 | Creates a record detailing cause of failure.
|
---|
2478 | May raise exception on database error.
|
---|
2479 | """
|
---|
2480 |
|
---|
2481 | #
|
---|
2482 | # Overflow protection.
|
---|
2483 | #
|
---|
2484 | if dCounts is not None:
|
---|
2485 | sCountName = 'cTestMsgsIn%d' % (idTestResult,);
|
---|
2486 | if sCountName not in dCounts:
|
---|
2487 | self._oDb.execute('SELECT COUNT(idTestResultMsg)\n'
|
---|
2488 | 'FROM TestResultMsgs\n'
|
---|
2489 | 'WHERE idTestResult = %s\n'
|
---|
2490 | , ( idTestResult,));
|
---|
2491 | dCounts[sCountName] = self._oDb.fetchOne()[0];
|
---|
2492 | dCounts[sCountName] += 1;
|
---|
2493 | if dCounts[sCountName] > config.g_kcMaxTestMsgsPerTR:
|
---|
2494 | raise TestResultHangingOffence('Too many messages under for one test result!');
|
---|
2495 |
|
---|
2496 | if len(sText) > config.g_kcchMaxTestMsg:
|
---|
2497 | raise TestResultHangingOffence('Failure details message is too long: %d chars - "%s"' % (len(sText), sText));
|
---|
2498 |
|
---|
2499 | #
|
---|
2500 | # Do the job.
|
---|
2501 | #
|
---|
2502 | idStrMsg = self.strTabString(sText, fCommit);
|
---|
2503 | if tsCreated is None:
|
---|
2504 | self._oDb.execute('INSERT INTO TestResultMsgs (\n'
|
---|
2505 | ' idTestResult,\n'
|
---|
2506 | ' idTestSet,\n'
|
---|
2507 | ' idStrMsg,\n'
|
---|
2508 | ' enmLevel)\n'
|
---|
2509 | 'VALUES ( %s, %s, %s, %s)\n'
|
---|
2510 | , ( idTestResult, idTestSet, idStrMsg, 'failure',) );
|
---|
2511 | else:
|
---|
2512 | self._oDb.execute('INSERT INTO TestResultMsgs (\n'
|
---|
2513 | ' idTestResult,\n'
|
---|
2514 | ' idTestSet,\n'
|
---|
2515 | ' tsCreated,\n'
|
---|
2516 | ' idStrMsg,\n'
|
---|
2517 | ' enmLevel)\n'
|
---|
2518 | 'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
|
---|
2519 | , ( idTestResult, idTestSet, tsCreated, idStrMsg, 'failure',) );
|
---|
2520 |
|
---|
2521 | self._oDb.maybeCommit(fCommit);
|
---|
2522 | return True;
|
---|
2523 |
|
---|
2524 |
|
---|
2525 | def _completeTestResults(self, oTestResult, tsDone, enmStatus, cErrors = 0, fCommit = False):
|
---|
2526 | """
|
---|
2527 | Completes a test result. Updates the oTestResult object.
|
---|
2528 | May raise exception on database error.
|
---|
2529 | """
|
---|
2530 | self._oDb.dprint('** _completeTestResults: cErrors=%s tsDone=%s enmStatus=%s oTestResults=\n%s'
|
---|
2531 | % (cErrors, tsDone, enmStatus, oTestResult,));
|
---|
2532 |
|
---|
2533 | #
|
---|
2534 | # Sanity check: No open sub tests (aoStack should make sure about this!).
|
---|
2535 | #
|
---|
2536 | self._oDb.execute('SELECT COUNT(idTestResult)\n'
|
---|
2537 | 'FROM TestResults\n'
|
---|
2538 | 'WHERE idTestResultParent = %s\n'
|
---|
2539 | ' AND enmStatus = %s\n'
|
---|
2540 | , ( oTestResult.idTestResult, TestResultData.ksTestStatus_Running,));
|
---|
2541 | cOpenSubTest = self._oDb.fetchOne()[0];
|
---|
2542 | assert cOpenSubTest == 0, 'cOpenSubTest=%d - %s' % (cOpenSubTest, oTestResult,);
|
---|
2543 | assert oTestResult.enmStatus == TestResultData.ksTestStatus_Running;
|
---|
2544 |
|
---|
2545 | #
|
---|
2546 | # Make sure the reporter isn't lying about successes or error counts.
|
---|
2547 | #
|
---|
2548 | self._oDb.execute('SELECT COALESCE(SUM(cErrors), 0)\n'
|
---|
2549 | 'FROM TestResults\n'
|
---|
2550 | 'WHERE idTestResultParent = %s\n'
|
---|
2551 | , ( oTestResult.idTestResult, ));
|
---|
2552 | cMinErrors = self._oDb.fetchOne()[0] + oTestResult.cErrors;
|
---|
2553 | cErrors = max(cErrors, cMinErrors);
|
---|
2554 | if cErrors > 0 and enmStatus == TestResultData.ksTestStatus_Success:
|
---|
2555 | enmStatus = TestResultData.ksTestStatus_Failure
|
---|
2556 |
|
---|
2557 | #
|
---|
2558 | # Do the update.
|
---|
2559 | #
|
---|
2560 | if tsDone is None:
|
---|
2561 | self._oDb.execute('UPDATE TestResults\n'
|
---|
2562 | 'SET cErrors = %s,\n'
|
---|
2563 | ' enmStatus = %s,\n'
|
---|
2564 | ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
|
---|
2565 | 'WHERE idTestResult = %s\n'
|
---|
2566 | 'RETURNING tsElapsed'
|
---|
2567 | , ( cErrors, enmStatus, oTestResult.idTestResult,) );
|
---|
2568 | else:
|
---|
2569 | self._oDb.execute('UPDATE TestResults\n'
|
---|
2570 | 'SET cErrors = %s,\n'
|
---|
2571 | ' enmStatus = %s,\n'
|
---|
2572 | ' tsElapsed = TIMESTAMP WITH TIME ZONE %s - tsCreated\n'
|
---|
2573 | 'WHERE idTestResult = %s\n'
|
---|
2574 | 'RETURNING tsElapsed'
|
---|
2575 | , ( cErrors, enmStatus, tsDone, oTestResult.idTestResult,) );
|
---|
2576 |
|
---|
2577 | oTestResult.tsElapsed = self._oDb.fetchOne()[0];
|
---|
2578 | oTestResult.enmStatus = enmStatus;
|
---|
2579 | oTestResult.cErrors = cErrors;
|
---|
2580 |
|
---|
2581 | self._oDb.maybeCommit(fCommit);
|
---|
2582 | return None;
|
---|
2583 |
|
---|
2584 | def _doPopHint(self, aoStack, cStackEntries, dCounts, idTestSet):
|
---|
2585 | """ Executes a PopHint. """
|
---|
2586 | assert cStackEntries >= 0;
|
---|
2587 | while len(aoStack) > cStackEntries:
|
---|
2588 | if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running:
|
---|
2589 | self._newFailureDetails(aoStack[0].idTestResult, idTestSet, 'XML error: Missing </Test>', dCounts);
|
---|
2590 | self._completeTestResults(aoStack[0], tsDone = None, cErrors = 1,
|
---|
2591 | enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True);
|
---|
2592 | aoStack.pop(0);
|
---|
2593 | return True;
|
---|
2594 |
|
---|
2595 |
|
---|
2596 | @staticmethod
|
---|
2597 | def _validateElement(sName, dAttribs, fClosed):
|
---|
2598 | """
|
---|
2599 | Validates an element and its attributes.
|
---|
2600 | """
|
---|
2601 |
|
---|
2602 | #
|
---|
2603 | # Validate attributes by name.
|
---|
2604 | #
|
---|
2605 |
|
---|
2606 | # Validate integer attributes.
|
---|
2607 | for sAttr in [ 'errors', 'testdepth' ]:
|
---|
2608 | if sAttr in dAttribs:
|
---|
2609 | try:
|
---|
2610 | _ = int(dAttribs[sAttr]);
|
---|
2611 | except:
|
---|
2612 | return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],);
|
---|
2613 |
|
---|
2614 | # Validate long attributes.
|
---|
2615 | for sAttr in [ 'value', ]:
|
---|
2616 | if sAttr in dAttribs:
|
---|
2617 | try:
|
---|
2618 | _ = long(dAttribs[sAttr]); # pylint: disable=redefined-variable-type
|
---|
2619 | except:
|
---|
2620 | return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],);
|
---|
2621 |
|
---|
2622 | # Validate string attributes.
|
---|
2623 | for sAttr in [ 'name', 'text' ]: # 'unit' can be zero length.
|
---|
2624 | if sAttr in dAttribs and not dAttribs[sAttr]:
|
---|
2625 | return 'Element %s has an empty %s attribute value.' % (sName, sAttr,);
|
---|
2626 |
|
---|
2627 | # Validate the timestamp attribute.
|
---|
2628 | if 'timestamp' in dAttribs:
|
---|
2629 | (dAttribs['timestamp'], sError) = ModelDataBase.validateTs(dAttribs['timestamp'], fAllowNull = False);
|
---|
2630 | if sError is not None:
|
---|
2631 | return 'Element %s has an invalid timestamp ("%s"): %s' % (sName, dAttribs['timestamp'], sError,);
|
---|
2632 |
|
---|
2633 |
|
---|
2634 | #
|
---|
2635 | # Check that attributes that are required are present.
|
---|
2636 | # We ignore extra attributes.
|
---|
2637 | #
|
---|
2638 | dElementAttribs = \
|
---|
2639 | {
|
---|
2640 | 'Test': [ 'timestamp', 'name', ],
|
---|
2641 | 'Value': [ 'timestamp', 'name', 'unit', 'value', ],
|
---|
2642 | 'FailureDetails': [ 'timestamp', 'text', ],
|
---|
2643 | 'Passed': [ 'timestamp', ],
|
---|
2644 | 'Skipped': [ 'timestamp', ],
|
---|
2645 | 'Failed': [ 'timestamp', 'errors', ],
|
---|
2646 | 'TimedOut': [ 'timestamp', 'errors', ],
|
---|
2647 | 'End': [ 'timestamp', ],
|
---|
2648 | 'PushHint': [ 'testdepth', ],
|
---|
2649 | 'PopHint': [ 'testdepth', ],
|
---|
2650 | };
|
---|
2651 | if sName not in dElementAttribs:
|
---|
2652 | return 'Unknown element "%s".' % (sName,);
|
---|
2653 | for sAttr in dElementAttribs[sName]:
|
---|
2654 | if sAttr not in dAttribs:
|
---|
2655 | return 'Element %s requires attribute "%s".' % (sName, sAttr);
|
---|
2656 |
|
---|
2657 | #
|
---|
2658 | # Only the Test element can (and must) remain open.
|
---|
2659 | #
|
---|
2660 | if sName == 'Test' and fClosed:
|
---|
2661 | return '<Test/> is not allowed.';
|
---|
2662 | if sName != 'Test' and not fClosed:
|
---|
2663 | return 'All elements except <Test> must be closed.';
|
---|
2664 |
|
---|
2665 | return None;
|
---|
2666 |
|
---|
2667 | @staticmethod
|
---|
2668 | def _parseElement(sElement):
|
---|
2669 | """
|
---|
2670 | Parses an element.
|
---|
2671 |
|
---|
2672 | """
|
---|
2673 | #
|
---|
2674 | # Element level bits.
|
---|
2675 | #
|
---|
2676 | sName = sElement.split()[0];
|
---|
2677 | sElement = sElement[len(sName):];
|
---|
2678 |
|
---|
2679 | fClosed = sElement[-1] == '/';
|
---|
2680 | if fClosed:
|
---|
2681 | sElement = sElement[:-1];
|
---|
2682 |
|
---|
2683 | #
|
---|
2684 | # Attributes.
|
---|
2685 | #
|
---|
2686 | sError = None;
|
---|
2687 | dAttribs = {};
|
---|
2688 | sElement = sElement.strip();
|
---|
2689 | while sElement:
|
---|
2690 | # Extract attribute name.
|
---|
2691 | off = sElement.find('=');
|
---|
2692 | if off < 0 or not sElement[:off].isalnum():
|
---|
2693 | sError = 'Attributes shall have alpha numberical names and have values.';
|
---|
2694 | break;
|
---|
2695 | sAttr = sElement[:off];
|
---|
2696 |
|
---|
2697 | # Extract attribute value.
|
---|
2698 | if off + 2 >= len(sElement) or sElement[off + 1] != '"':
|
---|
2699 | sError = 'Attribute (%s) value is missing or not in double quotes.' % (sAttr,);
|
---|
2700 | break;
|
---|
2701 | off += 2;
|
---|
2702 | offEndQuote = sElement.find('"', off);
|
---|
2703 | if offEndQuote < 0:
|
---|
2704 | sError = 'Attribute (%s) value is missing end quotation mark.' % (sAttr,);
|
---|
2705 | break;
|
---|
2706 | sValue = sElement[off:offEndQuote];
|
---|
2707 |
|
---|
2708 | # Check for duplicates.
|
---|
2709 | if sAttr in dAttribs:
|
---|
2710 | sError = 'Attribute "%s" appears more than once.' % (sAttr,);
|
---|
2711 | break;
|
---|
2712 |
|
---|
2713 | # Unescape the value.
|
---|
2714 | sValue = sValue.replace('<', '<');
|
---|
2715 | sValue = sValue.replace('>', '>');
|
---|
2716 | sValue = sValue.replace(''', '\'');
|
---|
2717 | sValue = sValue.replace('"', '"');
|
---|
2718 | sValue = sValue.replace('
', '\n');
|
---|
2719 | sValue = sValue.replace('
', '\r');
|
---|
2720 | sValue = sValue.replace('&', '&'); # last
|
---|
2721 |
|
---|
2722 | # Done.
|
---|
2723 | dAttribs[sAttr] = sValue;
|
---|
2724 |
|
---|
2725 | # advance
|
---|
2726 | sElement = sElement[offEndQuote + 1:];
|
---|
2727 | sElement = sElement.lstrip();
|
---|
2728 |
|
---|
2729 | #
|
---|
2730 | # Validate the element before we return.
|
---|
2731 | #
|
---|
2732 | if sError is None:
|
---|
2733 | sError = TestResultLogic._validateElement(sName, dAttribs, fClosed);
|
---|
2734 |
|
---|
2735 | return (sName, dAttribs, sError)
|
---|
2736 |
|
---|
2737 | def _handleElement(self, sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts):
|
---|
2738 | """
|
---|
2739 | Worker for processXmlStream that handles one element.
|
---|
2740 |
|
---|
2741 | Returns None on success, error string on bad XML or similar.
|
---|
2742 | Raises exception on hanging offence and on database error.
|
---|
2743 | """
|
---|
2744 | if sName == 'Test':
|
---|
2745 | iNestingDepth = aoStack[0].iNestingDepth + 1 if aoStack else 0;
|
---|
2746 | aoStack.insert(0, self._newTestResult(idTestResultParent = aoStack[0].idTestResult, idTestSet = idTestSet,
|
---|
2747 | tsCreated = dAttribs['timestamp'], sName = dAttribs['name'],
|
---|
2748 | iNestingDepth = iNestingDepth, dCounts = dCounts, fCommit = True) );
|
---|
2749 |
|
---|
2750 | elif sName == 'Value':
|
---|
2751 | self._newTestValue(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet, tsCreated = dAttribs['timestamp'],
|
---|
2752 | sName = dAttribs['name'], sUnit = dAttribs['unit'], lValue = long(dAttribs['value']),
|
---|
2753 | dCounts = dCounts, fCommit = True);
|
---|
2754 |
|
---|
2755 | elif sName == 'FailureDetails':
|
---|
2756 | self._newFailureDetails(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet,
|
---|
2757 | tsCreated = dAttribs['timestamp'], sText = dAttribs['text'], dCounts = dCounts,
|
---|
2758 | fCommit = True);
|
---|
2759 |
|
---|
2760 | elif sName == 'Passed':
|
---|
2761 | self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
|
---|
2762 | enmStatus = TestResultData.ksTestStatus_Success, fCommit = True);
|
---|
2763 |
|
---|
2764 | elif sName == 'Skipped':
|
---|
2765 | self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
|
---|
2766 | enmStatus = TestResultData.ksTestStatus_Skipped, fCommit = True);
|
---|
2767 |
|
---|
2768 | elif sName == 'Failed':
|
---|
2769 | self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
|
---|
2770 | enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True);
|
---|
2771 |
|
---|
2772 | elif sName == 'TimedOut':
|
---|
2773 | self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
|
---|
2774 | enmStatus = TestResultData.ksTestStatus_TimedOut, fCommit = True);
|
---|
2775 |
|
---|
2776 | elif sName == 'End':
|
---|
2777 | self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
|
---|
2778 | cErrors = int(dAttribs.get('errors', '1')),
|
---|
2779 | enmStatus = TestResultData.ksTestStatus_Success, fCommit = True);
|
---|
2780 |
|
---|
2781 | elif sName == 'PushHint':
|
---|
2782 | if len(aaiHints) > 1:
|
---|
2783 | return 'PushHint cannot be nested.'
|
---|
2784 |
|
---|
2785 | aaiHints.insert(0, [len(aoStack), int(dAttribs['testdepth'])]);
|
---|
2786 |
|
---|
2787 | elif sName == 'PopHint':
|
---|
2788 | if not aaiHints:
|
---|
2789 | return 'No hint to pop.'
|
---|
2790 |
|
---|
2791 | iDesiredTestDepth = int(dAttribs['testdepth']);
|
---|
2792 | cStackEntries, iTestDepth = aaiHints.pop(0);
|
---|
2793 | self._doPopHint(aoStack, cStackEntries, dCounts, idTestSet); # Fake the necessary '<End/></Test>' tags.
|
---|
2794 | if iDesiredTestDepth != iTestDepth:
|
---|
2795 | return 'PopHint tag has different testdepth: %d, on stack %d.' % (iDesiredTestDepth, iTestDepth);
|
---|
2796 | else:
|
---|
2797 | return 'Unexpected element "%s".' % (sName,);
|
---|
2798 | return None;
|
---|
2799 |
|
---|
2800 |
|
---|
2801 | def processXmlStream(self, sXml, idTestSet):
|
---|
2802 | """
|
---|
2803 | Processes the "XML" stream section given in sXml.
|
---|
2804 |
|
---|
2805 | The sXml isn't a complete XML document, even should we save up all sXml
|
---|
2806 | for a given set, they may not form a complete and well formed XML
|
---|
2807 | document since the test may be aborted, abend or simply be buggy. We
|
---|
2808 | therefore do our own parsing and treat the XML tags as commands more
|
---|
2809 | than anything else.
|
---|
2810 |
|
---|
2811 | Returns (sError, fUnforgivable), where sError is None on success.
|
---|
2812 | May raise database exception.
|
---|
2813 | """
|
---|
2814 | aoStack = self._getResultStack(idTestSet); # [0] == top; [-1] == bottom.
|
---|
2815 | if not aoStack:
|
---|
2816 | return ('No open results', True);
|
---|
2817 | self._oDb.dprint('** processXmlStream len(aoStack)=%s' % (len(aoStack),));
|
---|
2818 | #self._oDb.dprint('processXmlStream: %s' % (self._stringifyStack(aoStack),));
|
---|
2819 | #self._oDb.dprint('processXmlStream: sXml=%s' % (sXml,));
|
---|
2820 |
|
---|
2821 | dCounts = {};
|
---|
2822 | aaiHints = [];
|
---|
2823 | sError = None;
|
---|
2824 |
|
---|
2825 | fExpectCloseTest = False;
|
---|
2826 | sXml = sXml.strip();
|
---|
2827 | while sXml:
|
---|
2828 | if sXml.startswith('</Test>'): # Only closing tag.
|
---|
2829 | offNext = len('</Test>');
|
---|
2830 | if len(aoStack) <= 1:
|
---|
2831 | sError = 'Trying to close the top test results.'
|
---|
2832 | break;
|
---|
2833 | # ASSUMES that we've just seen an <End/>, <Passed/>, <Failed/>,
|
---|
2834 | # <TimedOut/> or <Skipped/> tag earlier in this call!
|
---|
2835 | if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running or not fExpectCloseTest:
|
---|
2836 | sError = 'Missing <End/>, <Passed/>, <Failed/>, <TimedOut/> or <Skipped/> tag.';
|
---|
2837 | break;
|
---|
2838 | aoStack.pop(0);
|
---|
2839 | fExpectCloseTest = False;
|
---|
2840 |
|
---|
2841 | elif fExpectCloseTest:
|
---|
2842 | sError = 'Expected </Test>.'
|
---|
2843 | break;
|
---|
2844 |
|
---|
2845 | elif sXml.startswith('<?xml '): # Ignore (included files).
|
---|
2846 | offNext = sXml.find('?>');
|
---|
2847 | if offNext < 0:
|
---|
2848 | sError = 'Unterminated <?xml ?> element.';
|
---|
2849 | break;
|
---|
2850 | offNext += 2;
|
---|
2851 |
|
---|
2852 | elif sXml[0] == '<':
|
---|
2853 | # Parse and check the tag.
|
---|
2854 | if not sXml[1].isalpha():
|
---|
2855 | sError = 'Malformed element.';
|
---|
2856 | break;
|
---|
2857 | offNext = sXml.find('>')
|
---|
2858 | if offNext < 0:
|
---|
2859 | sError = 'Unterminated element.';
|
---|
2860 | break;
|
---|
2861 | (sName, dAttribs, sError) = self._parseElement(sXml[1:offNext]);
|
---|
2862 | offNext += 1;
|
---|
2863 | if sError is not None:
|
---|
2864 | break;
|
---|
2865 |
|
---|
2866 | # Handle it.
|
---|
2867 | try:
|
---|
2868 | sError = self._handleElement(sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts);
|
---|
2869 | except TestResultHangingOffence as oXcpt:
|
---|
2870 | self._inhumeTestResults(aoStack, idTestSet, str(oXcpt));
|
---|
2871 | return (str(oXcpt), True);
|
---|
2872 |
|
---|
2873 |
|
---|
2874 | fExpectCloseTest = sName in [ 'End', 'Passed', 'Failed', 'TimedOut', 'Skipped', ];
|
---|
2875 | else:
|
---|
2876 | sError = 'Unexpected content.';
|
---|
2877 | break;
|
---|
2878 |
|
---|
2879 | # Advance.
|
---|
2880 | sXml = sXml[offNext:];
|
---|
2881 | sXml = sXml.lstrip();
|
---|
2882 |
|
---|
2883 | #
|
---|
2884 | # Post processing checks.
|
---|
2885 | #
|
---|
2886 | if sError is None and fExpectCloseTest:
|
---|
2887 | sError = 'Expected </Test> before the end of the XML section.'
|
---|
2888 | elif sError is None and aaiHints:
|
---|
2889 | sError = 'Expected </PopHint> before the end of the XML section.'
|
---|
2890 | if aaiHints:
|
---|
2891 | self._doPopHint(aoStack, aaiHints[-1][0], dCounts, idTestSet);
|
---|
2892 |
|
---|
2893 | #
|
---|
2894 | # Log the error.
|
---|
2895 | #
|
---|
2896 | if sError is not None:
|
---|
2897 | SystemLogLogic(self._oDb).addEntry(SystemLogData.ksEvent_XmlResultMalformed,
|
---|
2898 | 'idTestSet=%s idTestResult=%s XML="%s" %s'
|
---|
2899 | % ( idTestSet,
|
---|
2900 | aoStack[0].idTestResult if aoStack else -1,
|
---|
2901 | sXml[:min(len(sXml), 30)],
|
---|
2902 | sError, ),
|
---|
2903 | cHoursRepeat = 6, fCommit = True);
|
---|
2904 | return (sError, False);
|
---|
2905 |
|
---|
2906 |
|
---|
2907 |
|
---|
2908 |
|
---|
2909 |
|
---|
2910 | #
|
---|
2911 | # Unit testing.
|
---|
2912 | #
|
---|
2913 |
|
---|
2914 | # pylint: disable=missing-docstring
|
---|
2915 | class TestResultDataTestCase(ModelDataBaseTestCase):
|
---|
2916 | def setUp(self):
|
---|
2917 | self.aoSamples = [TestResultData(),];
|
---|
2918 |
|
---|
2919 | class TestResultValueDataTestCase(ModelDataBaseTestCase):
|
---|
2920 | def setUp(self):
|
---|
2921 | self.aoSamples = [TestResultValueData(),];
|
---|
2922 |
|
---|
2923 | if __name__ == '__main__':
|
---|
2924 | unittest.main();
|
---|
2925 | # not reached.
|
---|
2926 |
|
---|