VirtualBox

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

Last change on this file since 79092 was 79092, checked in by vboxsync, 6 years ago

ValKit,++: Pylint 2.3.1 adjustments.

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