VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/testresults.py

Last change on this file was 106835, checked in by vboxsync, 5 weeks ago

Validation Kit: Reverted r165745 again, as it needs manually updating the testmanager. bugref:10762

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette