VirtualBox

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

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

ValidationKit: teach the sheriff to draw the right conclusions from an installer running into a timeout, plus minor typo and other cleanups

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

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