VirtualBox

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

最後變更 在這個檔案從59687是 57680,由 vboxsync 提交於 10 年 前

testresults.py: the 'unit' attribute can be empty (RTTESTUNIT_NONE) - 2nd try.

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 73.5 KB
 
1# -*- coding: utf-8 -*-
2# $Id: testresults.py 57680 2015-09-09 20:16:35Z vboxsync $
3# pylint: disable=C0302
4
5## @todo Rename this file to testresult.py!
6
7"""
8Test Manager - Fetch test results.
9"""
10
11__copyright__ = \
12"""
13Copyright (C) 2012-2015 Oracle Corporation
14
15This file is part of VirtualBox Open Source Edition (OSE), as
16available from http://www.alldomusa.eu.org. This file is free software;
17you can redistribute it and/or modify it under the terms of the GNU
18General Public License (GPL) as published by the Free Software
19Foundation, in version 2 as it comes in the "COPYING" file of the
20VirtualBox OSE distribution. VirtualBox OSE is distributed in the
21hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
22
23The contents of this file may alternatively be used under the terms
24of the Common Development and Distribution License Version 1.0
25(CDDL) only, as it comes in the "COPYING.CDDL" file of the
26VirtualBox OSE distribution, in which case the provisions of the
27CDDL are applicable instead of those of the GPL.
28
29You may elect to license modified versions of this file under the
30terms and conditions of either the GPL or the CDDL or both.
31"""
32__version__ = "$Revision: 57680 $"
33# Standard python imports.
34import unittest;
35
36# Validation Kit imports.
37from common import constants;
38from testmanager import config;
39from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMExceptionBase, TMTooManyRows;
40from testmanager.core.testgroup import TestGroupData
41from testmanager.core.build import BuildDataEx
42from testmanager.core.testbox import TestBoxData
43from testmanager.core.testcase import TestCaseData
44from testmanager.core.schedgroup import SchedGroupData
45from testmanager.core.systemlog import SystemLogData, SystemLogLogic;
46
47
48class TestResultData(ModelDataBase):
49 """
50 Test case execution result data
51 """
52
53 ## @name TestStatus_T
54 # @{
55 ksTestStatus_Running = 'running';
56 ksTestStatus_Success = 'success';
57 ksTestStatus_Skipped = 'skipped';
58 ksTestStatus_BadTestBox = 'bad-testbox';
59 ksTestStatus_Aborted = 'aborted';
60 ksTestStatus_Failure = 'failure';
61 ksTestStatus_TimedOut = 'timed-out';
62 ksTestStatus_Rebooted = 'rebooted';
63 ## @}
64
65 ## List of relatively harmless (to testgroup/case) statuses.
66 kasHarmlessTestStatuses = [ ksTestStatus_Skipped, ksTestStatus_BadTestBox, ksTestStatus_Aborted, ];
67 ## List of bad statuses.
68 kasBadTestStatuses = [ ksTestStatus_Failure, ksTestStatus_TimedOut, ksTestStatus_Rebooted, ];
69
70
71 ksIdAttr = 'idTestResult';
72
73 ksParam_idTestResult = 'TestResultData_idTestResult';
74 ksParam_idTestResultParent = 'TestResultData_idTestResultParent';
75 ksParam_idTestSet = 'TestResultData_idTestSet';
76 ksParam_tsCreated = 'TestResultData_tsCreated';
77 ksParam_tsElapsed = 'TestResultData_tsElapsed';
78 ksParam_idStrName = 'TestResultData_idStrName';
79 ksParam_cErrors = 'TestResultData_cErrors';
80 ksParam_enmStatus = 'TestResultData_enmStatus';
81 ksParam_iNestingDepth = 'TestResultData_iNestingDepth';
82 kasValidValues_enmStatus = [
83 ksTestStatus_Running,
84 ksTestStatus_Success,
85 ksTestStatus_Skipped,
86 ksTestStatus_BadTestBox,
87 ksTestStatus_Aborted,
88 ksTestStatus_Failure,
89 ksTestStatus_TimedOut,
90 ksTestStatus_Rebooted
91 ];
92
93
94 def __init__(self):
95 ModelDataBase.__init__(self)
96 self.idTestResult = None
97 self.idTestResultParent = None
98 self.idTestSet = None
99 self.tsCreated = None
100 self.tsElapsed = None
101 self.idStrName = None
102 self.cErrors = 0;
103 self.enmStatus = None
104 self.iNestingDepth = None
105
106 def initFromDbRow(self, aoRow):
107 """
108 Reinitialize from a SELECT * FROM TestResults.
109 Return self. Raises exception if no row.
110 """
111 if aoRow is None:
112 raise TMExceptionBase('Test result record not found.')
113
114 self.idTestResult = aoRow[0]
115 self.idTestResultParent = aoRow[1]
116 self.idTestSet = aoRow[2]
117 self.tsCreated = aoRow[3]
118 self.tsElapsed = aoRow[4]
119 self.idStrName = aoRow[5]
120 self.cErrors = aoRow[6]
121 self.enmStatus = aoRow[7]
122 self.iNestingDepth = aoRow[8]
123 return self;
124
125 def isFailure(self):
126 """ Check if it's a real failure. """
127 return self.enmStatus in self.kasBadTestStatuses;
128
129
130class TestResultDataEx(TestResultData):
131 """
132 Extended test result data class.
133
134 This is intended for use as a node in a result tree. This is not intended
135 for serialization to parameters or vice versa. Use TestResultLogic to
136 construct the tree.
137 """
138
139 def __init__(self):
140 TestResultData.__init__(self)
141 self.sName = None; # idStrName resolved.
142 self.oParent = None; # idTestResultParent within the tree.
143
144 self.aoChildren = []; # TestResultDataEx;
145 self.aoValues = []; # TestResultValue;
146 self.aoMsgs = []; # TestResultMsg;
147 self.aoFiles = []; # TestResultFile;
148
149 def initFromDbRow(self, aoRow):
150 """
151 Initialize from a query like this:
152 SELECT TestResults.*, TestResultStrTab.sValue
153 FROM TestResults, TestResultStrTab
154 WHERE TestResultStrTab.idStr = TestResults.idStrName
155
156 Note! The caller is expected to fetch children, values, failure
157 details, and files.
158 """
159 self.sName = None;
160 self.oParent = None;
161 self.aoChildren = [];
162 self.aoValues = [];
163 self.aoMsgs = [];
164 self.aoFiles = [];
165
166 TestResultData.initFromDbRow(self, aoRow);
167
168 self.sName = aoRow[9];
169 return self;
170
171
172class TestResultValueData(ModelDataBase):
173 """
174 Test result value data.
175 """
176
177 ksIdAttr = 'idTestResultValue';
178
179 ksParam_idTestResultValue = 'TestResultValue_idTestResultValue';
180 ksParam_idTestResult = 'TestResultValue_idTestResult';
181 ksParam_idTestSet = 'TestResultValue_idTestSet';
182 ksParam_tsCreated = 'TestResultValue_tsCreated';
183 ksParam_idStrName = 'TestResultValue_idStrName';
184 ksParam_lValue = 'TestResultValue_lValue';
185 ksParam_iUnit = 'TestResultValue_iUnit';
186
187 def __init__(self):
188 ModelDataBase.__init__(self)
189 self.idTestResultValue = None;
190 self.idTestResult = None;
191 self.idTestSet = None;
192 self.tsCreated = None;
193 self.idStrName = None;
194 self.lValue = None;
195 self.iUnit = 0;
196
197 def initFromDbRow(self, aoRow):
198 """
199 Reinitialize from a SELECT * FROM TestResultValues.
200 Return self. Raises exception if no row.
201 """
202 if aoRow is None:
203 raise TMExceptionBase('Test result value record not found.')
204
205 self.idTestResultValue = aoRow[0];
206 self.idTestResult = aoRow[1];
207 self.idTestSet = aoRow[2];
208 self.tsCreated = aoRow[3];
209 self.idStrName = aoRow[4];
210 self.lValue = aoRow[5];
211 self.iUnit = aoRow[6];
212 return self;
213
214
215class TestResultValueDataEx(TestResultValueData):
216 """
217 Extends TestResultValue by resolving the value name and unit string.
218 """
219
220 def __init__(self):
221 TestResultValueData.__init__(self)
222 self.sName = None;
223 self.sUnit = '';
224
225 def initFromDbRow(self, aoRow):
226 """
227 Reinitialize from a query like this:
228 SELECT TestResultValues.*, TestResultStrTab.sValue
229 FROM TestResultValues, TestResultStrTab
230 WHERE TestResultStrTab.idStr = TestResultValues.idStrName
231
232 Return self. Raises exception if no row.
233 """
234 TestResultValueData.initFromDbRow(self, aoRow);
235 self.sName = aoRow[7];
236 if self.iUnit < len(constants.valueunit.g_asNames):
237 self.sUnit = constants.valueunit.g_asNames[self.iUnit];
238 else:
239 self.sUnit = '<%d>' % (self.iUnit,);
240 return self;
241
242class TestResultMsgData(ModelDataBase):
243 """
244 Test result message data.
245 """
246
247 ksIdAttr = 'idTestResultMsg';
248
249 ksParam_idTestResultMsg = 'TestResultValue_idTestResultMsg';
250 ksParam_idTestResult = 'TestResultValue_idTestResult';
251 ksParam_tsCreated = 'TestResultValue_tsCreated';
252 ksParam_idStrMsg = 'TestResultValue_idStrMsg';
253 ksParam_enmLevel = 'TestResultValue_enmLevel';
254
255 def __init__(self):
256 ModelDataBase.__init__(self)
257 self.idTestResultMsg = None;
258 self.idTestResult = None;
259 self.tsCreated = None;
260 self.idStrMsg = None;
261 self.enmLevel = None;
262
263 def initFromDbRow(self, aoRow):
264 """
265 Reinitialize from a SELECT * FROM TestResultMsgs.
266 Return self. Raises exception if no row.
267 """
268 if aoRow is None:
269 raise TMExceptionBase('Test result value record not found.')
270
271 self.idTestResultMsg = aoRow[0];
272 self.idTestResult = aoRow[1];
273 self.tsCreated = aoRow[2];
274 self.idStrMsg = aoRow[3];
275 self.enmLevel = aoRow[4];
276 return self;
277
278class TestResultMsgDataEx(TestResultMsgData):
279 """
280 Extends TestResultMsg by resolving the message string.
281 """
282
283 def __init__(self):
284 TestResultMsgData.__init__(self)
285 self.sMsg = None;
286
287 def initFromDbRow(self, aoRow):
288 """
289 Reinitialize from a query like this:
290 SELECT TestResultMsg.*, TestResultStrTab.sValue
291 FROM TestResultMsg, TestResultStrTab
292 WHERE TestResultStrTab.idStr = TestResultMsgs.idStrName
293
294 Return self. Raises exception if no row.
295 """
296 TestResultMsgData.initFromDbRow(self, aoRow);
297 self.sMsg = aoRow[5];
298 return self;
299
300class TestResultFileData(ModelDataBase):
301 """
302 Test result message data.
303 """
304
305 ksIdAttr = 'idTestResultFile';
306
307 ksParam_idTestResultFile = 'TestResultFile_idTestResultFile';
308 ksParam_idTestResult = 'TestResultFile_idTestResult';
309 ksParam_tsCreated = 'TestResultFile_tsCreated';
310 ksParam_idStrFile = 'TestResultFile_idStrFile';
311 ksParam_idStrDescription = 'TestResultFile_idStrDescription';
312 ksParam_idStrKind = 'TestResultFile_idStrKind';
313 ksParam_idStrMime = 'TestResultFile_idStrMime';
314
315 def __init__(self):
316 ModelDataBase.__init__(self)
317 self.idTestResultFile = None;
318 self.idTestResult = None;
319 self.tsCreated = None;
320 self.idStrFile = None;
321 self.idStrDescription = None;
322 self.idStrKind = None;
323 self.idStrMime = None;
324
325 def initFromDbRow(self, aoRow):
326 """
327 Reinitialize from a SELECT * FROM TestResultFiles.
328 Return self. Raises exception if no row.
329 """
330 if aoRow is None:
331 raise TMExceptionBase('Test result file record not found.')
332
333 self.idTestResultFile = aoRow[0];
334 self.idTestResult = aoRow[1];
335 self.tsCreated = aoRow[2];
336 self.idStrFile = aoRow[3];
337 self.idStrDescription = aoRow[4];
338 self.idStrKind = aoRow[5];
339 self.idStrMime = aoRow[6];
340 return self;
341
342class TestResultFileDataEx(TestResultFileData):
343 """
344 Extends TestResultFile by resolving the strings.
345 """
346
347 def __init__(self):
348 TestResultFileData.__init__(self)
349 self.sFile = None;
350 self.sDescription = None;
351 self.sKind = None;
352 self.sMime = None;
353
354 def initFromDbRow(self, aoRow):
355 """
356 Reinitialize from a query like this:
357 SELECT TestResultFiles.*,
358 StrTabFile.sValue AS sFile,
359 StrTabDesc.sValue AS sDescription
360 StrTabKind.sValue AS sKind,
361 StrTabMime.sValue AS sMime,
362 FROM ...
363
364 Return self. Raises exception if no row.
365 """
366 TestResultFileData.initFromDbRow(self, aoRow);
367 self.sFile = aoRow[7];
368 self.sDescription = aoRow[8];
369 self.sKind = aoRow[9];
370 self.sMime = aoRow[10];
371 return self;
372
373 def initFakeMainLog(self, oTestSet):
374 """
375 Reinitializes to represent the main.log object (not in DB).
376
377 Returns self.
378 """
379 self.idTestResultFile = 0;
380 self.idTestResult = oTestSet.idTestResult;
381 self.tsCreated = oTestSet.tsCreated;
382 self.idStrFile = None;
383 self.idStrDescription = None;
384 self.idStrKind = None;
385 self.idStrMime = None;
386
387 self.sFile = 'main.log';
388 self.sDescription = '';
389 self.sKind = 'log/main';
390 self.sMime = 'text/plain';
391 return self;
392
393 def isProbablyUtf8Encoded(self):
394 """
395 Checks if the file is likely to be UTF-8 encoded.
396 """
397 if self.sMime in [ 'text/plain', 'text/html' ]:
398 return True;
399 return False;
400
401 def getMimeWithEncoding(self):
402 """
403 Gets the MIME type with encoding if likely to be UTF-8.
404 """
405 if self.isProbablyUtf8Encoded():
406 return '%s; charset=utf-8' % (self.sMime,);
407 return self.sMime;
408
409
410class TestResultListingData(ModelDataBase): # pylint: disable=R0902
411 """
412 Test case result data representation for table listing
413 """
414
415 def __init__(self):
416 """Initialize"""
417 ModelDataBase.__init__(self)
418
419 self.idTestSet = None
420
421 self.idBuildCategory = None;
422 self.sProduct = None
423 self.sRepository = None;
424 self.sBranch = None
425 self.sType = None
426 self.idBuild = None;
427 self.sVersion = None;
428 self.iRevision = None
429
430 self.sOs = None;
431 self.sOsVersion = None;
432 self.sArch = None;
433 self.sCpuVendor = None;
434 self.sCpuName = None;
435 self.cCpus = None;
436 self.fCpuHwVirt = None;
437 self.fCpuNestedPaging = None;
438 self.fCpu64BitGuest = None;
439 self.idTestBox = None
440 self.sTestBoxName = None
441
442 self.tsCreated = None
443 self.tsElapsed = None
444 self.enmStatus = None
445 self.cErrors = None;
446
447 self.idTestCase = None
448 self.sTestCaseName = None
449 self.sBaseCmd = None
450 self.sArgs = None
451
452 self.idBuildTestSuite = None;
453 self.iRevisionTestSuite = None;
454
455 def initFromDbRow(self, aoRow):
456 """
457 Reinitialize from a database query.
458 Return self. Raises exception if no row.
459 """
460 if aoRow is None:
461 raise TMExceptionBase('Test result record not found.')
462
463 self.idTestSet = aoRow[0];
464
465 self.idBuildCategory = aoRow[1];
466 self.sProduct = aoRow[2];
467 self.sRepository = aoRow[3];
468 self.sBranch = aoRow[4];
469 self.sType = aoRow[5];
470 self.idBuild = aoRow[6];
471 self.sVersion = aoRow[7];
472 self.iRevision = aoRow[8];
473
474 self.sOs = aoRow[9];
475 self.sOsVersion = aoRow[10];
476 self.sArch = aoRow[11];
477 self.sCpuVendor = aoRow[12];
478 self.sCpuName = aoRow[13];
479 self.cCpus = aoRow[14];
480 self.fCpuHwVirt = aoRow[15];
481 self.fCpuNestedPaging = aoRow[16];
482 self.fCpu64BitGuest = aoRow[17];
483 self.idTestBox = aoRow[18];
484 self.sTestBoxName = aoRow[19];
485
486 self.tsCreated = aoRow[20];
487 self.tsElapsed = aoRow[21];
488 self.enmStatus = aoRow[22];
489 self.cErrors = aoRow[23];
490
491 self.idTestCase = aoRow[24];
492 self.sTestCaseName = aoRow[25];
493 self.sBaseCmd = aoRow[26];
494 self.sArgs = aoRow[27];
495
496 self.idBuildTestSuite = aoRow[28];
497 self.iRevisionTestSuite = aoRow[29];
498
499 return self
500
501
502class TestResultHangingOffence(TMExceptionBase):
503 """Hanging offence committed by test case."""
504 pass;
505
506class TestResultLogic(ModelLogicBase): # pylint: disable=R0903
507 """
508 Results grouped by scheduling group.
509 """
510
511 #
512 # Result grinding for displaying in the WUI.
513 #
514
515 ksResultsGroupingTypeNone = 'ResultsGroupingTypeNone';
516 ksResultsGroupingTypeTestGroup = 'ResultsGroupingTypeTestGroup';
517 ksResultsGroupingTypeBuildRev = 'ResultsGroupingTypeBuild';
518 ksResultsGroupingTypeTestBox = 'ResultsGroupingTypeTestBox';
519 ksResultsGroupingTypeTestCase = 'ResultsGroupingTypeTestCase';
520 ksResultsGroupingTypeSchedGroup = 'ResultsGroupingTypeSchedGroup';
521
522 ## @name Result sorting options.
523 ## @{
524 ksResultsSortByRunningAndStart = 'ResultsSortByRunningAndStart'; ##< Default
525 ksResultsSortByBuildRevision = 'ResultsSortByBuildRevision';
526 ksResultsSortByTestBoxName = 'ResultsSortByTestBoxName';
527 ksResultsSortByTestBoxOs = 'ResultsSortByTestBoxOs';
528 ksResultsSortByTestBoxOsVersion = 'ResultsSortByTestBoxOsVersion';
529 ksResultsSortByTestBoxOsArch = 'ResultsSortByTestBoxOsArch';
530 ksResultsSortByTestBoxArch = 'ResultsSortByTestBoxArch';
531 ksResultsSortByTestBoxCpuVendor = 'ResultsSortByTestBoxCpuVendor';
532 ksResultsSortByTestBoxCpuName = 'ResultsSortByTestBoxCpuName';
533 ksResultsSortByTestBoxCpuRev = 'ResultsSortByTestBoxCpuRev';
534 ksResultsSortByTestBoxCpuFeatures = 'ResultsSortByTestBoxCpuFeatures';
535 ksResultsSortByTestCaseName = 'ResultsSortByTestCaseName';
536 kasResultsSortBy = {
537 ksResultsSortByRunningAndStart,
538 ksResultsSortByBuildRevision,
539 ksResultsSortByTestBoxName,
540 ksResultsSortByTestBoxOs,
541 ksResultsSortByTestBoxOsVersion,
542 ksResultsSortByTestBoxOsArch,
543 ksResultsSortByTestBoxArch,
544 ksResultsSortByTestBoxCpuVendor,
545 ksResultsSortByTestBoxCpuName,
546 ksResultsSortByTestBoxCpuRev,
547 ksResultsSortByTestBoxCpuFeatures,
548 ksResultsSortByTestCaseName,
549 };
550 ## Used by the WUI for generating the drop down.
551 kaasResultsSortByTitles = (
552 ( ksResultsSortByRunningAndStart, 'Running & Start TS' ),
553 ( ksResultsSortByBuildRevision, 'Build Revision' ),
554 ( ksResultsSortByTestBoxName, 'TestBox Name' ),
555 ( ksResultsSortByTestBoxOs, 'O/S' ),
556 ( ksResultsSortByTestBoxOsVersion, 'O/S Version' ),
557 ( ksResultsSortByTestBoxOsArch, 'O/S & Architecture' ),
558 ( ksResultsSortByTestBoxArch, 'Architecture' ),
559 ( ksResultsSortByTestBoxCpuVendor, 'CPU Vendor' ),
560 ( ksResultsSortByTestBoxCpuName, 'CPU Vendor & Name' ),
561 ( ksResultsSortByTestBoxCpuRev, 'CPU Vendor & Revision' ),
562 ( ksResultsSortByTestBoxCpuFeatures, 'CPU Features' ),
563 ( ksResultsSortByTestCaseName, 'Test Case Name' ),
564 );
565 ## @}
566
567 ## Default sort by map.
568 kdResultSortByMap = {
569 ksResultsSortByRunningAndStart: ('', None, None, ''),
570 ksResultsSortByBuildRevision: (
571 # Sorting tables.
572 ', Builds',
573 # Sorting table join(s).
574 ' AND TestSets.idBuild = Builds.idBuild'
575 ' AND Builds.tsExpire >= TestSets.tsCreated'
576 ' AND Builds.tsEffective <= TestSets.tsCreated',
577 # Start of ORDER BY statement.
578 ' Builds.iRevision DESC',
579 # Extra columns to fetch for the above ORDER BY to work in a SELECT DISTINCT statement.
580 '' ),
581 ksResultsSortByTestBoxName: (
582 ', TestBoxes',
583 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
584 ' TestBoxes.sName DESC',
585 '' ),
586 ksResultsSortByTestBoxOsArch: (
587 ', TestBoxes',
588 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
589 ' TestBoxes.sOs, TestBoxes.sCpuArch',
590 '' ),
591 ksResultsSortByTestBoxOs: (
592 ', TestBoxes',
593 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
594 ' TestBoxes.sOs',
595 '' ),
596 ksResultsSortByTestBoxOsVersion: (
597 ', TestBoxes',
598 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
599 ' TestBoxes.sOs, TestBoxes.sOsVersion DESC',
600 '' ),
601 ksResultsSortByTestBoxArch: (
602 ', TestBoxes',
603 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
604 ' TestBoxes.sCpuArch',
605 '' ),
606 ksResultsSortByTestBoxCpuVendor: (
607 ', TestBoxes',
608 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
609 ' TestBoxes.sCpuVendor',
610 '' ),
611 ksResultsSortByTestBoxCpuName: (
612 ', TestBoxes',
613 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
614 ' TestBoxes.sCpuVendor, TestBoxes.sCpuName',
615 '' ),
616 ksResultsSortByTestBoxCpuRev: (
617 ', TestBoxes',
618 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
619 ' TestBoxes.sCpuVendor, TestBoxes.lCpuRevision DESC',
620 ', TestBoxes.lCpuRevision' ),
621 ksResultsSortByTestBoxCpuFeatures: (
622 ', TestBoxes',
623 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
624 ' TestBoxes.fCpuHwVirt DESC, TestBoxes.fCpuNestedPaging DESC, TestBoxes.fCpu64BitGuest DESC, TestBoxes.cCpus DESC',
625 ', TestBoxes.cCpus' ),
626 ksResultsSortByTestCaseName: (
627 ', TestCases',
628 ' AND TestSets.idGenTestCase = TestCases.idGenTestCase',
629 ' TestCases.sName',
630 '' ),
631 };
632
633 kdResultGroupingMap = {
634 ksResultsGroupingTypeNone: (
635 # Grouping tables; # Grouping field; # Grouping where addition. # Sort by overrides.
636 'TestSets', None, None, {}
637 ),
638 ksResultsGroupingTypeTestGroup: ('TestSets', 'TestSets.idTestGroup', None, {}),
639 ksResultsGroupingTypeTestBox: ('TestSets', 'TestSets.idTestBox', None, {}),
640 ksResultsGroupingTypeTestCase: ('TestSets', 'TestSets.idTestCase', None, {}),
641 ksResultsGroupingTypeBuildRev: (
642 'TestSets, Builds',
643 'Builds.iRevision',
644 ' AND Builds.idBuild = TestSets.idBuild'
645 ' AND Builds.tsExpire > TestSets.tsCreated'
646 ' AND Builds.tsEffective <= TestSets.tsCreated',
647 { ksResultsSortByBuildRevision: ( '', None, ' Builds.iRevision DESC' ), }
648 ),
649 ksResultsGroupingTypeSchedGroup: (
650 'TestSets, TestBoxes',
651 'TestBoxes.idSchedGroup',
652 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
653 { ksResultsSortByTestBoxName: ( '', None, ' TestBoxes.sName DESC', '' ),
654 ksResultsSortByTestBoxOsArch: ( '', None, ' TestBoxes.sOs, TestBoxes.sCpuArch', '' ),
655 ksResultsSortByTestBoxOs: ( '', None, ' TestBoxes.sOs', '' ),
656 ksResultsSortByTestBoxOsVersion: ( '', None, ' TestBoxes.sOs, TestBoxes.sOsVersion DESC', '' ),
657 ksResultsSortByTestBoxArch: ( '', None, ' TestBoxes.sCpuArch', '' ),
658 ksResultsSortByTestBoxCpuVendor: ( '', None, ' TestBoxes.sCpuVendor', '' ),
659 ksResultsSortByTestBoxCpuName: ( '', None, ' TestBoxes.sCpuVendor, TestBoxes.sCpuName', '' ),
660 ksResultsSortByTestBoxCpuRev: (
661 '', None, ' TestBoxes.sCpuVendor, TestBoxes.lCpuRevision DESC', ', TestBoxes.lCpuRevision' ),
662 ksResultsSortByTestBoxCpuFeatures: (
663 ' TestBoxes.fCpuHwVirt DESC, TestBoxes.fCpuNestedPaging DESC, TestBoxes.fCpu64BitGuest DESC, '
664 + 'TestBoxes.cCpus DESC',
665 ', TestBoxes.cCpus' ), }
666 ),
667 };
668
669
670 def _getTimePeriodQueryPart(self, tsNow, sInterval):
671 """
672 Get part of SQL query responsible for SELECT data within
673 specified period of time.
674 """
675 assert sInterval is not None; # too many rows.
676
677 cMonthsMourningPeriod = 2; # Stop reminding everyone about testboxes after 2 months. (May also speed up the query.)
678 if tsNow is None:
679 sRet = '(TestSets.tsDone IS NULL OR TestSets.tsDone >= (CURRENT_TIMESTAMP - \'%s\'::interval))\n' \
680 ' AND TestSets.tsCreated >= (CURRENT_TIMESTAMP - \'%s\'::interval - \'%u months\'::interval)\n' \
681 % (sInterval, sInterval, cMonthsMourningPeriod);
682 else:
683 sTsNow = '\'%s\'::TIMESTAMP' % (tsNow,); # It's actually a string already. duh.
684 sRet = 'TestSets.tsCreated <= %s\n' \
685 ' AND TestSets.tsCreated >= (%s - \'%s\'::interval - \'%u months\'::interval)\n' \
686 ' AND (TestSets.tsDone IS NULL OR TestSets.tsDone >= (%s - \'%s\'::interval))\n' \
687 % ( sTsNow,
688 sTsNow, sInterval, cMonthsMourningPeriod,
689 sTsNow, sInterval );
690 return sRet
691
692 def fetchResultsForListing(self, iStart, cMaxRows, tsNow, sInterval, enmResultSortBy,
693 enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures):
694 """
695 Fetches TestResults table content.
696
697 If @param enmResultsGroupingType and @param iResultsGroupingValue
698 are not None, then resulting (returned) list contains only records
699 that match specified @param enmResultsGroupingType.
700
701 If @param enmResultsGroupingType is None, then
702 @param iResultsGroupingValue is ignored.
703
704 Returns an array (list) of TestResultData items, empty list if none.
705 Raises exception on error.
706 """
707
708 #
709 # Get SQL query parameters
710 #
711 if enmResultsGroupingType is None or enmResultsGroupingType not in self.kdResultGroupingMap:
712 raise TMExceptionBase('Unknown grouping type');
713 if enmResultSortBy is None or enmResultSortBy not in self.kasResultsSortBy:
714 raise TMExceptionBase('Unknown sorting');
715 sGroupingTables, sGroupingField, sGroupingCondition, dSortingOverrides = self.kdResultGroupingMap[enmResultsGroupingType];
716 if enmResultSortBy in dSortingOverrides:
717 sSortingTables, sSortingWhere, sSortingOrderBy, sSortingColumns = dSortingOverrides[enmResultSortBy];
718 else:
719 sSortingTables, sSortingWhere, sSortingOrderBy, sSortingColumns = self.kdResultSortByMap[enmResultSortBy];
720
721 #
722 # Construct the query.
723 #
724 sQuery = 'SELECT DISTINCT TestSets.idTestSet,\n' \
725 ' BuildCategories.idBuildCategory,\n' \
726 ' BuildCategories.sProduct,\n' \
727 ' BuildCategories.sRepository,\n' \
728 ' BuildCategories.sBranch,\n' \
729 ' BuildCategories.sType,\n' \
730 ' Builds.idBuild,\n' \
731 ' Builds.sVersion,\n' \
732 ' Builds.iRevision,\n' \
733 ' TestBoxes.sOs,\n' \
734 ' TestBoxes.sOsVersion,\n' \
735 ' TestBoxes.sCpuArch,\n' \
736 ' TestBoxes.sCpuVendor,\n' \
737 ' TestBoxes.sCpuName,\n' \
738 ' TestBoxes.cCpus,\n' \
739 ' TestBoxes.fCpuHwVirt,\n' \
740 ' TestBoxes.fCpuNestedPaging,\n' \
741 ' TestBoxes.fCpu64BitGuest,\n' \
742 ' TestBoxes.idTestBox,\n' \
743 ' TestBoxes.sName,\n' \
744 ' TestResults.tsCreated,\n' \
745 ' COALESCE(TestResults.tsElapsed, CURRENT_TIMESTAMP - TestResults.tsCreated),\n' \
746 ' TestSets.enmStatus,\n' \
747 ' TestResults.cErrors,\n' \
748 ' TestCases.idTestCase,\n' \
749 ' TestCases.sName,\n' \
750 ' TestCases.sBaseCmd,\n' \
751 ' TestCaseArgs.sArgs,\n' \
752 ' TestSuiteBits.idBuild AS idBuildTestSuite,\n' \
753 ' TestSuiteBits.iRevision AS iRevisionTestSuite,\n' \
754 ' (TestSets.tsDone IS NULL) SortRunningFirst' + sSortingColumns + '\n' \
755 'FROM BuildCategories,\n' \
756 ' Builds,\n' \
757 ' TestBoxes,\n' \
758 ' TestResults,\n' \
759 ' TestCases,\n' \
760 ' TestCaseArgs,\n' \
761 ' ( SELECT TestSets.idTestSet AS idTestSet,\n' \
762 ' TestSets.tsDone AS tsDone,\n' \
763 ' TestSets.tsCreated AS tsCreated,\n' \
764 ' TestSets.enmStatus AS enmStatus,\n' \
765 ' TestSets.idBuild AS idBuild,\n' \
766 ' TestSets.idBuildTestSuite AS idBuildTestSuite,\n' \
767 ' TestSets.idGenTestBox AS idGenTestBox,\n' \
768 ' TestSets.idGenTestCase AS idGenTestCase,\n' \
769 ' TestSets.idGenTestCaseArgs AS idGenTestCaseArgs\n' \
770 ' FROM ' + sGroupingTables + sSortingTables + '\n' \
771 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval);
772 if fOnlyFailures:
773 sQuery += ' AND TestSets.enmStatus != \'success\'::TestStatus_T' \
774 ' AND TestSets.enmStatus != \'running\'::TestStatus_T';
775 if sGroupingField is not None:
776 sQuery += ' AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,);
777 if sGroupingCondition is not None:
778 sQuery += sGroupingCondition.replace(' AND ', ' AND ');
779 if sSortingWhere is not None:
780 sQuery += sSortingWhere.replace(' AND ', ' AND ');
781 sQuery += ' ORDER BY ';
782 if sSortingOrderBy is not None:
783 sQuery += sSortingOrderBy + ',\n ';
784 sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n' \
785 ' LIMIT %s OFFSET %s\n' % (cMaxRows, iStart,);
786
787 sQuery += ' ) AS TestSets\n' \
788 ' LEFT OUTER JOIN Builds AS TestSuiteBits\n' \
789 ' ON TestSets.idBuildTestSuite = TestSuiteBits.idBuild\n' \
790 'WHERE TestSets.idTestSet = TestResults.idTestSet\n' \
791 ' AND TestResults.idTestResultParent is NULL\n' \
792 ' AND TestSets.idBuild = Builds.idBuild\n' \
793 ' AND Builds.tsExpire > TestSets.tsCreated\n' \
794 ' AND Builds.tsEffective <= TestSets.tsCreated\n' \
795 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' \
796 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox\n' \
797 ' AND TestSets.idGenTestCase = TestCases.idGenTestCase\n' \
798 ' AND TestSets.idGenTestCaseArgs = TestCaseArgs.idGenTestCaseArgs\n' \
799 'ORDER BY ';
800 if sSortingOrderBy is not None:
801 sQuery += sSortingOrderBy + ',\n ';
802 sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n';
803
804 #
805 # Execute the query and return the wrapped results.
806 #
807 self._oDb.execute(sQuery);
808
809 aoRows = [];
810 for aoRow in self._oDb.fetchAll():
811 aoRows.append(TestResultListingData().initFromDbRow(aoRow))
812
813 return aoRows
814
815 def getEntriesCount(self, tsNow, sInterval, enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures):
816 """
817 Get number of table records.
818
819 If @param enmResultsGroupingType and @param iResultsGroupingValue
820 are not None, then we count only only those records
821 that match specified @param enmResultsGroupingType.
822
823 If @param enmResultsGroupingType is None, then
824 @param iResultsGroupingValue is ignored.
825 """
826
827 #
828 # Get SQL query parameters
829 #
830 if enmResultsGroupingType is None:
831 raise TMExceptionBase('Unknown grouping type')
832
833 if enmResultsGroupingType not in self.kdResultGroupingMap:
834 raise TMExceptionBase('Unknown grouping type')
835 sGroupingTables, sGroupingField, sGroupingCondition, _ = self.kdResultGroupingMap[enmResultsGroupingType];
836
837 #
838 # Construct the query.
839 #
840 sQuery = 'SELECT COUNT(idTestSet)\n' \
841 'FROM ' + sGroupingTables + '\n' \
842 'WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval);
843 if fOnlyFailures:
844 sQuery += ' AND TestSets.enmStatus != \'success\'::TestStatus_T' \
845 ' AND TestSets.enmStatus != \'running\'::TestStatus_T';
846 if sGroupingField is not None:
847 sQuery += ' AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,);
848 if sGroupingCondition is not None:
849 sQuery += sGroupingCondition.replace(' AND ', ' AND ');
850
851 #
852 # Execute the query and return the result.
853 #
854 self._oDb.execute(sQuery)
855 return self._oDb.fetchOne()[0]
856
857 def getTestGroups(self, tsNow, sPeriod):
858 """
859 Get list of uniq TestGroupData objects which
860 found in all test results.
861 """
862
863 self._oDb.execute('SELECT DISTINCT TestGroups.*\n'
864 'FROM TestGroups, TestSets\n'
865 'WHERE TestSets.idTestGroup = TestGroups.idTestGroup\n'
866 ' AND TestGroups.tsExpire > TestSets.tsCreated\n'
867 ' AND TestGroups.tsEffective <= TestSets.tsCreated'
868 ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
869
870 aaoRows = self._oDb.fetchAll()
871 aoRet = []
872 for aoRow in aaoRows:
873 ## @todo Need to take time into consideration. Will go belly up if we delete a testgroup.
874 aoRet.append(TestGroupData().initFromDbRow(aoRow))
875
876 return aoRet
877
878 def getBuilds(self, tsNow, sPeriod):
879 """
880 Get list of uniq BuildDataEx objects which
881 found in all test results.
882 """
883
884 self._oDb.execute('SELECT DISTINCT Builds.*, BuildCategories.*\n'
885 'FROM Builds, BuildCategories, TestSets\n'
886 'WHERE TestSets.idBuild = Builds.idBuild\n'
887 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
888 ' AND Builds.tsExpire > TestSets.tsCreated\n'
889 ' AND Builds.tsEffective <= TestSets.tsCreated'
890 ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
891
892 aaoRows = self._oDb.fetchAll()
893 aoRet = []
894 for aoRow in aaoRows:
895 aoRet.append(BuildDataEx().initFromDbRow(aoRow))
896
897 return aoRet
898
899 def getTestBoxes(self, tsNow, sPeriod):
900 """
901 Get list of uniq TestBoxData objects which
902 found in all test results.
903 """
904
905 ## @todo do all in one query.
906 self._oDb.execute('SELECT DISTINCT TestBoxes.idTestBox, TestBoxes.idGenTestBox\n'
907 'FROM TestBoxes, TestSets\n'
908 'WHERE TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
909 ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod) +
910 'ORDER BY TestBoxes.idTestBox, TestBoxes.idGenTestBox DESC' );
911 idPrevTestBox = -1;
912 asIdGenTestBoxes = [];
913 for aoRow in self._oDb.fetchAll():
914 if aoRow[0] != idPrevTestBox:
915 idPrevTestBox = aoRow[0];
916 asIdGenTestBoxes.append(str(aoRow[1]));
917
918 aoRet = []
919 if len(asIdGenTestBoxes) > 0:
920 self._oDb.execute('SELECT *\n'
921 'FROM TestBoxes\n'
922 'WHERE idGenTestBox IN (' + ','.join(asIdGenTestBoxes) + ')\n'
923 'ORDER BY sName');
924 for aoRow in self._oDb.fetchAll():
925 aoRet.append(TestBoxData().initFromDbRow(aoRow));
926 return aoRet
927
928 def getTestCases(self, tsNow, sPeriod):
929 """
930 Get a list of unique TestCaseData objects which is appears in the test
931 specified result period.
932 """
933
934 self._oDb.execute('SELECT DISTINCT TestCases.idTestCase, TestCases.idGenTestCase, TestSets.tsConfig\n'
935 'FROM TestCases, TestSets\n'
936 'WHERE TestSets.idTestCase = TestCases.idTestCase\n'
937 ' AND TestCases.tsExpire > TestSets.tsCreated\n'
938 ' AND TestCases.tsEffective <= TestSets.tsCreated\n'
939 ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod) +
940 'ORDER BY TestCases.idTestCase, TestCases.idGenTestCase DESC\n');
941
942 aaoRows = self._oDb.fetchAll()
943 aoRet = []
944 idPrevTestCase = -1;
945 for aoRow in aaoRows:
946 ## @todo reduce subqueries
947 if aoRow[0] != idPrevTestCase:
948 idPrevTestCase = aoRow[0];
949 aoRet.append(TestCaseData().initFromDbWithGenId(self._oDb, aoRow[1], aoRow[2]))
950
951 return aoRet
952
953 def getSchedGroups(self, tsNow, sPeriod):
954 """
955 Get list of uniq SchedGroupData objects which
956 found in all test results.
957 """
958
959 self._oDb.execute('SELECT DISTINCT TestBoxes.idSchedGroup\n'
960 'FROM TestBoxes, TestSets\n'
961 'WHERE TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
962 ' AND TestBoxes.tsExpire > TestSets.tsCreated\n'
963 ' AND TestBoxes.tsEffective <= TestSets.tsCreated'
964 ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
965
966 aiRows = self._oDb.fetchAll()
967 aoRet = []
968 for iRow in aiRows:
969 ## @todo reduce subqueries
970 aoRet.append(SchedGroupData().initFromDbWithId(self._oDb, iRow))
971
972 return aoRet
973
974 def getById(self, idTestResult):
975 """
976 Get build record by its id
977 """
978 self._oDb.execute('SELECT *\n'
979 'FROM TestResults\n'
980 'WHERE idTestResult = %s\n',
981 (idTestResult,))
982
983 aRows = self._oDb.fetchAll()
984 if len(aRows) not in (0, 1):
985 raise TMExceptionBase('Found more than one test result with the same credentials. Database structure is corrupted.')
986 try:
987 return TestResultData().initFromDbRow(aRows[0])
988 except IndexError:
989 return None
990
991
992 #
993 # Details view and interface.
994 #
995
996 def fetchResultTree(self, idTestSet, cMaxDepth = None):
997 """
998 Fetches the result tree for the given test set.
999
1000 Returns a tree of TestResultDataEx nodes.
1001 Raises exception on invalid input and database issues.
1002 """
1003 # Depth first, i.e. just like the XML added them.
1004 ## @todo this still isn't performing extremely well, consider optimizations.
1005 sQuery = self._oDb.formatBindArgs(
1006 'SELECT TestResults.*,\n'
1007 ' TestResultStrTab.sValue,\n'
1008 ' EXISTS ( SELECT idTestResultValue\n'
1009 ' FROM TestResultValues\n'
1010 ' WHERE TestResultValues.idTestResult = TestResults.idTestResult ) AS fHasValues,\n'
1011 ' EXISTS ( SELECT idTestResultMsg\n'
1012 ' FROM TestResultMsgs\n'
1013 ' WHERE TestResultMsgs.idTestResult = TestResults.idTestResult ) AS fHasMsgs,\n'
1014 ' EXISTS ( SELECT idTestResultFile\n'
1015 ' FROM TestResultFiles\n'
1016 ' WHERE TestResultFiles.idTestResult = TestResults.idTestResult ) AS fHasFiles\n'
1017 'FROM TestResults, TestResultStrTab\n'
1018 'WHERE TestResults.idTestSet = %s\n'
1019 ' AND TestResults.idStrName = TestResultStrTab.idStr\n'
1020 , ( idTestSet, ));
1021 if cMaxDepth is not None:
1022 sQuery += self._oDb.formatBindArgs(' AND TestResults.iNestingDepth <= %s\n', (cMaxDepth,));
1023 sQuery += 'ORDER BY idTestResult ASC\n'
1024
1025 self._oDb.execute(sQuery);
1026 cRows = self._oDb.getRowCount();
1027 if cRows > 65536:
1028 raise TMTooManyRows('Too many rows returned for idTestSet=%d: %d' % (idTestSet, cRows,));
1029
1030 aaoRows = self._oDb.fetchAll();
1031 if len(aaoRows) == 0:
1032 raise TMExceptionBase('No test results for idTestSet=%d.' % (idTestSet,));
1033
1034 # Set up the root node first.
1035 aoRow = aaoRows[0];
1036 oRoot = TestResultDataEx().initFromDbRow(aoRow);
1037 if oRoot.idTestResultParent is not None:
1038 raise self._oDb.integrityException('The root TestResult (#%s) has a parent (#%s)!'
1039 % (oRoot.idTestResult, oRoot.idTestResultParent));
1040 self._fetchResultTreeNodeExtras(oRoot, aoRow[-3], aoRow[-2], aoRow[-1]);
1041
1042 # The chilren (if any).
1043 dLookup = { oRoot.idTestResult: oRoot };
1044 oParent = oRoot;
1045 for iRow in range(1, len(aaoRows)):
1046 aoRow = aaoRows[iRow];
1047 oCur = TestResultDataEx().initFromDbRow(aoRow);
1048 self._fetchResultTreeNodeExtras(oCur, aoRow[-3], aoRow[-2], aoRow[-1]);
1049
1050 # Figure out and vet the parent.
1051 if oParent.idTestResult != oCur.idTestResultParent:
1052 oParent = dLookup.get(oCur.idTestResultParent, None);
1053 if oParent is None:
1054 raise self._oDb.integrityException('TestResult #%d is orphaned from its parent #%s.'
1055 % (oCur.idTestResult, oCur.idTestResultParent,));
1056 if oParent.iNestingDepth + 1 != oCur.iNestingDepth:
1057 raise self._oDb.integrityException('TestResult #%d has incorrect nesting depth (%d instead of %d)'
1058 % (oCur.idTestResult, oCur.iNestingDepth, oParent.iNestingDepth + 1,));
1059
1060 # Link it up.
1061 oCur.oParent = oParent;
1062 oParent.aoChildren.append(oCur);
1063 dLookup[oCur.idTestResult] = oCur;
1064
1065 return (oRoot, dLookup);
1066
1067 def _fetchResultTreeNodeExtras(self, oCurNode, fHasValues, fHasMsgs, fHasFiles):
1068 """
1069 fetchResultTree worker that fetches values, message and files for the
1070 specified node.
1071 """
1072 assert(oCurNode.aoValues == []);
1073 assert(oCurNode.aoMsgs == []);
1074 assert(oCurNode.aoFiles == []);
1075
1076 if fHasValues:
1077 self._oDb.execute('SELECT TestResultValues.*,\n'
1078 ' TestResultStrTab.sValue\n'
1079 'FROM TestResultValues, TestResultStrTab\n'
1080 'WHERE TestResultValues.idTestResult = %s\n'
1081 ' AND TestResultValues.idStrName = TestResultStrTab.idStr\n'
1082 'ORDER BY idTestResultValue ASC\n'
1083 , ( oCurNode.idTestResult, ));
1084 for aoRow in self._oDb.fetchAll():
1085 oCurNode.aoValues.append(TestResultValueDataEx().initFromDbRow(aoRow));
1086
1087 if fHasMsgs:
1088 self._oDb.execute('SELECT TestResultMsgs.*,\n'
1089 ' TestResultStrTab.sValue\n'
1090 'FROM TestResultMsgs, TestResultStrTab\n'
1091 'WHERE TestResultMsgs.idTestResult = %s\n'
1092 ' AND TestResultMsgs.idStrMsg = TestResultStrTab.idStr\n'
1093 'ORDER BY idTestResultMsg ASC\n'
1094 , ( oCurNode.idTestResult, ));
1095 for aoRow in self._oDb.fetchAll():
1096 oCurNode.aoMsgs.append(TestResultMsgDataEx().initFromDbRow(aoRow));
1097
1098 if fHasFiles:
1099 self._oDb.execute('SELECT TestResultFiles.*,\n'
1100 ' StrTabFile.sValue AS sFile,\n'
1101 ' StrTabDesc.sValue AS sDescription,\n'
1102 ' StrTabKind.sValue AS sKind,\n'
1103 ' StrTabMime.sValue AS sMime\n'
1104 'FROM TestResultFiles,\n'
1105 ' TestResultStrTab AS StrTabFile,\n'
1106 ' TestResultStrTab AS StrTabDesc,\n'
1107 ' TestResultStrTab AS StrTabKind,\n'
1108 ' TestResultStrTab AS StrTabMime\n'
1109 'WHERE TestResultFiles.idTestResult = %s\n'
1110 ' AND TestResultFiles.idStrFile = StrTabFile.idStr\n'
1111 ' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n'
1112 ' AND TestResultFiles.idStrKind = StrTabKind.idStr\n'
1113 ' AND TestResultFiles.idStrMime = StrTabMime.idStr\n'
1114 'ORDER BY idTestResultFile ASC\n'
1115 , ( oCurNode.idTestResult, ));
1116 for aoRow in self._oDb.fetchAll():
1117 oCurNode.aoFiles.append(TestResultFileDataEx().initFromDbRow(aoRow));
1118
1119 return True;
1120
1121
1122
1123 #
1124 # TestBoxController interface(s).
1125 #
1126
1127 def _inhumeTestResults(self, aoStack, idTestSet, sError):
1128 """
1129 The test produces too much output, kill and bury it.
1130
1131 Note! We leave the test set open, only the test result records are
1132 completed. Thus, _getResultStack will return an empty stack and
1133 cause XML processing to fail immediately, while we can still
1134 record when it actually completed in the test set the normal way.
1135 """
1136 self._oDb.dprint('** _inhumeTestResults: idTestSet=%d\n%s' % (idTestSet, self._stringifyStack(aoStack),));
1137
1138 #
1139 # First add a message.
1140 #
1141 self._newFailureDetails(aoStack[0].idTestResult, sError, None);
1142
1143 #
1144 # The complete all open test results.
1145 #
1146 for oTestResult in aoStack:
1147 oTestResult.cErrors += 1;
1148 self._completeTestResults(oTestResult, None, TestResultData.ksTestStatus_Failure, oTestResult.cErrors);
1149
1150 # A bit of paranoia.
1151 self._oDb.execute('UPDATE TestResults\n'
1152 'SET cErrors = cErrors + 1,\n'
1153 ' enmStatus = \'failure\'::TestStatus_T,\n'
1154 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
1155 'WHERE idTestSet = %s\n'
1156 ' AND enmStatus = \'running\'::TestStatus_T\n'
1157 , ( idTestSet, ));
1158 self._oDb.commit();
1159
1160 return None;
1161
1162 def strTabString(self, sString, fCommit = False):
1163 """
1164 Gets the string table id for the given string, adding it if new.
1165
1166 Note! A copy of this code is also in TestSetLogic.
1167 """
1168 ## @todo move this and make a stored procedure for it.
1169 self._oDb.execute('SELECT idStr\n'
1170 'FROM TestResultStrTab\n'
1171 'WHERE sValue = %s'
1172 , (sString,));
1173 if self._oDb.getRowCount() == 0:
1174 self._oDb.execute('INSERT INTO TestResultStrTab (sValue)\n'
1175 'VALUES (%s)\n'
1176 'RETURNING idStr\n'
1177 , (sString,));
1178 if fCommit:
1179 self._oDb.commit();
1180 return self._oDb.fetchOne()[0];
1181
1182 @staticmethod
1183 def _stringifyStack(aoStack):
1184 """Returns a string rep of the stack."""
1185 sRet = '';
1186 for i in range(len(aoStack)):
1187 sRet += 'aoStack[%d]=%s\n' % (i, aoStack[i]);
1188 return sRet;
1189
1190 def _getResultStack(self, idTestSet):
1191 """
1192 Gets the current stack of result sets.
1193 """
1194 self._oDb.execute('SELECT *\n'
1195 'FROM TestResults\n'
1196 'WHERE idTestSet = %s\n'
1197 ' AND enmStatus = \'running\'::TestStatus_T\n'
1198 'ORDER BY idTestResult DESC'
1199 , ( idTestSet, ));
1200 aoStack = [];
1201 for aoRow in self._oDb.fetchAll():
1202 aoStack.append(TestResultData().initFromDbRow(aoRow));
1203
1204 for i in range(len(aoStack)):
1205 assert aoStack[i].iNestingDepth == len(aoStack) - i - 1, self._stringifyStack(aoStack);
1206
1207 return aoStack;
1208
1209 def _newTestResult(self, idTestResultParent, idTestSet, iNestingDepth, tsCreated, sName, dCounts, fCommit = False):
1210 """
1211 Creates a new test result.
1212 Returns the TestResultData object for the new record.
1213 May raise exception on database error.
1214 """
1215 assert idTestResultParent is not None;
1216 assert idTestResultParent > 1;
1217
1218 #
1219 # This isn't necessarily very efficient, but it's necessary to prevent
1220 # a wild test or testbox from filling up the database.
1221 #
1222 sCountName = 'cTestResults';
1223 if sCountName not in dCounts:
1224 self._oDb.execute('SELECT COUNT(idTestResult)\n'
1225 'FROM TestResults\n'
1226 'WHERE idTestSet = %s\n'
1227 , ( idTestSet,));
1228 dCounts[sCountName] = self._oDb.fetchOne()[0];
1229 dCounts[sCountName] += 1;
1230 if dCounts[sCountName] > config.g_kcMaxTestResultsPerTS:
1231 raise TestResultHangingOffence('Too many sub-tests in total!');
1232
1233 sCountName = 'cTestResultsIn%d' % (idTestResultParent,);
1234 if sCountName not in dCounts:
1235 self._oDb.execute('SELECT COUNT(idTestResult)\n'
1236 'FROM TestResults\n'
1237 'WHERE idTestResultParent = %s\n'
1238 , ( idTestResultParent,));
1239 dCounts[sCountName] = self._oDb.fetchOne()[0];
1240 dCounts[sCountName] += 1;
1241 if dCounts[sCountName] > config.g_kcMaxTestResultsPerTR:
1242 raise TestResultHangingOffence('Too many immediate sub-tests!');
1243
1244 # This is also a hanging offence.
1245 if iNestingDepth > config.g_kcMaxTestResultDepth:
1246 raise TestResultHangingOffence('To deep sub-test nesting!');
1247
1248 # Ditto.
1249 if len(sName) > config.g_kcchMaxTestResultName:
1250 raise TestResultHangingOffence('Test name is too long: %d chars - "%s"' % (len(sName), sName));
1251
1252 #
1253 # Within bounds, do the job.
1254 #
1255 idStrName = self.strTabString(sName, fCommit);
1256 self._oDb.execute('INSERT INTO TestResults (\n'
1257 ' idTestResultParent,\n'
1258 ' idTestSet,\n'
1259 ' tsCreated,\n'
1260 ' idStrName,\n'
1261 ' iNestingDepth )\n'
1262 'VALUES (%s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
1263 'RETURNING *\n'
1264 , ( idTestResultParent, idTestSet, tsCreated, idStrName, iNestingDepth) )
1265 oData = TestResultData().initFromDbRow(self._oDb.fetchOne());
1266
1267 self._oDb.maybeCommit(fCommit);
1268 return oData;
1269
1270 def _newTestValue(self, idTestResult, idTestSet, sName, lValue, sUnit, dCounts, tsCreated = None, fCommit = False):
1271 """
1272 Creates a test value.
1273 May raise exception on database error.
1274 """
1275
1276 #
1277 # Bounds checking.
1278 #
1279 sCountName = 'cTestValues';
1280 if sCountName not in dCounts:
1281 self._oDb.execute('SELECT COUNT(idTestResultValue)\n'
1282 'FROM TestResultValues, TestResults\n'
1283 'WHERE TestResultValues.idTestResult = TestResults.idTestResult\n'
1284 ' AND TestResults.idTestSet = %s\n'
1285 , ( idTestSet,));
1286 dCounts[sCountName] = self._oDb.fetchOne()[0];
1287 dCounts[sCountName] += 1;
1288 if dCounts[sCountName] > config.g_kcMaxTestValuesPerTS:
1289 raise TestResultHangingOffence('Too many values in total!');
1290
1291 sCountName = 'cTestValuesIn%d' % (idTestResult,);
1292 if sCountName not in dCounts:
1293 self._oDb.execute('SELECT COUNT(idTestResultValue)\n'
1294 'FROM TestResultValues\n'
1295 'WHERE idTestResult = %s\n'
1296 , ( idTestResult,));
1297 dCounts[sCountName] = self._oDb.fetchOne()[0];
1298 dCounts[sCountName] += 1;
1299 if dCounts[sCountName] > config.g_kcMaxTestValuesPerTR:
1300 raise TestResultHangingOffence('Too many immediate values for one test result!');
1301
1302 if len(sName) > config.g_kcchMaxTestValueName:
1303 raise TestResultHangingOffence('Value name is too long: %d chars - "%s"' % (len(sName), sName));
1304
1305 #
1306 # Do the job.
1307 #
1308 iUnit = constants.valueunit.g_kdNameToConst.get(sUnit, constants.valueunit.NONE);
1309
1310 idStrName = self.strTabString(sName, fCommit);
1311 if tsCreated is None:
1312 self._oDb.execute('INSERT INTO TestResultValues (\n'
1313 ' idTestResult,\n'
1314 ' idTestSet,\n'
1315 ' idStrName,\n'
1316 ' lValue,\n'
1317 ' iUnit)\n'
1318 'VALUES ( %s, %s, %s, %s, %s )\n'
1319 , ( idTestResult, idTestSet, idStrName, lValue, iUnit,) );
1320 else:
1321 self._oDb.execute('INSERT INTO TestResultValues (\n'
1322 ' idTestResult,\n'
1323 ' idTestSet,\n'
1324 ' tsCreated,\n'
1325 ' idStrName,\n'
1326 ' lValue,\n'
1327 ' iUnit)\n'
1328 'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s, %s )\n'
1329 , ( idTestResult, idTestSet, tsCreated, idStrName, lValue, iUnit,) );
1330 self._oDb.maybeCommit(fCommit);
1331 return True;
1332
1333 def _newFailureDetails(self, idTestResult, sText, dCounts, tsCreated = None, fCommit = False):
1334 """
1335 Creates a record detailing cause of failure.
1336 May raise exception on database error.
1337 """
1338
1339 #
1340 # Overflow protection.
1341 #
1342 if dCounts is not None:
1343 sCountName = 'cTestMsgsIn%d' % (idTestResult,);
1344 if sCountName not in dCounts:
1345 self._oDb.execute('SELECT COUNT(idTestResultMsg)\n'
1346 'FROM TestResultMsgs\n'
1347 'WHERE idTestResult = %s\n'
1348 , ( idTestResult,));
1349 dCounts[sCountName] = self._oDb.fetchOne()[0];
1350 dCounts[sCountName] += 1;
1351 if dCounts[sCountName] > config.g_kcMaxTestMsgsPerTR:
1352 raise TestResultHangingOffence('Too many messages under for one test result!');
1353
1354 if len(sText) > config.g_kcchMaxTestMsg:
1355 raise TestResultHangingOffence('Failure details message is too long: %d chars - "%s"' % (len(sText), sText));
1356
1357 #
1358 # Do the job.
1359 #
1360 idStrMsg = self.strTabString(sText, fCommit);
1361 if tsCreated is None:
1362 self._oDb.execute('INSERT INTO TestResultMsgs (\n'
1363 ' idTestResult,\n'
1364 ' idStrMsg,\n'
1365 ' enmLevel)\n'
1366 'VALUES ( %s, %s, %s)\n'
1367 , ( idTestResult, idStrMsg, 'failure',) );
1368 else:
1369 self._oDb.execute('INSERT INTO TestResultMsgs (\n'
1370 ' idTestResult,\n'
1371 ' tsCreated,\n'
1372 ' idStrMsg,\n'
1373 ' enmLevel)\n'
1374 'VALUES ( %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
1375 , ( idTestResult, tsCreated, idStrMsg, 'failure',) );
1376
1377 self._oDb.maybeCommit(fCommit);
1378 return True;
1379
1380
1381 def _completeTestResults(self, oTestResult, tsDone, enmStatus, cErrors = 0, fCommit = False):
1382 """
1383 Completes a test result. Updates the oTestResult object.
1384 May raise exception on database error.
1385 """
1386 self._oDb.dprint('** _completeTestResults: cErrors=%s tsDone=%s enmStatus=%s oTestResults=\n%s'
1387 % (cErrors, tsDone, enmStatus, oTestResult,));
1388
1389 #
1390 # Sanity check: No open sub tests (aoStack should make sure about this!).
1391 #
1392 self._oDb.execute('SELECT COUNT(idTestResult)\n'
1393 'FROM TestResults\n'
1394 'WHERE idTestResultParent = %s\n'
1395 ' AND enmStatus = %s\n'
1396 , ( oTestResult.idTestResult, TestResultData.ksTestStatus_Running,));
1397 cOpenSubTest = self._oDb.fetchOne()[0];
1398 assert cOpenSubTest == 0, 'cOpenSubTest=%d - %s' % (cOpenSubTest, oTestResult,);
1399 assert oTestResult.enmStatus == TestResultData.ksTestStatus_Running;
1400
1401 #
1402 # Make sure the reporter isn't lying about successes or error counts.
1403 #
1404 self._oDb.execute('SELECT COALESCE(SUM(cErrors), 0)\n'
1405 'FROM TestResults\n'
1406 'WHERE idTestResultParent = %s\n'
1407 , ( oTestResult.idTestResult, ));
1408 cMinErrors = self._oDb.fetchOne()[0] + oTestResult.cErrors;
1409 if cErrors < cMinErrors:
1410 cErrors = cMinErrors;
1411 if cErrors > 0 and enmStatus == TestResultData.ksTestStatus_Success:
1412 enmStatus = TestResultData.ksTestStatus_Failure
1413
1414 #
1415 # Do the update.
1416 #
1417 if tsDone is None:
1418 self._oDb.execute('UPDATE TestResults\n'
1419 'SET cErrors = %s,\n'
1420 ' enmStatus = %s,\n'
1421 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
1422 'WHERE idTestResult = %s\n'
1423 'RETURNING tsElapsed'
1424 , ( cErrors, enmStatus, oTestResult.idTestResult,) );
1425 else:
1426 self._oDb.execute('UPDATE TestResults\n'
1427 'SET cErrors = %s,\n'
1428 ' enmStatus = %s,\n'
1429 ' tsElapsed = TIMESTAMP WITH TIME ZONE %s - tsCreated\n'
1430 'WHERE idTestResult = %s\n'
1431 'RETURNING tsElapsed'
1432 , ( cErrors, enmStatus, tsDone, oTestResult.idTestResult,) );
1433
1434 oTestResult.tsElapsed = self._oDb.fetchOne()[0];
1435 oTestResult.enmStatus = enmStatus;
1436 oTestResult.cErrors = cErrors;
1437
1438 self._oDb.maybeCommit(fCommit);
1439 return None;
1440
1441 def _doPopHint(self, aoStack, cStackEntries, dCounts):
1442 """ Executes a PopHint. """
1443 assert cStackEntries >= 0;
1444 while len(aoStack) > cStackEntries:
1445 if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running:
1446 self._newFailureDetails(aoStack[0].idTestResult, 'XML error: Missing </Test>', dCounts);
1447 self._completeTestResults(aoStack[0], tsDone = None, cErrors = 1,
1448 enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True);
1449 aoStack.pop(0);
1450 return True;
1451
1452
1453 @staticmethod
1454 def _validateElement(sName, dAttribs, fClosed):
1455 """
1456 Validates an element and its attributes.
1457 """
1458
1459 #
1460 # Validate attributes by name.
1461 #
1462
1463 # Validate integer attributes.
1464 for sAttr in [ 'errors', 'testdepth' ]:
1465 if sAttr in dAttribs:
1466 try:
1467 _ = int(dAttribs[sAttr]);
1468 except:
1469 return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],);
1470
1471 # Validate long attributes.
1472 for sAttr in [ 'value', ]:
1473 if sAttr in dAttribs:
1474 try:
1475 _ = long(dAttribs[sAttr]);
1476 except:
1477 return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],);
1478
1479 # Validate string attributes.
1480 for sAttr in [ 'name', 'text' ]: # 'unit' can be zero length.
1481 if sAttr in dAttribs and len(dAttribs[sAttr]) == 0:
1482 return 'Element %s has an empty %s attribute value.' % (sName, sAttr,);
1483
1484 # Validate the timestamp attribute.
1485 if 'timestamp' in dAttribs:
1486 (dAttribs['timestamp'], sError) = ModelDataBase.validateTs(dAttribs['timestamp'], fAllowNull = False);
1487 if sError is not None:
1488 return 'Element %s has an invalid timestamp ("%s"): %s' % (sName, dAttribs['timestamp'], sError,);
1489
1490
1491 #
1492 # Check that attributes that are required are present.
1493 # We ignore extra attributes.
1494 #
1495 dElementAttribs = \
1496 {
1497 'Test': [ 'timestamp', 'name', ],
1498 'Value': [ 'timestamp', 'name', 'unit', 'value', ],
1499 'FailureDetails': [ 'timestamp', 'text', ],
1500 'Passed': [ 'timestamp', ],
1501 'Skipped': [ 'timestamp', ],
1502 'Failed': [ 'timestamp', 'errors', ],
1503 'TimedOut': [ 'timestamp', 'errors', ],
1504 'End': [ 'timestamp', ],
1505 'PushHint': [ 'testdepth', ],
1506 'PopHint': [ 'testdepth', ],
1507 };
1508 if sName not in dElementAttribs:
1509 return 'Unknown element "%s".' % (sName,);
1510 for sAttr in dElementAttribs[sName]:
1511 if sAttr not in dAttribs:
1512 return 'Element %s requires attribute "%s".' % (sName, sAttr);
1513
1514 #
1515 # Only the Test element can (and must) remain open.
1516 #
1517 if sName == 'Test' and fClosed:
1518 return '<Test/> is not allowed.';
1519 if sName != 'Test' and not fClosed:
1520 return 'All elements except <Test> must be closed.';
1521
1522 return None;
1523
1524 @staticmethod
1525 def _parseElement(sElement):
1526 """
1527 Parses an element.
1528
1529 """
1530 #
1531 # Element level bits.
1532 #
1533 sName = sElement.split()[0];
1534 sElement = sElement[len(sName):];
1535
1536 fClosed = sElement[-1] == '/';
1537 if fClosed:
1538 sElement = sElement[:-1];
1539
1540 #
1541 # Attributes.
1542 #
1543 sError = None;
1544 dAttribs = {};
1545 sElement = sElement.strip();
1546 while len(sElement) > 0:
1547 # Extract attribute name.
1548 off = sElement.find('=');
1549 if off < 0 or not sElement[:off].isalnum():
1550 sError = 'Attributes shall have alpha numberical names and have values.';
1551 break;
1552 sAttr = sElement[:off];
1553
1554 # Extract attribute value.
1555 if off + 2 >= len(sElement) or sElement[off + 1] != '"':
1556 sError = 'Attribute (%s) value is missing or not in double quotes.' % (sAttr,);
1557 break;
1558 off += 2;
1559 offEndQuote = sElement.find('"', off);
1560 if offEndQuote < 0:
1561 sError = 'Attribute (%s) value is missing end quotation mark.' % (sAttr,);
1562 break;
1563 sValue = sElement[off:offEndQuote];
1564
1565 # Check for duplicates.
1566 if sAttr in dAttribs:
1567 sError = 'Attribute "%s" appears more than once.' % (sAttr,);
1568 break;
1569
1570 # Unescape the value.
1571 sValue = sValue.replace('&lt;', '<');
1572 sValue = sValue.replace('&gt;', '>');
1573 sValue = sValue.replace('&apos;', '\'');
1574 sValue = sValue.replace('&quot;', '"');
1575 sValue = sValue.replace('&#xA;', '\n');
1576 sValue = sValue.replace('&#xD;', '\r');
1577 sValue = sValue.replace('&amp;', '&'); # last
1578
1579 # Done.
1580 dAttribs[sAttr] = sValue;
1581
1582 # advance
1583 sElement = sElement[offEndQuote + 1:];
1584 sElement = sElement.lstrip();
1585
1586 #
1587 # Validate the element before we return.
1588 #
1589 if sError is None:
1590 sError = TestResultLogic._validateElement(sName, dAttribs, fClosed);
1591
1592 return (sName, dAttribs, sError)
1593
1594 def _handleElement(self, sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts):
1595 """
1596 Worker for processXmlStream that handles one element.
1597
1598 Returns None on success, error string on bad XML or similar.
1599 Raises exception on hanging offence and on database error.
1600 """
1601 if sName == 'Test':
1602 iNestingDepth = aoStack[0].iNestingDepth + 1 if len(aoStack) > 0 else 0;
1603 aoStack.insert(0, self._newTestResult(idTestResultParent = aoStack[0].idTestResult, idTestSet = idTestSet,
1604 tsCreated = dAttribs['timestamp'], sName = dAttribs['name'],
1605 iNestingDepth = iNestingDepth, dCounts = dCounts, fCommit = True) );
1606
1607 elif sName == 'Value':
1608 self._newTestValue(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet, tsCreated = dAttribs['timestamp'],
1609 sName = dAttribs['name'], sUnit = dAttribs['unit'], lValue = long(dAttribs['value']),
1610 dCounts = dCounts, fCommit = True);
1611
1612 elif sName == 'FailureDetails':
1613 self._newFailureDetails(idTestResult = aoStack[0].idTestResult, tsCreated = dAttribs['timestamp'],
1614 sText = dAttribs['text'], dCounts = dCounts, fCommit = True);
1615
1616 elif sName == 'Passed':
1617 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
1618 enmStatus = TestResultData.ksTestStatus_Success, fCommit = True);
1619
1620 elif sName == 'Skipped':
1621 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
1622 enmStatus = TestResultData.ksTestStatus_Skipped, fCommit = True);
1623
1624 elif sName == 'Failed':
1625 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
1626 enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True);
1627
1628 elif sName == 'TimedOut':
1629 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
1630 enmStatus = TestResultData.ksTestStatus_TimedOut, fCommit = True);
1631
1632 elif sName == 'End':
1633 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
1634 cErrors = int(dAttribs.get('errors', '1')),
1635 enmStatus = TestResultData.ksTestStatus_Success, fCommit = True);
1636
1637 elif sName == 'PushHint':
1638 if len(aaiHints) > 1:
1639 return 'PushHint cannot be nested.'
1640
1641 aaiHints.insert(0, [len(aoStack), int(dAttribs['testdepth'])]);
1642
1643 elif sName == 'PopHint':
1644 if len(aaiHints) < 1:
1645 return 'No hint to pop.'
1646
1647 iDesiredTestDepth = int(dAttribs['testdepth']);
1648 cStackEntries, iTestDepth = aaiHints.pop(0);
1649 self._doPopHint(aoStack, cStackEntries, dCounts); # Fake the necessary '<End/></Test>' tags.
1650 if iDesiredTestDepth != iTestDepth:
1651 return 'PopHint tag has different testdepth: %d, on stack %d.' % (iDesiredTestDepth, iTestDepth);
1652 else:
1653 return 'Unexpected element "%s".' % (sName,);
1654 return None;
1655
1656
1657 def processXmlStream(self, sXml, idTestSet):
1658 """
1659 Processes the "XML" stream section given in sXml.
1660
1661 The sXml isn't a complete XML document, even should we save up all sXml
1662 for a given set, they may not form a complete and well formed XML
1663 document since the test may be aborted, abend or simply be buggy. We
1664 therefore do our own parsing and treat the XML tags as commands more
1665 than anything else.
1666
1667 Returns (sError, fUnforgivable), where sError is None on success.
1668 May raise database exception.
1669 """
1670 aoStack = self._getResultStack(idTestSet); # [0] == top; [-1] == bottom.
1671 if len(aoStack) == 0:
1672 return ('No open results', True);
1673 self._oDb.dprint('** processXmlStream len(aoStack)=%s' % (len(aoStack),));
1674 #self._oDb.dprint('processXmlStream: %s' % (self._stringifyStack(aoStack),));
1675 #self._oDb.dprint('processXmlStream: sXml=%s' % (sXml,));
1676
1677 dCounts = {};
1678 aaiHints = [];
1679 sError = None;
1680
1681 fExpectCloseTest = False;
1682 sXml = sXml.strip();
1683 while len(sXml) > 0:
1684 if sXml.startswith('</Test>'): # Only closing tag.
1685 offNext = len('</Test>');
1686 if len(aoStack) <= 1:
1687 sError = 'Trying to close the top test results.'
1688 break;
1689 # ASSUMES that we've just seen an <End/>, <Passed/>, <Failed/>,
1690 # <TimedOut/> or <Skipped/> tag earlier in this call!
1691 if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running or not fExpectCloseTest:
1692 sError = 'Missing <End/>, <Passed/>, <Failed/>, <TimedOut/> or <Skipped/> tag.';
1693 break;
1694 aoStack.pop(0);
1695 fExpectCloseTest = False;
1696
1697 elif fExpectCloseTest:
1698 sError = 'Expected </Test>.'
1699 break;
1700
1701 elif sXml.startswith('<?xml '): # Ignore (included files).
1702 offNext = sXml.find('?>');
1703 if offNext < 0:
1704 sError = 'Unterminated <?xml ?> element.';
1705 break;
1706 offNext += 2;
1707
1708 elif sXml[0] == '<':
1709 # Parse and check the tag.
1710 if not sXml[1].isalpha():
1711 sError = 'Malformed element.';
1712 break;
1713 offNext = sXml.find('>')
1714 if offNext < 0:
1715 sError = 'Unterminated element.';
1716 break;
1717 (sName, dAttribs, sError) = self._parseElement(sXml[1:offNext]);
1718 offNext += 1;
1719 if sError is not None:
1720 break;
1721
1722 # Handle it.
1723 try:
1724 sError = self._handleElement(sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts);
1725 except TestResultHangingOffence as oXcpt:
1726 self._inhumeTestResults(aoStack, idTestSet, str(oXcpt));
1727 return (str(oXcpt), True);
1728
1729
1730 fExpectCloseTest = sName in [ 'End', 'Passed', 'Failed', 'TimedOut', 'Skipped', ];
1731 else:
1732 sError = 'Unexpected content.';
1733 break;
1734
1735 # Advance.
1736 sXml = sXml[offNext:];
1737 sXml = sXml.lstrip();
1738
1739 #
1740 # Post processing checks.
1741 #
1742 if sError is None and fExpectCloseTest:
1743 sError = 'Expected </Test> before the end of the XML section.'
1744 elif sError is None and len(aaiHints) > 0:
1745 sError = 'Expected </PopHint> before the end of the XML section.'
1746 if len(aaiHints) > 0:
1747 self._doPopHint(aoStack, aaiHints[-1][0], dCounts);
1748
1749 #
1750 # Log the error.
1751 #
1752 if sError is not None:
1753 SystemLogLogic(self._oDb).addEntry(SystemLogData.ksEvent_XmlResultMalformed,
1754 'idTestSet=%s idTestResult=%s XML="%s" %s'
1755 % ( idTestSet,
1756 aoStack[0].idTestResult if len(aoStack) > 0 else -1,
1757 sXml[:30 if len(sXml) >= 30 else len(sXml)],
1758 sError, ),
1759 cHoursRepeat = 6, fCommit = True);
1760 return (sError, False);
1761
1762
1763#
1764# Unit testing.
1765#
1766
1767# pylint: disable=C0111
1768class TestResultDataTestCase(ModelDataBaseTestCase):
1769 def setUp(self):
1770 self.aoSamples = [TestResultData(),];
1771
1772class TestResultValueDataTestCase(ModelDataBaseTestCase):
1773 def setUp(self):
1774 self.aoSamples = [TestResultValueData(),];
1775
1776if __name__ == '__main__':
1777 unittest.main();
1778 # not reached.
1779
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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