VirtualBox

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

Last change on this file since 52776 was 52776, checked in by vboxsync, 10 years ago

fix OSE

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