VirtualBox

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

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

testmanager: Adding sComment and fRawMode fields to TestBoxes and moves the strings into a separate shared string table.

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