VirtualBox

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

最後變更 在這個檔案從96407是 96407,由 vboxsync 提交於 3 年 前

scm copyright and license note update

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

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