VirtualBox

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

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

pylint

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