VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 2 years ago

Copyright year updates by scm.

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