VirtualBox

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

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

testresults.py: Added 'unit' to kaTcMisc.

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