VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/testset.py@ 61424

Last change on this file since 61424 was 61424, checked in by vboxsync, 9 years ago

vsheriff: Some progress.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testset.py 61424 2016-06-03 02:22:30Z vboxsync $
3
4"""
5Test Manager - TestSet.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2015 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.215389.xyz. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 61424 $"
30
31
32# Standard python imports.
33import os;
34import zipfile;
35import unittest;
36
37# Validation Kit imports.
38from common import utils;
39from testmanager import config;
40from testmanager.core import db;
41from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, \
42 TMExceptionBase, TMTooManyRows, TMRowNotFound;
43from testmanager.core.testbox import TestBoxData;
44from testmanager.core.testresults import TestResultFileDataEx;
45
46
47class TestSetData(ModelDataBase):
48 """
49 TestSet Data.
50 """
51
52 ## @name TestStatus_T
53 # @{
54 ksTestStatus_Running = 'running';
55 ksTestStatus_Success = 'success';
56 ksTestStatus_Skipped = 'skipped';
57 ksTestStatus_BadTestBox = 'bad-testbox';
58 ksTestStatus_Aborted = 'aborted';
59 ksTestStatus_Failure = 'failure';
60 ksTestStatus_TimedOut = 'timed-out';
61 ksTestStatus_Rebooted = 'rebooted';
62 ## @}
63
64 ## List of relatively harmless (to testgroup/case) statuses.
65 kasHarmlessTestStatuses = [ ksTestStatus_Skipped, ksTestStatus_BadTestBox, ksTestStatus_Aborted, ];
66 ## List of bad statuses.
67 kasBadTestStatuses = [ ksTestStatus_Failure, ksTestStatus_TimedOut, ksTestStatus_Rebooted, ];
68
69 ksIdAttr = 'idTestSet';
70
71 ksParam_idTestSet = 'TestSet_idTestSet';
72 ksParam_tsConfig = 'TestSet_tsConfig';
73 ksParam_tsCreated = 'TestSet_tsCreated';
74 ksParam_tsDone = 'TestSet_tsDone';
75 ksParam_enmStatus = 'TestSet_enmStatus';
76 ksParam_idBuild = 'TestSet_idBuild';
77 ksParam_idBuildCategory = 'TestSet_idBuildCategory';
78 ksParam_idBuildTestSuite = 'TestSet_idBuildTestSuite';
79 ksParam_idGenTestBox = 'TestSet_idGenTestBox';
80 ksParam_idTestBox = 'TestSet_idTestBox';
81 ksParam_idTestGroup = 'TestSet_idTestGroup';
82 ksParam_idGenTestCase = 'TestSet_idGenTestCase';
83 ksParam_idTestCase = 'TestSet_idTestCase';
84 ksParam_idGenTestCaseArgs = 'TestSet_idGenTestCaseArgs';
85 ksParam_idTestCaseArgs = 'TestSet_idTestCaseArgs';
86 ksParam_idTestResult = 'TestSet_idTestResult';
87 ksParam_sBaseFilename = 'TestSet_sBaseFilename';
88 ksParam_iGangMemberNo = 'TestSet_iGangMemberNo';
89 ksParam_idTestSetGangLeader = 'TestSet_idTestSetGangLeader';
90
91 kasAllowNullAttributes = ['tsDone', 'idBuildTestSuite', 'idTestSetGangLeader' ];
92 kasValidValues_enmStatus = [
93 ksTestStatus_Running,
94 ksTestStatus_Success,
95 ksTestStatus_Skipped,
96 ksTestStatus_BadTestBox,
97 ksTestStatus_Aborted,
98 ksTestStatus_Failure,
99 ksTestStatus_TimedOut,
100 ksTestStatus_Rebooted,
101 ];
102 kiMin_iGangMemberNo = 0;
103 kiMax_iGangMemberNo = 1023;
104
105
106 def __init__(self):
107 ModelDataBase.__init__(self);
108
109 #
110 # Initialize with defaults.
111 # See the database for explanations of each of these fields.
112 #
113 self.idTestSet = None;
114 self.tsConfig = None;
115 self.tsCreated = None;
116 self.tsDone = None;
117 self.enmStatus = 'running';
118 self.idBuild = None;
119 self.idBuildCategory = None;
120 self.idBuildTestSuite = None;
121 self.idGenTestBox = None;
122 self.idTestBox = None;
123 self.idTestGroup = None;
124 self.idGenTestCase = None;
125 self.idTestCase = None;
126 self.idGenTestCaseArgs = None;
127 self.idTestCaseArgs = None;
128 self.idTestResult = None;
129 self.sBaseFilename = None;
130 self.iGangMemberNo = 0;
131 self.idTestSetGangLeader = None;
132
133 def initFromDbRow(self, aoRow):
134 """
135 Internal worker for initFromDbWithId and initFromDbWithGenId as well as
136 TestBoxSetLogic.
137 """
138
139 if aoRow is None:
140 raise TMRowNotFound('TestSet not found.');
141
142 self.idTestSet = aoRow[0];
143 self.tsConfig = aoRow[1];
144 self.tsCreated = aoRow[2];
145 self.tsDone = aoRow[3];
146 self.enmStatus = aoRow[4];
147 self.idBuild = aoRow[5];
148 self.idBuildCategory = aoRow[6];
149 self.idBuildTestSuite = aoRow[7];
150 self.idGenTestBox = aoRow[8];
151 self.idTestBox = aoRow[9];
152 self.idTestGroup = aoRow[10];
153 self.idGenTestCase = aoRow[11];
154 self.idTestCase = aoRow[12];
155 self.idGenTestCaseArgs = aoRow[13];
156 self.idTestCaseArgs = aoRow[14];
157 self.idTestResult = aoRow[15];
158 self.sBaseFilename = aoRow[16];
159 self.iGangMemberNo = aoRow[17];
160 self.idTestSetGangLeader = aoRow[18];
161 return self;
162
163
164 def initFromDbWithId(self, oDb, idTestSet):
165 """
166 Initialize the object from the database.
167 """
168 oDb.execute('SELECT *\n'
169 'FROM TestSets\n'
170 'WHERE idTestSet = %s\n'
171 , (idTestSet, ) );
172 aoRow = oDb.fetchOne()
173 if aoRow is None:
174 raise TMRowNotFound('idTestSet=%s not found' % (idTestSet,));
175 return self.initFromDbRow(aoRow);
176
177
178 def openFile(self, sFilename, sMode = 'rb'):
179 """
180 Opens a file.
181
182 Returns (oFile, cbFile, fIsStream) on success.
183 Returns (None, sErrorMsg, None) on failure.
184 Will not raise exceptions, unless the class instance is invalid.
185 """
186 assert sMode in [ 'rb', 'r', 'rU' ];
187
188 # Try raw file first.
189 sFile1 = os.path.join(config.g_ksFileAreaRootDir, self.sBaseFilename + '-' + sFilename);
190 try:
191 oFile = open(sFile1, sMode);
192 return (oFile, os.fstat(oFile.fileno()).st_size, False);
193 except Exception as oXcpt1:
194 # Try the zip archive next.
195 sFile2 = os.path.join(config.g_ksZipFileAreaRootDir, self.sBaseFilename + '.zip');
196 try:
197 oZipFile = zipfile.ZipFile(sFile2, 'r');
198 oFile = oZipFile.open(sFilename, sMode if sMode != 'rb' else 'r');
199 cbFile = oZipFile.getinfo(sFilename).file_size;
200 return (oFile, cbFile, True);
201 except Exception as oXcpt2:
202 # Construct a meaningful error message.
203 try:
204 if os.path.exists(sFile1):
205 return (None, 'Error opening "%s": %s' % (sFile1, oXcpt1), None);
206 if not os.path.exists(sFile2):
207 return (None, 'File "%s" not found. [%s, %s]' % (sFilename, sFile1, sFile2,), None);
208 return (None, 'Error opening "%s" inside "%s": %s' % (sFilename, sFile2, oXcpt2), None);
209 except Exception as oXcpt3:
210 return (None, 'OMG! %s; %s; %s' % (oXcpt1, oXcpt2, oXcpt3,), None);
211 return (None, 'Code not reachable!', None);
212
213 def createFile(self, sFilename, sMode = 'wb'):
214 """
215 Creates a new file.
216
217 Returns oFile on success.
218 Returns sErrorMsg on failure.
219 """
220 assert sMode in [ 'wb', 'w', 'wU' ];
221
222 # Try raw file first.
223 sFile1 = os.path.join(config.g_ksFileAreaRootDir, self.sBaseFilename + '-' + sFilename);
224 try:
225 if not os.path.exists(os.path.dirname(sFile1)):
226 os.makedirs(os.path.dirname(sFile1), 0o755);
227 oFile = open(sFile1, sMode);
228 except Exception as oXcpt1:
229 return str(oXcpt1);
230 return oFile;
231
232 @staticmethod
233 def findLogOffsetForTimestamp(sLogContent, tsTimestamp, offStart = 0, fAfter = False):
234 """
235 Log parsing utility function for finding the offset for the given timestamp.
236
237 We ASSUME the log lines are prefixed with UTC timestamps on the format
238 '09:43:55.789353'.
239
240 Return index into the sLogContent string, 0 if not found.
241 """
242 # Turn tsTimestamp into a string compatible with what we expect to find in the log.
243 oTsZulu = db.dbTimestampToZuluDatetime(tsTimestamp);
244 sWantedTs = oTsZulu.strftime('%H:%M:%S.%f');
245 assert len(sWantedTs) == 15;
246
247 # Now loop thru the string, line by line.
248 offRet = offStart;
249 off = offStart;
250 while True:
251 sThisTs = sLogContent[off : off + 15];
252 if len(sThisTs) >= 15 \
253 and sThisTs[2] == ':' \
254 and sThisTs[5] == ':' \
255 and sThisTs[8] == '.' \
256 and sThisTs[14] in '0123456789':
257 if sThisTs < sWantedTs:
258 offRet = off;
259 elif sThisTs == sWantedTs:
260 if not fAfter:
261 return off;
262 offRet = off;
263 else:
264 if fAfter:
265 offRet = off;
266 break;
267
268 # next line.
269 off = sLogContent.find('\n', off);
270 if off < 0:
271 if fAfter:
272 offRet = len(sLogContent);
273 break;
274 off += 1;
275
276 return offRet;
277
278 @staticmethod
279 def extractLogSection(sLogContent, tsStart, tsLast):
280 """
281 Returns log section from tsStart to tsLast (or all if we cannot make sense of it).
282 """
283 offStart = TestSetData.findLogOffsetForTimestamp(sLogContent, tsStart);
284 offEnd = TestSetData.findLogOffsetForTimestamp(sLogContent, tsLast, offStart, fAfter = True);
285 return sLogContent[offStart : offEnd];
286
287 @staticmethod
288 def extractLogSectionElapsed(sLogContent, tsStart, tsElapsed):
289 """
290 Returns log section from tsStart and tsElapsed forward (or all if we cannot make sense of it).
291 """
292 tsStart = db.dbTimestampToZuluDatetime(tsStart);
293 tsLast = tsStart + tsElapsed;
294 return TestSetData.extractLogSection(sLogContent, tsStart, tsLast);
295
296
297
298class TestSetLogic(ModelLogicBase):
299 """
300 TestSet logic.
301 """
302
303
304 def __init__(self, oDb):
305 ModelLogicBase.__init__(self, oDb);
306
307
308 def tryFetch(self, idTestSet):
309 """
310 Attempts to fetch a test set.
311
312 Returns a TestSetData object on success.
313 Returns None if no status was found.
314 Raises exception on other errors.
315 """
316 self._oDb.execute('SELECT *\n'
317 'FROM TestSets\n'
318 'WHERE idTestSet = %s\n',
319 (idTestSet,));
320 if self._oDb.getRowCount() == 0:
321 return None;
322 oData = TestSetData();
323 return oData.initFromDbRow(self._oDb.fetchOne());
324
325 def strTabString(self, sString, fCommit = False):
326 """
327 Gets the string table id for the given string, adding it if new.
328 """
329 ## @todo move this and make a stored procedure for it.
330 self._oDb.execute('SELECT idStr\n'
331 'FROM TestResultStrTab\n'
332 'WHERE sValue = %s'
333 , (sString,));
334 if self._oDb.getRowCount() == 0:
335 self._oDb.execute('INSERT INTO TestResultStrTab (sValue)\n'
336 'VALUES (%s)\n'
337 'RETURNING idStr\n'
338 , (sString,));
339 if fCommit:
340 self._oDb.commit();
341 return self._oDb.fetchOne()[0];
342
343 def complete(self, idTestSet, sStatus, fCommit = False):
344 """
345 Completes the testset.
346 Returns the test set ID of the gang leader, None if no gang involvement.
347 Raises exceptions on database errors and invalid input.
348 """
349
350 assert sStatus != TestSetData.ksTestStatus_Running;
351
352 #
353 # Get the basic test set data and check if there is anything to do here.
354 #
355 oData = TestSetData().initFromDbWithId(self._oDb, idTestSet);
356 if oData.enmStatus != TestSetData.ksTestStatus_Running:
357 raise TMExceptionBase('TestSet %s is already completed as %s.' % (idTestSet, oData.enmStatus));
358 if oData.idTestResult is None:
359 raise self._oDb.integrityException('idTestResult is NULL for TestSet %u' % (idTestSet,));
360
361 #
362 # Close open sub test results, count these as errors.
363 # Note! No need to propagate error counts here. Only one tree line will
364 # have open sets, and it will go all the way to the root.
365 #
366 self._oDb.execute('SELECT idTestResult\n'
367 'FROM TestResults\n'
368 'WHERE idTestSet = %s\n'
369 ' AND enmStatus = %s\n'
370 ' AND idTestResult <> %s\n'
371 'ORDER BY idTestResult DESC\n'
372 , (idTestSet, TestSetData.ksTestStatus_Running, oData.idTestResult));
373 aaoRows = self._oDb.fetchAll();
374 if len(aaoRows):
375 idStr = self.strTabString('Unclosed test result', fCommit = fCommit);
376 for aoRow in aaoRows:
377 self._oDb.execute('UPDATE TestResults\n'
378 'SET enmStatus = \'failure\',\n'
379 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n'
380 ' cErrors = cErrors + 1\n'
381 'WHERE idTestResult = %s\n'
382 , (aoRow[0],));
383 self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idStrMsg, enmLevel)\n'
384 'VALUES ( %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
385 , (aoRow[0], idStr,));
386
387 #
388 # If it's a success result, check it against error counters.
389 #
390 if sStatus not in TestSetData.kasBadTestStatuses:
391 self._oDb.execute('SELECT COUNT(*)\n'
392 'FROM TestResults\n'
393 'WHERE idTestSet = %s\n'
394 ' AND cErrors > 0\n'
395 , (idTestSet,));
396 cErrors = self._oDb.fetchOne()[0];
397 if cErrors > 0:
398 sStatus = TestSetData.ksTestStatus_Failure;
399
400 #
401 # If it's an pure 'failure', check for timeouts and propagate it.
402 #
403 if sStatus == TestSetData.ksTestStatus_Failure:
404 self._oDb.execute('SELECT COUNT(*)\n'
405 'FROM TestResults\n'
406 'WHERE idTestSet = %s\n'
407 ' AND enmStatus = %s\n'
408 , ( idTestSet, TestSetData.ksTestStatus_TimedOut, ));
409 if self._oDb.fetchOne()[0] > 0:
410 sStatus = TestSetData.ksTestStatus_TimedOut;
411
412 #
413 # Complete the top level test result and then the test set.
414 #
415 self._oDb.execute('UPDATE TestResults\n'
416 'SET cErrors = (SELECT COALESCE(SUM(cErrors), 0)\n'
417 ' FROM TestResults\n'
418 ' WHERE idTestResultParent = %s)\n'
419 'WHERE idTestResult = %s\n'
420 'RETURNING cErrors\n'
421 , (oData.idTestResult, oData.idTestResult));
422 cErrors = self._oDb.fetchOne()[0];
423 if cErrors == 0 and sStatus in TestSetData.kasBadTestStatuses:
424 self._oDb.execute('UPDATE TestResults\n'
425 'SET cErrors = 1\n'
426 'WHERE idTestResult = %s\n'
427 , (oData.idTestResult,));
428 elif cErrors > 0 and sStatus not in TestSetData.kasBadTestStatuses:
429 sStatus = TestSetData.ksTestStatus_Failure; # Impossible.
430 self._oDb.execute('UPDATE TestResults\n'
431 'SET enmStatus = %s,\n'
432 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
433 'WHERE idTestResult = %s\n'
434 , (sStatus, oData.idTestResult,));
435
436 self._oDb.execute('UPDATE TestSets\n'
437 'SET enmStatus = %s,\n'
438 ' tsDone = CURRENT_TIMESTAMP\n'
439 'WHERE idTestSet = %s\n'
440 , (sStatus, idTestSet,));
441
442 self._oDb.maybeCommit(fCommit);
443 return oData.idTestSetGangLeader;
444
445 def completeAsAbandond(self, idTestSet, fCommit = False):
446 """
447 Completes the testset as abandoned if necessary.
448
449 See scenario #9:
450 file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandond-testcase
451
452 Returns True if successfully completed as abandond, False if it's already
453 completed, and raises exceptions under exceptional circumstances.
454 """
455
456 #
457 # Get the basic test set data and check if there is anything to do here.
458 #
459 oData = self.tryFetch(idTestSet);
460 if oData is None:
461 return False;
462 if oData.enmStatus != TestSetData.ksTestStatus_Running:
463 return False;
464
465 if oData.idTestResult is not None:
466 #
467 # Clean up test results, adding a message why they failed.
468 #
469 self._oDb.execute('UPDATE TestResults\n'
470 'SET enmStatus = \'failure\',\n'
471 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n'
472 ' cErrors = cErrors + 1\n'
473 'WHERE idTestSet = %s\n'
474 ' AND enmStatus = \'running\'::TestStatus_T\n'
475 , (idTestSet,));
476
477 idStr = self.strTabString('The test was abandond by the testbox', fCommit = fCommit);
478 self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idStrMsg, enmLevel)\n'
479 'VALUES ( %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
480 , (oData.idTestResult, idStr,));
481
482 #
483 # Complete the testset.
484 #
485 self._oDb.execute('UPDATE TestSets\n'
486 'SET enmStatus = \'failure\',\n'
487 ' tsDone = CURRENT_TIMESTAMP\n'
488 'WHERE idTestSet = %s\n'
489 ' AND enmStatus = \'running\'::TestStatus_T\n'
490 , (idTestSet,));
491
492 self._oDb.maybeCommit(fCommit);
493 return True;
494
495 def completeAsGangGatheringTimeout(self, idTestSet, fCommit = False):
496 """
497 Completes the testset with a gang-gathering timeout.
498 Raises exceptions on database errors and invalid input.
499 """
500 #
501 # Get the basic test set data and check if there is anything to do here.
502 #
503 oData = TestSetData().initFromDbWithId(self._oDb, idTestSet);
504 if oData.enmStatus != TestSetData.ksTestStatus_Running:
505 raise TMExceptionBase('TestSet %s is already completed as %s.' % (idTestSet, oData.enmStatus));
506 if oData.idTestResult is None:
507 raise self._oDb.integrityException('idTestResult is NULL for TestSet %u' % (idTestSet,));
508
509 #
510 # Complete the top level test result and then the test set.
511 #
512 self._oDb.execute('UPDATE TestResults\n'
513 'SET enmStatus = \'failure\',\n'
514 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n'
515 ' cErrors = cErrors + 1\n'
516 'WHERE idTestSet = %s\n'
517 ' AND enmStatus = \'running\'::TestStatus_T\n'
518 , (idTestSet,));
519
520 idStr = self.strTabString('Gang gathering timed out', fCommit = fCommit);
521 self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idStrMsg, enmLevel)\n'
522 'VALUES ( %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
523 , (oData.idTestResult, idStr,));
524
525 self._oDb.execute('UPDATE TestSets\n'
526 'SET enmStatus = \'failure\',\n'
527 ' tsDone = CURRENT_TIMESTAMP\n'
528 'WHERE idTestSet = %s\n'
529 , (idTestSet,));
530
531 self._oDb.maybeCommit(fCommit);
532 return True;
533
534 def createFile(self, oTestSet, sName, sMime, sKind, sDesc, cbFile, fCommit = False): # pylint: disable=R0914
535 """
536 Creates a file and associating with the current test result record in
537 the test set.
538
539 Returns file object that the file content can be written to.
540 Raises exception on database error, I/O errors, if there are too many
541 files in the test set or if they take up too much disk space.
542
543 The caller (testboxdisp.py) is expected to do basic input validation,
544 so we skip that and get on with the bits only we can do.
545 """
546
547 #
548 # Furhter input and limit checks.
549 #
550 if oTestSet.enmStatus != TestSetData.ksTestStatus_Running:
551 raise TMExceptionBase('Cannot create files on a test set with status "%s".' % (oTestSet.enmStatus,));
552
553 self._oDb.execute('SELECT TestResultStrTab.sValue\n'
554 'FROM TestResultFiles,\n'
555 ' TestResults,\n'
556 ' TestResultStrTab\n'
557 'WHERE TestResults.idTestSet = %s\n'
558 ' AND TestResultFiles.idTestResult = TestResults.idTestResult\n'
559 ' AND TestResultStrTab.idStr = TestResultFiles.idStrFile\n'
560 , ( oTestSet.idTestSet,));
561 if self._oDb.getRowCount() + 1 > config.g_kcMaxUploads:
562 raise TMExceptionBase('Uploaded too many files already (%d).' % (self._oDb.getRowCount(),));
563
564 dFiles = {}
565 cbTotalFiles = 0;
566 for aoRow in self._oDb.fetchAll():
567 dFiles[aoRow[0].lower()] = 1; # For determining a unique filename further down.
568 sFile = os.path.join(config.g_ksFileAreaRootDir, oTestSet.sBaseFilename + '-' + aoRow[0]);
569 try:
570 cbTotalFiles += os.path.getsize(sFile);
571 except:
572 cbTotalFiles += config.g_kcMbMaxUploadSingle * 1048576;
573 if (cbTotalFiles + cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadTotal:
574 raise TMExceptionBase('Will exceed total upload limit: %u bytes + %u bytes > %s MiB.' \
575 % (cbTotalFiles, cbFile, config.g_kcMbMaxUploadTotal));
576
577 #
578 # Create a new file.
579 #
580 self._oDb.execute('SELECT idTestResult\n'
581 'FROM TestResults\n'
582 'WHERE idTestSet = %s\n'
583 ' AND enmStatus = \'running\'::TestStatus_T\n'
584 'ORDER BY idTestResult DESC\n'
585 'LIMIT 1\n'
586 % ( oTestSet.idTestSet, ));
587 if self._oDb.getRowCount() < 1:
588 raise TMExceptionBase('No open test results - someone committed a capital offence or we ran into a race.');
589 idTestResult = self._oDb.fetchOne()[0];
590
591 if sName.lower() in dFiles:
592 # Note! There is in theory a race here, but that's something the
593 # test driver doing parallel upload with non-unique names
594 # should worry about. The TD should always avoid this path.
595 sOrgName = sName;
596 for i in range(2, config.g_kcMaxUploads + 6):
597 sName = '%s-%s' % (i, sName,);
598 if sName not in dFiles:
599 break;
600 sName = None;
601 if sName is None:
602 raise TMExceptionBase('Failed to find unique name for %s.' % (sOrgName,));
603
604 self._oDb.execute('INSERT INTO TestResultFiles(idTestResult, idStrFile, idStrDescription, idStrKind, idStrMime)\n'
605 'VALUES (%s, %s, %s, %s, %s)\n'
606 , ( idTestResult,
607 self.strTabString(sName),
608 self.strTabString(sDesc),
609 self.strTabString(sKind),
610 self.strTabString(sMime),
611 ));
612
613 oFile = oTestSet.createFile(sName, 'wb');
614 if utils.isString(oFile):
615 raise TMExceptionBase('Error creating "%s": %s' % (sName, oFile));
616 self._oDb.maybeCommit(fCommit);
617 return oFile;
618
619 def getGang(self, idTestSetGangLeader):
620 """
621 Returns an array of TestBoxData object representing the gang for the given testset.
622 """
623 self._oDb.execute('SELECT TestBoxes.*\n'
624 'FROM TestBoxes, TestSets\n'
625 'WHERE TestSets.idTestSetGangLeader = %s\n'
626 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
627 'ORDER BY iGangMemberNo ASC\n'
628 , (idTestSetGangLeader,));
629 aaoRows = self._oDb.fetchAll();
630 aoTestBoxes = [];
631 for aoRow in aaoRows:
632 aoTestBoxes.append(TestBoxData().initFromDbRow(aoRow));
633 return aoTestBoxes;
634
635 def getFile(self, idTestSet, idTestResultFile):
636 """
637 Gets the TestResultFileEx corresponding to idTestResultFile.
638
639 Raises an exception if the file wasn't found, doesn't belong to
640 idTestSet, and on DB error.
641 """
642 self._oDb.execute('SELECT TestResultFiles.*,\n'
643 ' StrTabFile.sValue AS sFile,\n'
644 ' StrTabDesc.sValue AS sDescription,\n'
645 ' StrTabKind.sValue AS sKind,\n'
646 ' StrTabMime.sValue AS sMime\n'
647 'FROM TestResultFiles,\n'
648 ' TestResultStrTab AS StrTabFile,\n'
649 ' TestResultStrTab AS StrTabDesc,\n'
650 ' TestResultStrTab AS StrTabKind,\n'
651 ' TestResultStrTab AS StrTabMime,\n'
652 ' TestResults\n'
653 'WHERE TestResultFiles.idTestResultFile = %s\n'
654 ' AND TestResultFiles.idStrFile = StrTabFile.idStr\n'
655 ' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n'
656 ' AND TestResultFiles.idStrKind = StrTabKind.idStr\n'
657 ' AND TestResultFiles.idStrMime = StrTabMime.idStr\n'
658 ' AND TestResults.idTestResult = TestResultFiles.idTestResult\n'
659 ' AND TestResults.idTestSet = %s\n'
660 , ( idTestResultFile, idTestSet, ));
661 return TestResultFileDataEx().initFromDbRow(self._oDb.fetchOne());
662
663
664 def getById(self, idTestSet):
665 """
666 Get TestSet table record by its id
667 """
668 self._oDb.execute('SELECT *\n'
669 'FROM TestSets\n'
670 'WHERE idTestSet=%s\n',
671 (idTestSet,))
672
673 aRows = self._oDb.fetchAll()
674 if len(aRows) not in (0, 1):
675 raise TMTooManyRows('Found more than one test sets with the same credentials. Database structure is corrupted.')
676 try:
677 return TestSetData().initFromDbRow(aRows[0])
678 except IndexError:
679 return None
680
681
682 def fetchOrphaned(self):
683 """
684 Returns a list of TestSetData objects of orphaned test sets.
685
686 A test set is orphaned if tsDone is NULL and the testbox has created
687 one or more newer testsets.
688 """
689
690 self._oDb.execute('SELECT TestSets.*\n'
691 'FROM TestSets,\n'
692 ' (SELECT idTestSet, idTestBox FROM TestSets WHERE tsDone is NULL) AS t\n'
693 'WHERE TestSets.idTestSet = t.idTestSet\n'
694 ' AND EXISTS(SELECT 1 FROM TestSets st\n'
695 ' WHERE st.idTestBox = t.idTestBox AND st.idTestSet > t.idTestSet)\n'
696 ' AND NOT EXISTS(SELECT 1 FROM TestBoxStatuses tbs\n'
697 ' WHERE tbs.idTestBox = t.idTestBox AND tbs.idTestSet = t.idTestSet)\n'
698 'ORDER by TestSets.idTestBox, TestSets.idTestSet'
699 );
700 aoRet = [];
701 for aoRow in self._oDb.fetchAll():
702 aoRet.append(TestSetData().initFromDbRow(aoRow));
703 return aoRet;
704
705
706 #
707 # The virtual test sheriff interface.
708 #
709
710 def fetchBadTestBoxIds(self, cHoursBack = 2, tsNow = None):
711 """
712 Fetches a list of test box IDs which returned bad-testbox statuses in the
713 given period (tsDone).
714 """
715 if tsNow is None:
716 tsNow = self._oDb.getCurrentTimestamp();
717 self._oDb.execute('SELECT DISTINCT idTestBox\n'
718 'FROM TestSets\n'
719 'WHERE TestSets.enmStatus = \'bad-testbox\'\n'
720 ' AND tsDone <= %s\n'
721 ' AND tsDone > (%s - interval \'%s hours\')\n'
722 , ( tsNow, tsNow, cHoursBack,));
723 return [aoRow[0] for aoRow in self._oDb.fetchAll()];
724
725 def fetchSetsForTestBox(self, idTestBox, cHoursBack = 2, tsNow = None):
726 """
727 Fetches the TestSet rows for idTestBox for the given period (tsDone), w/o running ones.
728
729 Returns list of TestSetData sorted by tsDone in descending order.
730 """
731 if tsNow is None:
732 tsNow = self._oDb.getCurrentTimestamp();
733 self._oDb.execute('SELECT *\n'
734 'FROM TestSets\n'
735 'WHERE TestSets.idTestBox = %s\n'
736 ' AND tsDone IS NOT NULL\n'
737 ' AND tsDone <= %s\n'
738 ' AND tsDone > (%s - interval \'%s hours\')\n'
739 'ORDER by tsDone DESC\n'
740 , ( idTestBox, tsNow, tsNow, cHoursBack,));
741 return self._dbRowsToModelDataList(TestSetData);
742
743 def fetchFailedSetsWithoutReason(self, cHoursBack = 2, tsNow = None):
744 """
745 Fetches the TestSet failure rows without any currently (CURRENT_TIMESTAMP
746 not tsNow) assigned failure reason.
747
748 Returns list of TestSetData sorted by tsDone in descending order.
749
750 Note! Includes bad-testbox sets too as it can be useful to analyze these
751 too even if we normally count them in the 'skipped' category.
752 """
753 if tsNow is None:
754 tsNow = self._oDb.getCurrentTimestamp();
755 self._oDb.execute('SELECT TestSets.*\n'
756 'FROM TestSets\n'
757 ' LEFT OUTER JOIN TestResultFailures\n'
758 ' ON TestResultFailures.idTestSet = TestSets.idTestSet\n'
759 ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n'
760 'WHERE TestSets.tsDone IS NOT NULL\n'
761 ' AND TestSets.enmStatus IN ( %s, %s, %s, %s )\n'
762 ' AND TestSets.tsDone <= %s\n'
763 ' AND TestSets.tsDone > (%s - interval \'%s hours\')\n'
764 ' AND TestResultFailures.idTestSet IS NULL\n'
765 'ORDER by tsDone DESC\n'
766 , ( TestSetData.ksTestStatus_Failure, TestSetData.ksTestStatus_TimedOut,
767 TestSetData.ksTestStatus_Rebooted, TestSetData.ksTestStatus_BadTestBox,
768 tsNow,
769 tsNow, cHoursBack,));
770 return self._dbRowsToModelDataList(TestSetData);
771
772
773
774#
775# Unit testing.
776#
777
778# pylint: disable=C0111
779class TestSetDataTestCase(ModelDataBaseTestCase):
780 def setUp(self):
781 self.aoSamples = [TestSetData(),];
782
783if __name__ == '__main__':
784 unittest.main();
785 # not reached.
786
Note: See TracBrowser for help on using the repository browser.

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