VirtualBox

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

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

testmanager: Adding idTestSet to TestResultFiles and TestResultMsgs. Also adding proper indexes to these two tables.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testset.py 61462 2016-06-04 01:21:11Z 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.virtualbox.org. 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: 61462 $"
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, idTestSet, idStrMsg, enmLevel)\n'
384 'VALUES ( %s, %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
385 , (aoRow[0], idTestSet, 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, idTestSet, idStrMsg, enmLevel)\n'
479 'VALUES ( %s, %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
480 , (oData.idTestResult, idTestSet, 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, idTestSet, idStrMsg, enmLevel)\n'
522 'VALUES ( %s, %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
523 , (oData.idTestResult, idTestSet, 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, idTestSet, idStrFile, idStrDescription,\n'
605 ' idStrKind, idStrMime)\n'
606 'VALUES (%s, %s, %s, %s, %s, %s)\n'
607 , ( idTestResult,
608 oTestSet.idTestSet,
609 self.strTabString(sName),
610 self.strTabString(sDesc),
611 self.strTabString(sKind),
612 self.strTabString(sMime),
613 ));
614
615 oFile = oTestSet.createFile(sName, 'wb');
616 if utils.isString(oFile):
617 raise TMExceptionBase('Error creating "%s": %s' % (sName, oFile));
618 self._oDb.maybeCommit(fCommit);
619 return oFile;
620
621 def getGang(self, idTestSetGangLeader):
622 """
623 Returns an array of TestBoxData object representing the gang for the given testset.
624 """
625 self._oDb.execute('SELECT TestBoxes.*\n'
626 'FROM TestBoxes, TestSets\n'
627 'WHERE TestSets.idTestSetGangLeader = %s\n'
628 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
629 'ORDER BY iGangMemberNo ASC\n'
630 , (idTestSetGangLeader,));
631 aaoRows = self._oDb.fetchAll();
632 aoTestBoxes = [];
633 for aoRow in aaoRows:
634 aoTestBoxes.append(TestBoxData().initFromDbRow(aoRow));
635 return aoTestBoxes;
636
637 def getFile(self, idTestSet, idTestResultFile):
638 """
639 Gets the TestResultFileEx corresponding to idTestResultFile.
640
641 Raises an exception if the file wasn't found, doesn't belong to
642 idTestSet, and on DB error.
643 """
644 self._oDb.execute('SELECT TestResultFiles.*,\n'
645 ' StrTabFile.sValue AS sFile,\n'
646 ' StrTabDesc.sValue AS sDescription,\n'
647 ' StrTabKind.sValue AS sKind,\n'
648 ' StrTabMime.sValue AS sMime\n'
649 'FROM TestResultFiles,\n'
650 ' TestResultStrTab AS StrTabFile,\n'
651 ' TestResultStrTab AS StrTabDesc,\n'
652 ' TestResultStrTab AS StrTabKind,\n'
653 ' TestResultStrTab AS StrTabMime,\n'
654 ' TestResults\n'
655 'WHERE TestResultFiles.idTestResultFile = %s\n'
656 ' AND TestResultFiles.idStrFile = StrTabFile.idStr\n'
657 ' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n'
658 ' AND TestResultFiles.idStrKind = StrTabKind.idStr\n'
659 ' AND TestResultFiles.idStrMime = StrTabMime.idStr\n'
660 ' AND TestResults.idTestResult = TestResultFiles.idTestResult\n'
661 ' AND TestResults.idTestSet = %s\n'
662 , ( idTestResultFile, idTestSet, ));
663 return TestResultFileDataEx().initFromDbRow(self._oDb.fetchOne());
664
665
666 def getById(self, idTestSet):
667 """
668 Get TestSet table record by its id
669 """
670 self._oDb.execute('SELECT *\n'
671 'FROM TestSets\n'
672 'WHERE idTestSet=%s\n',
673 (idTestSet,))
674
675 aRows = self._oDb.fetchAll()
676 if len(aRows) not in (0, 1):
677 raise TMTooManyRows('Found more than one test sets with the same credentials. Database structure is corrupted.')
678 try:
679 return TestSetData().initFromDbRow(aRows[0])
680 except IndexError:
681 return None
682
683
684 def fetchOrphaned(self):
685 """
686 Returns a list of TestSetData objects of orphaned test sets.
687
688 A test set is orphaned if tsDone is NULL and the testbox has created
689 one or more newer testsets.
690 """
691
692 self._oDb.execute('SELECT TestSets.*\n'
693 'FROM TestSets,\n'
694 ' (SELECT idTestSet, idTestBox FROM TestSets WHERE tsDone is NULL) AS t\n'
695 'WHERE TestSets.idTestSet = t.idTestSet\n'
696 ' AND EXISTS(SELECT 1 FROM TestSets st\n'
697 ' WHERE st.idTestBox = t.idTestBox AND st.idTestSet > t.idTestSet)\n'
698 ' AND NOT EXISTS(SELECT 1 FROM TestBoxStatuses tbs\n'
699 ' WHERE tbs.idTestBox = t.idTestBox AND tbs.idTestSet = t.idTestSet)\n'
700 'ORDER by TestSets.idTestBox, TestSets.idTestSet'
701 );
702 aoRet = [];
703 for aoRow in self._oDb.fetchAll():
704 aoRet.append(TestSetData().initFromDbRow(aoRow));
705 return aoRet;
706
707
708 #
709 # The virtual test sheriff interface.
710 #
711
712 def fetchBadTestBoxIds(self, cHoursBack = 2, tsNow = None):
713 """
714 Fetches a list of test box IDs which returned bad-testbox statuses in the
715 given period (tsDone).
716 """
717 if tsNow is None:
718 tsNow = self._oDb.getCurrentTimestamp();
719 self._oDb.execute('SELECT DISTINCT idTestBox\n'
720 'FROM TestSets\n'
721 'WHERE TestSets.enmStatus = \'bad-testbox\'\n'
722 ' AND tsDone <= %s\n'
723 ' AND tsDone > (%s - interval \'%s hours\')\n'
724 , ( tsNow, tsNow, cHoursBack,));
725 return [aoRow[0] for aoRow in self._oDb.fetchAll()];
726
727 def fetchSetsForTestBox(self, idTestBox, cHoursBack = 2, tsNow = None):
728 """
729 Fetches the TestSet rows for idTestBox for the given period (tsDone), w/o running ones.
730
731 Returns list of TestSetData sorted by tsDone in descending order.
732 """
733 if tsNow is None:
734 tsNow = self._oDb.getCurrentTimestamp();
735 self._oDb.execute('SELECT *\n'
736 'FROM TestSets\n'
737 'WHERE TestSets.idTestBox = %s\n'
738 ' AND tsDone IS NOT NULL\n'
739 ' AND tsDone <= %s\n'
740 ' AND tsDone > (%s - interval \'%s hours\')\n'
741 'ORDER by tsDone DESC\n'
742 , ( idTestBox, tsNow, tsNow, cHoursBack,));
743 return self._dbRowsToModelDataList(TestSetData);
744
745 def fetchFailedSetsWithoutReason(self, cHoursBack = 2, tsNow = None):
746 """
747 Fetches the TestSet failure rows without any currently (CURRENT_TIMESTAMP
748 not tsNow) assigned failure reason.
749
750 Returns list of TestSetData sorted by tsDone in descending order.
751
752 Note! Includes bad-testbox sets too as it can be useful to analyze these
753 too even if we normally count them in the 'skipped' category.
754 """
755 if tsNow is None:
756 tsNow = self._oDb.getCurrentTimestamp();
757 self._oDb.execute('SELECT TestSets.*\n'
758 'FROM TestSets\n'
759 ' LEFT OUTER JOIN TestResultFailures\n'
760 ' ON TestResultFailures.idTestSet = TestSets.idTestSet\n'
761 ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n'
762 'WHERE TestSets.tsDone IS NOT NULL\n'
763 ' AND TestSets.enmStatus IN ( %s, %s, %s, %s )\n'
764 ' AND TestSets.tsDone <= %s\n'
765 ' AND TestSets.tsDone > (%s - interval \'%s hours\')\n'
766 ' AND TestResultFailures.idTestSet IS NULL\n'
767 'ORDER by tsDone DESC\n'
768 , ( TestSetData.ksTestStatus_Failure, TestSetData.ksTestStatus_TimedOut,
769 TestSetData.ksTestStatus_Rebooted, TestSetData.ksTestStatus_BadTestBox,
770 tsNow,
771 tsNow, cHoursBack,));
772 return self._dbRowsToModelDataList(TestSetData);
773
774
775
776#
777# Unit testing.
778#
779
780# pylint: disable=C0111
781class TestSetDataTestCase(ModelDataBaseTestCase):
782 def setUp(self):
783 self.aoSamples = [TestSetData(),];
784
785if __name__ == '__main__':
786 unittest.main();
787 # not reached.
788
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