VirtualBox

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

Last change on this file since 106834 was 106834, checked in by vboxsync, 5 weeks ago

Validation Kit: Added new log type for setupapi logs. bugref:10762

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

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