VirtualBox

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

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

testmanager: failiure reason fixes, some exception throwing cleanups, delinting with pylint 1.5.5.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 27.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testset.py 61220 2016-05-27 01:16:02Z 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: 61220 $"
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
231class TestSetLogic(ModelLogicBase):
232 """
233 TestSet logic.
234 """
235
236
237 def __init__(self, oDb):
238 ModelLogicBase.__init__(self, oDb);
239
240
241 def tryFetch(self, idTestSet):
242 """
243 Attempts to fetch a test set.
244
245 Returns a TestSetData object on success.
246 Returns None if no status was found.
247 Raises exception on other errors.
248 """
249 self._oDb.execute('SELECT *\n'
250 'FROM TestSets\n'
251 'WHERE idTestSet = %s\n',
252 (idTestSet,));
253 if self._oDb.getRowCount() == 0:
254 return None;
255 oData = TestSetData();
256 return oData.initFromDbRow(self._oDb.fetchOne());
257
258 def strTabString(self, sString, fCommit = False):
259 """
260 Gets the string table id for the given string, adding it if new.
261 """
262 ## @todo move this and make a stored procedure for it.
263 self._oDb.execute('SELECT idStr\n'
264 'FROM TestResultStrTab\n'
265 'WHERE sValue = %s'
266 , (sString,));
267 if self._oDb.getRowCount() == 0:
268 self._oDb.execute('INSERT INTO TestResultStrTab (sValue)\n'
269 'VALUES (%s)\n'
270 'RETURNING idStr\n'
271 , (sString,));
272 if fCommit:
273 self._oDb.commit();
274 return self._oDb.fetchOne()[0];
275
276 def complete(self, idTestSet, sStatus, fCommit = False):
277 """
278 Completes the testset.
279 Returns the test set ID of the gang leader, None if no gang involvement.
280 Raises exceptions on database errors and invalid input.
281 """
282
283 assert sStatus != TestSetData.ksTestStatus_Running;
284
285 #
286 # Get the basic test set data and check if there is anything to do here.
287 #
288 oData = TestSetData().initFromDbWithId(self._oDb, idTestSet);
289 if oData.enmStatus != TestSetData.ksTestStatus_Running:
290 raise TMExceptionBase('TestSet %s is already completed as %s.' % (idTestSet, oData.enmStatus));
291 if oData.idTestResult is None:
292 raise self._oDb.integrityException('idTestResult is NULL for TestSet %u' % (idTestSet,));
293
294 #
295 # Close open sub test results, count these as errors.
296 # Note! No need to propagate error counts here. Only one tree line will
297 # have open sets, and it will go all the way to the root.
298 #
299 self._oDb.execute('SELECT idTestResult\n'
300 'FROM TestResults\n'
301 'WHERE idTestSet = %s\n'
302 ' AND enmStatus = %s\n'
303 ' AND idTestResult <> %s\n'
304 'ORDER BY idTestResult DESC\n'
305 , (idTestSet, TestSetData.ksTestStatus_Running, oData.idTestResult));
306 aaoRows = self._oDb.fetchAll();
307 if len(aaoRows):
308 idStr = self.strTabString('Unclosed test result', fCommit = fCommit);
309 for aoRow in aaoRows:
310 self._oDb.execute('UPDATE TestResults\n'
311 'SET enmStatus = \'failure\',\n'
312 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n'
313 ' cErrors = cErrors + 1\n'
314 'WHERE idTestResult = %s\n'
315 , (aoRow[0],));
316 self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idStrMsg, enmLevel)\n'
317 'VALUES ( %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
318 , (aoRow[0], idStr,));
319
320 #
321 # If it's a success result, check it against error counters.
322 #
323 if sStatus not in TestSetData.kasBadTestStatuses:
324 self._oDb.execute('SELECT COUNT(*)\n'
325 'FROM TestResults\n'
326 'WHERE idTestSet = %s\n'
327 ' AND cErrors > 0\n'
328 , (idTestSet,));
329 cErrors = self._oDb.fetchOne()[0];
330 if cErrors > 0:
331 sStatus = TestSetData.ksTestStatus_Failure;
332
333 #
334 # If it's an pure 'failure', check for timeouts and propagate it.
335 #
336 if sStatus == TestSetData.ksTestStatus_Failure:
337 self._oDb.execute('SELECT COUNT(*)\n'
338 'FROM TestResults\n'
339 'WHERE idTestSet = %s\n'
340 ' AND enmStatus = %s\n'
341 , ( idTestSet, TestSetData.ksTestStatus_TimedOut, ));
342 if self._oDb.fetchOne()[0] > 0:
343 sStatus = TestSetData.ksTestStatus_TimedOut;
344
345 #
346 # Complete the top level test result and then the test set.
347 #
348 self._oDb.execute('UPDATE TestResults\n'
349 'SET cErrors = (SELECT COALESCE(SUM(cErrors), 0)\n'
350 ' FROM TestResults\n'
351 ' WHERE idTestResultParent = %s)\n'
352 'WHERE idTestResult = %s\n'
353 'RETURNING cErrors\n'
354 , (oData.idTestResult, oData.idTestResult));
355 cErrors = self._oDb.fetchOne()[0];
356 if cErrors == 0 and sStatus in TestSetData.kasBadTestStatuses:
357 self._oDb.execute('UPDATE TestResults\n'
358 'SET cErrors = 1\n'
359 'WHERE idTestResult = %s\n'
360 , (oData.idTestResult,));
361 elif cErrors > 0 and sStatus not in TestSetData.kasBadTestStatuses:
362 sStatus = TestSetData.ksTestStatus_Failure; # Impossible.
363 self._oDb.execute('UPDATE TestResults\n'
364 'SET enmStatus = %s,\n'
365 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
366 'WHERE idTestResult = %s\n'
367 , (sStatus, oData.idTestResult,));
368
369 self._oDb.execute('UPDATE TestSets\n'
370 'SET enmStatus = %s,\n'
371 ' tsDone = CURRENT_TIMESTAMP\n'
372 'WHERE idTestSet = %s\n'
373 , (sStatus, idTestSet,));
374
375 self._oDb.maybeCommit(fCommit);
376 return oData.idTestSetGangLeader;
377
378 def completeAsAbandond(self, idTestSet, fCommit = False):
379 """
380 Completes the testset as abandoned if necessary.
381
382 See scenario #9:
383 file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandond-testcase
384
385 Returns True if successfully completed as abandond, False if it's already
386 completed, and raises exceptions under exceptional circumstances.
387 """
388
389 #
390 # Get the basic test set data and check if there is anything to do here.
391 #
392 oData = self.tryFetch(idTestSet);
393 if oData is None:
394 return False;
395 if oData.enmStatus != TestSetData.ksTestStatus_Running:
396 return False;
397
398 if oData.idTestResult is not None:
399 #
400 # Clean up test results, adding a message why they failed.
401 #
402 self._oDb.execute('UPDATE TestResults\n'
403 'SET enmStatus = \'failure\',\n'
404 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n'
405 ' cErrors = cErrors + 1\n'
406 'WHERE idTestSet = %s\n'
407 ' AND enmStatus = \'running\'::TestStatus_T\n'
408 , (idTestSet,));
409
410 idStr = self.strTabString('The test was abandond by the testbox', fCommit = fCommit);
411 self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idStrMsg, enmLevel)\n'
412 'VALUES ( %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
413 , (oData.idTestResult, idStr,));
414
415 #
416 # Complete the testset.
417 #
418 self._oDb.execute('UPDATE TestSets\n'
419 'SET enmStatus = \'failure\',\n'
420 ' tsDone = CURRENT_TIMESTAMP\n'
421 'WHERE idTestSet = %s\n'
422 ' AND enmStatus = \'running\'::TestStatus_T\n'
423 , (idTestSet,));
424
425 self._oDb.maybeCommit(fCommit);
426 return True;
427
428 def completeAsGangGatheringTimeout(self, idTestSet, fCommit = False):
429 """
430 Completes the testset with a gang-gathering timeout.
431 Raises exceptions on database errors and invalid input.
432 """
433 #
434 # Get the basic test set data and check if there is anything to do here.
435 #
436 oData = TestSetData().initFromDbWithId(self._oDb, idTestSet);
437 if oData.enmStatus != TestSetData.ksTestStatus_Running:
438 raise TMExceptionBase('TestSet %s is already completed as %s.' % (idTestSet, oData.enmStatus));
439 if oData.idTestResult is None:
440 raise self._oDb.integrityException('idTestResult is NULL for TestSet %u' % (idTestSet,));
441
442 #
443 # Complete the top level test result and then the test set.
444 #
445 self._oDb.execute('UPDATE TestResults\n'
446 'SET enmStatus = \'failure\',\n'
447 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n'
448 ' cErrors = cErrors + 1\n'
449 'WHERE idTestSet = %s\n'
450 ' AND enmStatus = \'running\'::TestStatus_T\n'
451 , (idTestSet,));
452
453 idStr = self.strTabString('Gang gathering timed out', fCommit = fCommit);
454 self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idStrMsg, enmLevel)\n'
455 'VALUES ( %s, %s, \'failure\'::TestResultMsgLevel_T)\n'
456 , (oData.idTestResult, idStr,));
457
458 self._oDb.execute('UPDATE TestSets\n'
459 'SET enmStatus = \'failure\',\n'
460 ' tsDone = CURRENT_TIMESTAMP\n'
461 'WHERE idTestSet = %s\n'
462 , (idTestSet,));
463
464 self._oDb.maybeCommit(fCommit);
465 return True;
466
467 def createFile(self, oTestSet, sName, sMime, sKind, sDesc, cbFile, fCommit = False): # pylint: disable=R0914
468 """
469 Creates a file and associating with the current test result record in
470 the test set.
471
472 Returns file object that the file content can be written to.
473 Raises exception on database error, I/O errors, if there are too many
474 files in the test set or if they take up too much disk space.
475
476 The caller (testboxdisp.py) is expected to do basic input validation,
477 so we skip that and get on with the bits only we can do.
478 """
479
480 #
481 # Furhter input and limit checks.
482 #
483 if oTestSet.enmStatus != TestSetData.ksTestStatus_Running:
484 raise TMExceptionBase('Cannot create files on a test set with status "%s".' % (oTestSet.enmStatus,));
485
486 self._oDb.execute('SELECT TestResultStrTab.sValue\n'
487 'FROM TestResultFiles,\n'
488 ' TestResults,\n'
489 ' TestResultStrTab\n'
490 'WHERE TestResults.idTestSet = %s\n'
491 ' AND TestResultFiles.idTestResult = TestResults.idTestResult\n'
492 ' AND TestResultStrTab.idStr = TestResultFiles.idStrFile\n'
493 , ( oTestSet.idTestSet,));
494 if self._oDb.getRowCount() + 1 > config.g_kcMaxUploads:
495 raise TMExceptionBase('Uploaded too many files already (%d).' % (self._oDb.getRowCount(),));
496
497 dFiles = {}
498 cbTotalFiles = 0;
499 for aoRow in self._oDb.fetchAll():
500 dFiles[aoRow[0].lower()] = 1; # For determining a unique filename further down.
501 sFile = os.path.join(config.g_ksFileAreaRootDir, oTestSet.sBaseFilename + '-' + aoRow[0]);
502 try:
503 cbTotalFiles += os.path.getsize(sFile);
504 except:
505 cbTotalFiles += config.g_kcMbMaxUploadSingle * 1048576;
506 if (cbTotalFiles + cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadTotal:
507 raise TMExceptionBase('Will exceed total upload limit: %u bytes + %u bytes > %s MiB.' \
508 % (cbTotalFiles, cbFile, config.g_kcMbMaxUploadTotal));
509
510 #
511 # Create a new file.
512 #
513 self._oDb.execute('SELECT idTestResult\n'
514 'FROM TestResults\n'
515 'WHERE idTestSet = %s\n'
516 ' AND enmStatus = \'running\'::TestStatus_T\n'
517 'ORDER BY idTestResult\n'
518 'LIMIT 1\n'
519 % ( oTestSet.idTestSet, ));
520 if self._oDb.getRowCount() < 1:
521 raise TMExceptionBase('No open test results - someone committed a capital offence or we ran into a race.');
522 idTestResult = self._oDb.fetchOne()[0];
523
524 if sName.lower() in dFiles:
525 # Note! There is in theory a race here, but that's something the
526 # test driver doing parallel upload with non-unique names
527 # should worry about. The TD should always avoid this path.
528 sOrgName = sName;
529 for i in range(2, config.g_kcMaxUploads + 6):
530 sName = '%s-%s' % (i, sName,);
531 if sName not in dFiles:
532 break;
533 sName = None;
534 if sName is None:
535 raise TMExceptionBase('Failed to find unique name for %s.' % (sOrgName,));
536
537 self._oDb.execute('INSERT INTO TestResultFiles(idTestResult, idStrFile, idStrDescription, idStrKind, idStrMime)\n'
538 'VALUES (%s, %s, %s, %s, %s)\n'
539 , ( idTestResult,
540 self.strTabString(sName),
541 self.strTabString(sDesc),
542 self.strTabString(sKind),
543 self.strTabString(sMime),
544 ));
545
546 oFile = oTestSet.createFile(sName, 'wb');
547 if utils.isString(oFile):
548 raise TMExceptionBase('Error creating "%s": %s' % (sName, oFile));
549 self._oDb.maybeCommit(fCommit);
550 return oFile;
551
552 def getGang(self, idTestSetGangLeader):
553 """
554 Returns an array of TestBoxData object representing the gang for the given testset.
555 """
556 self._oDb.execute('SELECT TestBoxes.*\n'
557 'FROM TestBoxes, TestSets\n'
558 'WHERE TestSets.idTestSetGangLeader = %s\n'
559 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
560 'ORDER BY iGangMemberNo ASC\n'
561 , (idTestSetGangLeader,));
562 aaoRows = self._oDb.fetchAll();
563 aoTestBoxes = [];
564 for aoRow in aaoRows:
565 aoTestBoxes.append(TestBoxData().initFromDbRow(aoRow));
566 return aoTestBoxes;
567
568 def getFile(self, idTestSet, idTestResultFile):
569 """
570 Gets the TestResultFileEx corresponding to idTestResultFile.
571
572 Raises an exception if the file wasn't found, doesn't belong to
573 idTestSet, and on DB error.
574 """
575 self._oDb.execute('SELECT TestResultFiles.*,\n'
576 ' StrTabFile.sValue AS sFile,\n'
577 ' StrTabDesc.sValue AS sDescription,\n'
578 ' StrTabKind.sValue AS sKind,\n'
579 ' StrTabMime.sValue AS sMime\n'
580 'FROM TestResultFiles,\n'
581 ' TestResultStrTab AS StrTabFile,\n'
582 ' TestResultStrTab AS StrTabDesc,\n'
583 ' TestResultStrTab AS StrTabKind,\n'
584 ' TestResultStrTab AS StrTabMime,\n'
585 ' TestResults\n'
586 'WHERE TestResultFiles.idTestResultFile = %s\n'
587 ' AND TestResultFiles.idStrFile = StrTabFile.idStr\n'
588 ' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n'
589 ' AND TestResultFiles.idStrKind = StrTabKind.idStr\n'
590 ' AND TestResultFiles.idStrMime = StrTabMime.idStr\n'
591 ' AND TestResults.idTestResult = TestResultFiles.idTestResult\n'
592 ' AND TestResults.idTestSet = %s\n'
593 , ( idTestResultFile, idTestSet, ));
594 return TestResultFileDataEx().initFromDbRow(self._oDb.fetchOne());
595
596
597 def getById(self, idTestSet):
598 """
599 Get TestSet table record by its id
600 """
601 self._oDb.execute('SELECT *\n'
602 'FROM TestSets\n'
603 'WHERE idTestSet=%s\n',
604 (idTestSet,))
605
606 aRows = self._oDb.fetchAll()
607 if len(aRows) not in (0, 1):
608 raise TMTooManyRows('Found more than one test sets with the same credentials. Database structure is corrupted.')
609 try:
610 return TestSetData().initFromDbRow(aRows[0])
611 except IndexError:
612 return None
613
614
615 def fetchOrphaned(self):
616 """
617 Returns a list of TestSetData objects of orphaned test sets.
618
619 A test set is orphaned if tsDone is NULL and the testbox has created
620 one or more newer testsets.
621 """
622
623 self._oDb.execute('SELECT TestSets.*\n'
624 'FROM TestSets,\n'
625 ' (SELECT idTestSet, idTestBox FROM TestSets WHERE tsDone is NULL) AS t\n'
626 'WHERE TestSets.idTestSet = t.idTestSet\n'
627 ' AND EXISTS(SELECT 1 FROM TestSets st\n'
628 ' WHERE st.idTestBox = t.idTestBox AND st.idTestSet > t.idTestSet)\n'
629 ' AND NOT EXISTS(SELECT 1 FROM TestBoxStatuses tbs\n'
630 ' WHERE tbs.idTestBox = t.idTestBox AND tbs.idTestSet = t.idTestSet)\n'
631 'ORDER by TestSets.idTestBox, TestSets.idTestSet'
632 );
633 aoRet = [];
634 for aoRow in self._oDb.fetchAll():
635 aoRet.append(TestSetData().initFromDbRow(aoRow));
636 return aoRet;
637
638
639#
640# Unit testing.
641#
642
643# pylint: disable=C0111
644class TestSetDataTestCase(ModelDataBaseTestCase):
645 def setUp(self):
646 self.aoSamples = [TestSetData(),];
647
648if __name__ == '__main__':
649 unittest.main();
650 # not reached.
651
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