VirtualBox

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

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

testmanager: 'UInstall' filter. bugref:9151

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

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