VirtualBox

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

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

virtual test sheriff: Kick-off and the bad-testbox rebooting and disabling. (completely untested)

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

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