VirtualBox

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

Last change on this file since 65213 was 65167, checked in by vboxsync, 8 years ago

TestManager: Use regexp word matching for np, hw, raw and similarlly short works. This helps including the smoketests.

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

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