VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/testcase.py@ 93115

Last change on this file since 93115 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 65.4 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testcase.py 93115 2022-01-01 11:31:46Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Test Manager - Test Case.
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2012-2022 Oracle Corporation
12
13This file is part of VirtualBox Open Source Edition (OSE), as
14available from http://www.virtualbox.org. This file is free software;
15you can redistribute it and/or modify it under the terms of the GNU
16General Public License (GPL) as published by the Free Software
17Foundation, in version 2 as it comes in the "COPYING" file of the
18VirtualBox OSE distribution. VirtualBox OSE is distributed in the
19hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
20
21The contents of this file may alternatively be used under the terms
22of the Common Development and Distribution License Version 1.0
23(CDDL) only, as it comes in the "COPYING.CDDL" file of the
24VirtualBox OSE distribution, in which case the provisions of the
25CDDL are applicable instead of those of the GPL.
26
27You may elect to license modified versions of this file under the
28terms and conditions of either the GPL or the CDDL or both.
29"""
30__version__ = "$Revision: 93115 $"
31
32
33# Standard python imports.
34import copy;
35import sys;
36import unittest;
37
38# Validation Kit imports.
39from common import utils;
40from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \
41 TMInvalidData, TMRowNotFound, ChangeLogEntry, AttributeChangeEntry;
42from testmanager.core.globalresource import GlobalResourceData;
43from testmanager.core.useraccount import UserAccountLogic;
44
45# Python 3 hacks:
46if sys.version_info[0] >= 3:
47 long = int; # pylint: disable=redefined-builtin,invalid-name
48
49
50
51class TestCaseGlobalRsrcDepData(ModelDataBase):
52 """
53 Test case dependency on a global resource - data.
54 """
55
56 ksParam_idTestCase = 'TestCaseDependency_idTestCase';
57 ksParam_idGlobalRsrc = 'TestCaseDependency_idGlobalRsrc';
58 ksParam_tsEffective = 'TestCaseDependency_tsEffective';
59 ksParam_tsExpire = 'TestCaseDependency_tsExpire';
60 ksParam_uidAuthor = 'TestCaseDependency_uidAuthor';
61
62 kasAllowNullAttributes = ['idTestSet', ];
63
64 def __init__(self):
65 ModelDataBase.__init__(self);
66
67 #
68 # Initialize with defaults.
69 # See the database for explanations of each of these fields.
70 #
71 self.idTestCase = None;
72 self.idGlobalRsrc = None;
73 self.tsEffective = None;
74 self.tsExpire = None;
75 self.uidAuthor = None;
76
77 def initFromDbRow(self, aoRow):
78 """
79 Reinitialize from a SELECT * FROM TestCaseDeps row.
80 """
81 if aoRow is None:
82 raise TMRowNotFound('Test case not found.');
83
84 self.idTestCase = aoRow[0];
85 self.idGlobalRsrc = aoRow[1];
86 self.tsEffective = aoRow[2];
87 self.tsExpire = aoRow[3];
88 self.uidAuthor = aoRow[4];
89 return self;
90
91
92class TestCaseGlobalRsrcDepLogic(ModelLogicBase):
93 """
94 Test case dependency on a global resources - logic.
95 """
96
97 def getTestCaseDeps(self, idTestCase, tsNow = None):
98 """
99 Returns an array of (TestCaseGlobalRsrcDepData, GlobalResourceData)
100 with the global resources required by idTestCase.
101 Returns empty array if none found. Raises exception on database error.
102
103 Note! Maybe a bit overkill...
104 """
105 ## @todo This code isn't entirely kosher... Should use a DataEx with a oGlobalRsrc = GlobalResourceData().
106 if tsNow is not None:
107 self._oDb.execute('SELECT *\n'
108 'FROM TestCaseGlobalRsrcDeps, GlobalResources\n'
109 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n'
110 ' AND TestCaseGlobalRsrcDeps.tsExpire > %s\n'
111 ' AND TestCaseGlobalRsrcDeps.tsEffective <= %s\n'
112 ' AND GlobalResources.idGlobalRsrc = TestCaseGlobalRsrcDeps.idGlobalRsrc\n'
113 ' AND GlobalResources.tsExpire > %s\n'
114 ' AND GlobalResources.tsEffective <= %s\n'
115 , (idTestCase, tsNow, tsNow, tsNow, tsNow) );
116 else:
117 self._oDb.execute('SELECT *\n'
118 'FROM TestCaseGlobalRsrcDeps, GlobalResources\n'
119 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n'
120 ' AND GlobalResources.idGlobalRsrc = TestCaseGlobalRsrcDeps.idGlobalRsrc\n'
121 ' AND TestCaseGlobalRsrcDeps.tsExpire = \'infinity\'::TIMESTAMP\n'
122 ' AND GlobalResources.tsExpire = \'infinity\'::TIMESTAMP\n'
123 , (idTestCase,))
124 aaoRows = self._oDb.fetchAll();
125 aoRet = []
126 for aoRow in aaoRows:
127 oItem = [TestCaseDependencyData().initFromDbRow(aoRow),
128 GlobalResourceData().initFromDbRow(aoRow[5:])];
129 aoRet.append(oItem);
130
131 return aoRet
132
133 def getTestCaseDepsIds(self, idTestCase, tsNow = None):
134 """
135 Returns an array of global resources that idTestCase require.
136 Returns empty array if none found. Raises exception on database error.
137 """
138 if tsNow is not None:
139 self._oDb.execute('SELECT idGlobalRsrc\n'
140 'FROM TestCaseGlobalRsrcDeps\n'
141 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n'
142 ' AND TestCaseGlobalRsrcDeps.tsExpire > %s\n'
143 ' AND TestCaseGlobalRsrcDeps.tsEffective <= %s\n'
144 , (idTestCase, tsNow, tsNow, ) );
145 else:
146 self._oDb.execute('SELECT idGlobalRsrc\n'
147 'FROM TestCaseGlobalRsrcDeps\n'
148 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n'
149 ' AND TestCaseGlobalRsrcDeps.tsExpire = \'infinity\'::TIMESTAMP\n'
150 , (idTestCase,))
151 aidGlobalRsrcs = []
152 for aoRow in self._oDb.fetchAll():
153 aidGlobalRsrcs.append(aoRow[0]);
154 return aidGlobalRsrcs;
155
156
157 def getDepGlobalResourceData(self, idTestCase, tsNow = None):
158 """
159 Returns an array of objects of type GlobalResourceData on which the
160 specified test case depends on.
161 """
162 if tsNow is None :
163 self._oDb.execute('SELECT GlobalResources.*\n'
164 'FROM TestCaseGlobalRsrcDeps, GlobalResources\n'
165 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n'
166 ' AND GlobalResources.idGlobalRsrc = TestCaseGlobalRsrcDeps.idGlobalRsrc\n'
167 ' AND TestCaseGlobalRsrcDeps.tsExpire = \'infinity\'::TIMESTAMP\n'
168 ' AND GlobalResources.tsExpire = \'infinity\'::TIMESTAMP\n'
169 'ORDER BY GlobalResources.idGlobalRsrc\n'
170 , (idTestCase,))
171 else:
172 self._oDb.execute('SELECT GlobalResources.*\n'
173 'FROM TestCaseGlobalRsrcDeps, GlobalResources\n'
174 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n'
175 ' AND GlobalResources.idGlobalRsrc = TestCaseGlobalRsrcDeps.idGlobalRsrc\n'
176 ' AND TestCaseGlobalRsrcDeps.tsExpire > %s\n'
177 ' AND TestCaseGlobalRsrcDeps.tsExpire <= %s\n'
178 ' AND GlobalResources.tsExpire > %s\n'
179 ' AND GlobalResources.tsEffective <= %s\n'
180 'ORDER BY GlobalResources.idGlobalRsrc\n'
181 , (idTestCase, tsNow, tsNow, tsNow, tsNow));
182
183 aaoRows = self._oDb.fetchAll()
184 aoRet = []
185 for aoRow in aaoRows:
186 aoRet.append(GlobalResourceData().initFromDbRow(aoRow));
187
188 return aoRet
189
190
191class TestCaseDependencyData(ModelDataBase):
192 """
193 Test case dependency data
194 """
195
196 ksParam_idTestCase = 'TestCaseDependency_idTestCase';
197 ksParam_idTestCasePreReq = 'TestCaseDependency_idTestCasePreReq';
198 ksParam_tsEffective = 'TestCaseDependency_tsEffective';
199 ksParam_tsExpire = 'TestCaseDependency_tsExpire';
200 ksParam_uidAuthor = 'TestCaseDependency_uidAuthor';
201
202
203 def __init__(self):
204 ModelDataBase.__init__(self);
205
206 #
207 # Initialize with defaults.
208 # See the database for explanations of each of these fields.
209 #
210 self.idTestCase = None;
211 self.idTestCasePreReq = None;
212 self.tsEffective = None;
213 self.tsExpire = None;
214 self.uidAuthor = None;
215
216 def initFromDbRow(self, aoRow):
217 """
218 Reinitialize from a SELECT * FROM TestCaseDeps row.
219 """
220 if aoRow is None:
221 raise TMRowNotFound('Test case not found.');
222
223 self.idTestCase = aoRow[0];
224 self.idTestCasePreReq = aoRow[1];
225 self.tsEffective = aoRow[2];
226 self.tsExpire = aoRow[3];
227 self.uidAuthor = aoRow[4];
228 return self;
229
230 def initFromParams(self, oDisp, fStrict=True):
231 """
232 Initialize the object from parameters.
233 The input is not validated at all, except that all parameters must be
234 present when fStrict is True.
235 Note! Returns parameter NULL values, not database ones.
236 """
237
238 self.convertToParamNull();
239 fn = oDisp.getStringParam; # Shorter...
240
241 self.idTestCase = fn(self.ksParam_idTestCase, None, None if fStrict else self.idTestCase);
242 self.idTestCasePreReq = fn(self.ksParam_idTestCasePreReq, None, None if fStrict else self.idTestCasePreReq);
243 self.tsEffective = fn(self.ksParam_tsEffective, None, None if fStrict else self.tsEffective);
244 self.tsExpire = fn(self.ksParam_tsExpire, None, None if fStrict else self.tsExpire);
245 self.uidAuthor = fn(self.ksParam_uidAuthor, None, None if fStrict else self.uidAuthor);
246
247 return True
248
249 def validateAndConvert(self, oDb = None, enmValidateFor = ModelDataBase.ksValidateFor_Other):
250 """
251 Validates the input and converts valid fields to their right type.
252 Returns a dictionary with per field reports, only invalid fields will
253 be returned, so an empty dictionary means that the data is valid.
254
255 The dictionary keys are ksParam_*.
256 """
257 dErrors = dict()
258
259 self.idTestCase = self._validateInt( dErrors, self.ksParam_idTestCase, self.idTestCase);
260 self.idTestCasePreReq = self._validateInt( dErrors, self.ksParam_idTestCasePreReq, self.idTestCasePreReq);
261 self.tsEffective = self._validateTs( dErrors, self.ksParam_tsEffective, self.tsEffective);
262 self.tsExpire = self._validateTs( dErrors, self.ksParam_tsExpire, self.tsExpire);
263 self.uidAuthor = self._validateInt( dErrors, self.ksParam_uidAuthor, self.uidAuthor);
264
265 _ = oDb;
266 _ = enmValidateFor;
267 return dErrors
268
269 def convertFromParamNull(self):
270 """
271 Converts from parameter NULL values to database NULL values (None).
272 """
273 if self.idTestCase in [-1, '']: self.idTestCase = None;
274 if self.idTestCasePreReq in [-1, '']: self.idTestCasePreReq = None;
275 if self.tsEffective == '': self.tsEffective = None;
276 if self.tsExpire == '': self.tsExpire = None;
277 if self.uidAuthor in [-1, '']: self.uidAuthor = None;
278 return True;
279
280 def convertToParamNull(self):
281 """
282 Converts from database NULL values (None) to special values we can
283 pass thru parameters list.
284 """
285 if self.idTestCase is None: self.idTestCase = -1;
286 if self.idTestCasePreReq is None: self.idTestCasePreReq = -1;
287 if self.tsEffective is None: self.tsEffective = '';
288 if self.tsExpire is None: self.tsExpire = '';
289 if self.uidAuthor is None: self.uidAuthor = -1;
290 return True;
291
292 def isEqual(self, oOther):
293 """ Compares two instances. """
294 return self.idTestCase == oOther.idTestCase \
295 and self.idTestCasePreReq == oOther.idTestCasePreReq \
296 and self.tsEffective == oOther.tsEffective \
297 and self.tsExpire == oOther.tsExpire \
298 and self.uidAuthor == oOther.uidAuthor;
299
300 def getTestCasePreReqIds(self, aTestCaseDependencyData):
301 """
302 Get list of Test Case IDs which current
303 Test Case depends on
304 """
305 if not aTestCaseDependencyData:
306 return []
307
308 aoRet = []
309 for oTestCaseDependencyData in aTestCaseDependencyData:
310 aoRet.append(oTestCaseDependencyData.idTestCasePreReq)
311
312 return aoRet
313
314class TestCaseDependencyLogic(ModelLogicBase):
315 """Test case dependency management logic"""
316
317 def getTestCaseDeps(self, idTestCase, tsEffective = None):
318 """
319 Returns an array of TestCaseDependencyData with the prerequisites of
320 idTestCase.
321 Returns empty array if none found. Raises exception on database error.
322 """
323 if tsEffective is not None:
324 self._oDb.execute('SELECT *\n'
325 'FROM TestCaseDeps\n'
326 'WHERE idTestCase = %s\n'
327 ' AND tsExpire > %s\n'
328 ' AND tsEffective <= %s\n'
329 , (idTestCase, tsEffective, tsEffective, ) );
330 else:
331 self._oDb.execute('SELECT *\n'
332 'FROM TestCaseDeps\n'
333 'WHERE idTestCase = %s\n'
334 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
335 , (idTestCase, ) );
336 aaoRows = self._oDb.fetchAll();
337 aoRet = [];
338 for aoRow in aaoRows:
339 aoRet.append(TestCaseDependencyData().initFromDbRow(aoRow));
340
341 return aoRet
342
343 def getTestCaseDepsIds(self, idTestCase, tsNow = None):
344 """
345 Returns an array of test case IDs of the prerequisites of idTestCase.
346 Returns empty array if none found. Raises exception on database error.
347 """
348 if tsNow is not None:
349 self._oDb.execute('SELECT idTestCase\n'
350 'FROM TestCaseDeps\n'
351 'WHERE idTestCase = %s\n'
352 ' AND tsExpire > %s\n'
353 ' AND tsEffective <= %s\n'
354 , (idTestCase, tsNow, tsNow, ) );
355 else:
356 self._oDb.execute('SELECT idTestCase\n'
357 'FROM TestCaseDeps\n'
358 'WHERE idTestCase = %s\n'
359 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
360 , (idTestCase, ) );
361 aidPreReqs = [];
362 for aoRow in self._oDb.fetchAll():
363 aidPreReqs.append(aoRow[0]);
364 return aidPreReqs;
365
366
367 def getDepTestCaseData(self, idTestCase, tsNow = None):
368 """
369 Returns an array of objects of type TestCaseData2 on which
370 specified test case depends on
371 """
372 if tsNow is None:
373 self._oDb.execute('SELECT TestCases.*\n'
374 'FROM TestCases, TestCaseDeps\n'
375 'WHERE TestCaseDeps.idTestCase = %s\n'
376 ' AND TestCaseDeps.idTestCasePreReq = TestCases.idTestCase\n'
377 ' AND TestCaseDeps.tsExpire = \'infinity\'::TIMESTAMP\n'
378 ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP\n'
379 'ORDER BY TestCases.idTestCase\n'
380 , (idTestCase, ) );
381 else:
382 self._oDb.execute('SELECT TestCases.*\n'
383 'FROM TestCases, TestCaseDeps\n'
384 'WHERE TestCaseDeps.idTestCase = %s\n'
385 ' AND TestCaseDeps.idTestCasePreReq = TestCases.idTestCase\n'
386 ' AND TestCaseDeps.tsExpire > %s\n'
387 ' AND TestCaseDeps.tsEffective <= %s\n'
388 ' AND TestCases.tsExpire > %s\n'
389 ' AND TestCases.tsEffective <= %s\n'
390 'ORDER BY TestCases.idTestCase\n'
391 , (idTestCase, tsNow, tsNow, tsNow, tsNow, ) );
392
393 aaoRows = self._oDb.fetchAll()
394 aoRet = []
395 for aoRow in aaoRows:
396 aoRet.append(TestCaseData().initFromDbRow(aoRow));
397
398 return aoRet
399
400 def getApplicableDepTestCaseData(self, idTestCase):
401 """
402 Returns an array of objects of type TestCaseData on which
403 specified test case might depends on (all test
404 cases except the specified one and those testcases which are
405 depend on idTestCase)
406 """
407 self._oDb.execute('SELECT *\n'
408 'FROM TestCases\n'
409 'WHERE idTestCase <> %s\n'
410 ' AND idTestCase NOT IN (SELECT idTestCase\n'
411 ' FROM TestCaseDeps\n'
412 ' WHERE idTestCasePreReq=%s\n'
413 ' AND tsExpire = \'infinity\'::TIMESTAMP)\n'
414 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
415 , (idTestCase, idTestCase) )
416
417 aaoRows = self._oDb.fetchAll()
418 aoRet = []
419 for aoRow in aaoRows:
420 aoRet.append(TestCaseData().initFromDbRow(aoRow));
421
422 return aoRet
423
424class TestCaseData(ModelDataBase):
425 """
426 Test case data
427 """
428
429 ksIdAttr = 'idTestCase';
430 ksIdGenAttr = 'idGenTestCase';
431
432 ksParam_idTestCase = 'TestCase_idTestCase'
433 ksParam_tsEffective = 'TestCase_tsEffective'
434 ksParam_tsExpire = 'TestCase_tsExpire'
435 ksParam_uidAuthor = 'TestCase_uidAuthor'
436 ksParam_idGenTestCase = 'TestCase_idGenTestCase'
437 ksParam_sName = 'TestCase_sName'
438 ksParam_sDescription = 'TestCase_sDescription'
439 ksParam_fEnabled = 'TestCase_fEnabled'
440 ksParam_cSecTimeout = 'TestCase_cSecTimeout'
441 ksParam_sTestBoxReqExpr = 'TestCase_sTestBoxReqExpr';
442 ksParam_sBuildReqExpr = 'TestCase_sBuildReqExpr';
443 ksParam_sBaseCmd = 'TestCase_sBaseCmd'
444 ksParam_sValidationKitZips = 'TestCase_sValidationKitZips'
445 ksParam_sComment = 'TestCase_sComment'
446
447 kasAllowNullAttributes = [ 'idTestCase', 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestCase', 'sDescription',
448 'sTestBoxReqExpr', 'sBuildReqExpr', 'sValidationKitZips', 'sComment' ];
449
450 kcDbColumns = 14;
451
452 def __init__(self):
453 ModelDataBase.__init__(self);
454
455 #
456 # Initialize with defaults.
457 # See the database for explanations of each of these fields.
458 #
459 self.idTestCase = None;
460 self.tsEffective = None;
461 self.tsExpire = None;
462 self.uidAuthor = None;
463 self.idGenTestCase = None;
464 self.sName = None;
465 self.sDescription = None;
466 self.fEnabled = False;
467 self.cSecTimeout = 10; # Init with minimum timeout value
468 self.sTestBoxReqExpr = None;
469 self.sBuildReqExpr = None;
470 self.sBaseCmd = None;
471 self.sValidationKitZips = None;
472 self.sComment = None;
473
474 def initFromDbRow(self, aoRow):
475 """
476 Reinitialize from a SELECT * FROM TestCases row.
477 Returns self. Raises exception if no row.
478 """
479 if aoRow is None:
480 raise TMRowNotFound('Test case not found.');
481
482 self.idTestCase = aoRow[0];
483 self.tsEffective = aoRow[1];
484 self.tsExpire = aoRow[2];
485 self.uidAuthor = aoRow[3];
486 self.idGenTestCase = aoRow[4];
487 self.sName = aoRow[5];
488 self.sDescription = aoRow[6];
489 self.fEnabled = aoRow[7];
490 self.cSecTimeout = aoRow[8];
491 self.sTestBoxReqExpr = aoRow[9];
492 self.sBuildReqExpr = aoRow[10];
493 self.sBaseCmd = aoRow[11];
494 self.sValidationKitZips = aoRow[12];
495 self.sComment = aoRow[13];
496 return self;
497
498 def initFromDbWithId(self, oDb, idTestCase, tsNow = None, sPeriodBack = None):
499 """
500 Initialize the object from the database.
501 """
502 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
503 'SELECT *\n'
504 'FROM TestCases\n'
505 'WHERE idTestCase = %s\n'
506 , ( idTestCase,), tsNow, sPeriodBack));
507 aoRow = oDb.fetchOne()
508 if aoRow is None:
509 raise TMRowNotFound('idTestCase=%s not found (tsNow=%s sPeriodBack=%s)' % (idTestCase, tsNow, sPeriodBack,));
510 return self.initFromDbRow(aoRow);
511
512 def initFromDbWithGenId(self, oDb, idGenTestCase, tsNow = None):
513 """
514 Initialize the object from the database.
515 """
516 _ = tsNow; # For relevant for the TestCaseDataEx version only.
517 oDb.execute('SELECT *\n'
518 'FROM TestCases\n'
519 'WHERE idGenTestCase = %s\n'
520 , (idGenTestCase, ) );
521 return self.initFromDbRow(oDb.fetchOne());
522
523 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb):
524 if sAttr == 'cSecTimeout' and oValue not in aoNilValues: # Allow human readable interval formats.
525 return utils.parseIntervalSeconds(oValue);
526
527 (oValue, sError) = ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
528 if sError is None:
529 if sAttr == 'sTestBoxReqExpr':
530 sError = TestCaseData.validateTestBoxReqExpr(oValue);
531 elif sAttr == 'sBuildReqExpr':
532 sError = TestCaseData.validateBuildReqExpr(oValue);
533 elif sAttr == 'sBaseCmd':
534 _, sError = TestCaseData.validateStr(oValue, fAllowUnicodeSymbols=False);
535 return (oValue, sError);
536
537
538 #
539 # Misc.
540 #
541
542 def needValidationKitBit(self):
543 """
544 Predicate method for checking whether a validation kit build is required.
545 """
546 return self.sValidationKitZips is None \
547 or self.sValidationKitZips.find('@VALIDATIONKIT_ZIP@') >= 0;
548
549 def matchesTestBoxProps(self, oTestBoxData):
550 """
551 Checks if the all of the testbox related test requirements matches the
552 given testbox.
553
554 Returns True or False according to the expression, None on exception or
555 non-boolean expression result.
556 """
557 return TestCaseData.matchesTestBoxPropsEx(oTestBoxData, self.sTestBoxReqExpr);
558
559 def matchesBuildProps(self, oBuildDataEx):
560 """
561 Checks if the all of the build related test requirements matches the
562 given build.
563
564 Returns True or False according to the expression, None on exception or
565 non-boolean expression result.
566 """
567 return TestCaseData.matchesBuildPropsEx(oBuildDataEx, self.sBuildReqExpr);
568
569
570 #
571 # Expression validation code shared with TestCaseArgsDataEx.
572 #
573 @staticmethod
574 def _safelyEvalExpr(sExpr, dLocals, fMayRaiseXcpt = False):
575 """
576 Safely evaluate requirment expression given a set of locals.
577
578 Returns True or False according to the expression. If the expression
579 causes an exception to be raised or does not return a boolean result,
580 None will be returned.
581 """
582 if sExpr is None or sExpr == '':
583 return True;
584
585 dGlobals = \
586 {
587 '__builtins__': None,
588 'long': long,
589 'int': int,
590 'bool': bool,
591 'True': True,
592 'False': False,
593 'len': len,
594 'isinstance': isinstance,
595 'type': type,
596 'dict': dict,
597 'dir': dir,
598 'list': list,
599 'versionCompare': utils.versionCompare,
600 };
601
602 try:
603 fRc = eval(sExpr, dGlobals, dLocals);
604 except:
605 if fMayRaiseXcpt:
606 raise;
607 return None;
608
609 if not isinstance(fRc, bool):
610 if fMayRaiseXcpt:
611 raise Exception('not a boolean result: "%s" - %s' % (fRc, type(fRc)) );
612 return None;
613
614 return fRc;
615
616 @staticmethod
617 def _safelyValidateReqExpr(sExpr, adLocals):
618 """
619 Validates a requirement expression using the given sets of locals,
620 returning None on success and an error string on failure.
621 """
622 for dLocals in adLocals:
623 try:
624 TestCaseData._safelyEvalExpr(sExpr, dLocals, True);
625 except Exception as oXcpt:
626 return str(oXcpt);
627 return None;
628
629 @staticmethod
630 def validateTestBoxReqExpr(sExpr):
631 """
632 Validates a testbox expression, returning None on success and an error
633 string on failure.
634 """
635 adTestBoxes = \
636 [
637 {
638 'sOs': 'win',
639 'sOsVersion': '3.1',
640 'sCpuVendor': 'VirtualBox',
641 'sCpuArch': 'x86',
642 'cCpus': 1,
643 'fCpuHwVirt': False,
644 'fCpuNestedPaging': False,
645 'fCpu64BitGuest': False,
646 'fChipsetIoMmu': False,
647 'fRawMode': False,
648 'cMbMemory': 985034,
649 'cMbScratch': 1234089,
650 'iTestBoxScriptRev': 1,
651 'sName': 'emanon',
652 'uuidSystem': '8FF81BE5-3901-4AB1-8A65-B48D511C0321',
653 },
654 {
655 'sOs': 'linux',
656 'sOsVersion': '3.1',
657 'sCpuVendor': 'VirtualBox',
658 'sCpuArch': 'amd64',
659 'cCpus': 8191,
660 'fCpuHwVirt': True,
661 'fCpuNestedPaging': True,
662 'fCpu64BitGuest': True,
663 'fChipsetIoMmu': True,
664 'fRawMode': True,
665 'cMbMemory': 9999999999,
666 'cMbScratch': 9999999999999,
667 'iTestBoxScriptRev': 9999999,
668 'sName': 'emanon',
669 'uuidSystem': '00000000-0000-0000-0000-000000000000',
670 },
671 ];
672 return TestCaseData._safelyValidateReqExpr(sExpr, adTestBoxes);
673
674 @staticmethod
675 def matchesTestBoxPropsEx(oTestBoxData, sExpr):
676 """ Worker for TestCaseData.matchesTestBoxProps and TestCaseArgsDataEx.matchesTestBoxProps. """
677 if sExpr is None:
678 return True;
679 dLocals = \
680 {
681 'sOs': oTestBoxData.sOs,
682 'sOsVersion': oTestBoxData.sOsVersion,
683 'sCpuVendor': oTestBoxData.sCpuVendor,
684 'sCpuArch': oTestBoxData.sCpuArch,
685 'iCpuFamily': oTestBoxData.getCpuFamily(),
686 'iCpuModel': oTestBoxData.getCpuModel(),
687 'cCpus': oTestBoxData.cCpus,
688 'fCpuHwVirt': oTestBoxData.fCpuHwVirt,
689 'fCpuNestedPaging': oTestBoxData.fCpuNestedPaging,
690 'fCpu64BitGuest': oTestBoxData.fCpu64BitGuest,
691 'fChipsetIoMmu': oTestBoxData.fChipsetIoMmu,
692 'fRawMode': oTestBoxData.fRawMode,
693 'cMbMemory': oTestBoxData.cMbMemory,
694 'cMbScratch': oTestBoxData.cMbScratch,
695 'iTestBoxScriptRev': oTestBoxData.iTestBoxScriptRev,
696 'iPythonHexVersion': oTestBoxData.iPythonHexVersion,
697 'sName': oTestBoxData.sName,
698 'uuidSystem': oTestBoxData.uuidSystem,
699 };
700 return TestCaseData._safelyEvalExpr(sExpr, dLocals);
701
702 @staticmethod
703 def validateBuildReqExpr(sExpr):
704 """
705 Validates a testbox expression, returning None on success and an error
706 string on failure.
707 """
708 adBuilds = \
709 [
710 {
711 'sProduct': 'VirtualBox',
712 'sBranch': 'trunk',
713 'sType': 'release',
714 'asOsArches': ['win.amd64', 'win.x86'],
715 'sVersion': '1.0',
716 'iRevision': 1234,
717 'uidAuthor': None,
718 'idBuild': 953,
719 },
720 {
721 'sProduct': 'VirtualBox',
722 'sBranch': 'VBox-4.1',
723 'sType': 'release',
724 'asOsArches': ['linux.x86',],
725 'sVersion': '4.2.15',
726 'iRevision': 89876,
727 'uidAuthor': None,
728 'idBuild': 945689,
729 },
730 {
731 'sProduct': 'VirtualBox',
732 'sBranch': 'VBox-4.1',
733 'sType': 'strict',
734 'asOsArches': ['solaris.x86', 'solaris.amd64',],
735 'sVersion': '4.3.0_RC3',
736 'iRevision': 97939,
737 'uidAuthor': 33,
738 'idBuild': 9456893,
739 },
740 ];
741 return TestCaseData._safelyValidateReqExpr(sExpr, adBuilds);
742
743 @staticmethod
744 def matchesBuildPropsEx(oBuildDataEx, sExpr):
745 """
746 Checks if the all of the build related test requirements matches the
747 given build.
748 """
749 if sExpr is None:
750 return True;
751 dLocals = \
752 {
753 'sProduct': oBuildDataEx.oCat.sProduct,
754 'sBranch': oBuildDataEx.oCat.sBranch,
755 'sType': oBuildDataEx.oCat.sType,
756 'asOsArches': oBuildDataEx.oCat.asOsArches,
757 'sVersion': oBuildDataEx.sVersion,
758 'iRevision': oBuildDataEx.iRevision,
759 'uidAuthor': oBuildDataEx.uidAuthor,
760 'idBuild': oBuildDataEx.idBuild,
761 };
762 return TestCaseData._safelyEvalExpr(sExpr, dLocals);
763
764
765
766
767class TestCaseDataEx(TestCaseData):
768 """
769 Test case data.
770 """
771
772 ksParam_aoTestCaseArgs = 'TestCase_aoTestCaseArgs';
773 ksParam_aoDepTestCases = 'TestCase_aoDepTestCases';
774 ksParam_aoDepGlobalResources = 'TestCase_aoDepGlobalResources';
775
776 # Use [] instead of None.
777 kasAltArrayNull = [ 'aoTestCaseArgs', 'aoDepTestCases', 'aoDepGlobalResources' ];
778
779
780 def __init__(self):
781 TestCaseData.__init__(self);
782
783 # List of objects of type TestCaseData (or TestCaseDataEx, we don't
784 # care) on which current Test Case depends.
785 self.aoDepTestCases = [];
786
787 # List of objects of type GlobalResourceData on which current Test Case depends.
788 self.aoDepGlobalResources = [];
789
790 # List of objects of type TestCaseArgsData.
791 self.aoTestCaseArgs = [];
792
793 def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None):
794 """
795 Worker shared by the initFromDb* methods.
796 Returns self. Raises exception if no row or database error.
797 """
798 _ = sPeriodBack; ## @todo sPeriodBack
799 from testmanager.core.testcaseargs import TestCaseArgsLogic;
800 self.aoDepTestCases = TestCaseDependencyLogic(oDb).getDepTestCaseData(self.idTestCase, tsNow);
801 self.aoDepGlobalResources = TestCaseGlobalRsrcDepLogic(oDb).getDepGlobalResourceData(self.idTestCase, tsNow);
802 self.aoTestCaseArgs = TestCaseArgsLogic(oDb).getTestCaseArgs(self.idTestCase, tsNow);
803 # Note! The above arrays are sorted by their relvant IDs for fetchForChangeLog's sake.
804 return self;
805
806 def initFromDbRowEx(self, aoRow, oDb, tsNow = None):
807 """
808 Reinitialize from a SELECT * FROM TestCases row. Will query the
809 necessary additional data from oDb using tsNow.
810 Returns self. Raises exception if no row or database error.
811 """
812 TestCaseData.initFromDbRow(self, aoRow);
813 return self._initExtraMembersFromDb(oDb, tsNow);
814
815 def initFromDbWithId(self, oDb, idTestCase, tsNow = None, sPeriodBack = None):
816 """
817 Initialize the object from the database.
818 """
819 TestCaseData.initFromDbWithId(self, oDb, idTestCase, tsNow, sPeriodBack);
820 return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack);
821
822 def initFromDbWithGenId(self, oDb, idGenTestCase, tsNow = None):
823 """
824 Initialize the object from the database.
825 """
826 TestCaseData.initFromDbWithGenId(self, oDb, idGenTestCase);
827 if tsNow is None and not oDb.isTsInfinity(self.tsExpire):
828 tsNow = self.tsEffective;
829 return self._initExtraMembersFromDb(oDb, tsNow);
830
831 def getAttributeParamNullValues(self, sAttr):
832 if sAttr in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
833 return [[], ''];
834 return TestCaseData.getAttributeParamNullValues(self, sAttr);
835
836 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
837 """For dealing with the arrays."""
838 if sAttr not in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
839 return TestCaseData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict);
840
841 aoNewValues = [];
842 if sAttr == 'aoDepTestCases':
843 for idTestCase in oDisp.getListOfIntParams(sParam, 1, 0x7ffffffe, []):
844 oDep = TestCaseData();
845 oDep.idTestCase = str(idTestCase);
846 aoNewValues.append(oDep);
847
848 elif sAttr == 'aoDepGlobalResources':
849 for idGlobalRsrc in oDisp.getListOfIntParams(sParam, 1, 0x7ffffffe, []):
850 oGlobalRsrc = GlobalResourceData();
851 oGlobalRsrc.idGlobalRsrc = str(idGlobalRsrc);
852 aoNewValues.append(oGlobalRsrc);
853
854 elif sAttr == 'aoTestCaseArgs':
855 from testmanager.core.testcaseargs import TestCaseArgsData;
856 for sArgKey in oDisp.getStringParam(TestCaseDataEx.ksParam_aoTestCaseArgs, sDefault = '').split(','):
857 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (TestCaseDataEx.ksParam_aoTestCaseArgs, sArgKey,))
858 aoNewValues.append(TestCaseArgsData().initFromParams(oDispWrapper, fStrict = False));
859 return aoNewValues;
860
861 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): # pylint: disable=too-many-locals
862 """
863 Validate special arrays and requirement expressions.
864
865 For the two dependency arrays we have to supply missing bits by
866 looking them up in the database. In the argument variation case we
867 need to validate each item.
868 """
869 if sAttr not in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
870 return TestCaseData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
871
872 asErrors = [];
873 aoNewValues = [];
874 if sAttr == 'aoDepTestCases':
875 for oTestCase in self.aoDepTestCases:
876 if utils.isString(oTestCase.idTestCase): # Stored as string convertParamToAttribute.
877 oTestCase = copy.copy(oTestCase);
878 try:
879 oTestCase.idTestCase = int(oTestCase.idTestCase);
880 oTestCase.initFromDbWithId(oDb, oTestCase.idTestCase);
881 except Exception as oXcpt:
882 asErrors.append('Test case dependency #%s: %s' % (oTestCase.idTestCase, oXcpt));
883 aoNewValues.append(oTestCase);
884
885 elif sAttr == 'aoDepGlobalResources':
886 for oGlobalRsrc in self.aoDepGlobalResources:
887 if utils.isString(oGlobalRsrc.idGlobalRsrc): # Stored as string convertParamToAttribute.
888 oGlobalRsrc = copy.copy(oGlobalRsrc);
889 try:
890 oGlobalRsrc.idTestCase = int(oGlobalRsrc.idGlobalRsrc);
891 oGlobalRsrc.initFromDbWithId(oDb, oGlobalRsrc.idGlobalRsrc);
892 except Exception as oXcpt:
893 asErrors.append('Resource dependency #%s: %s' % (oGlobalRsrc.idGlobalRsrc, oXcpt));
894 aoNewValues.append(oGlobalRsrc);
895
896 else:
897 assert sAttr == 'aoTestCaseArgs';
898 if not self.aoTestCaseArgs:
899 return (None, 'The testcase requires at least one argument variation to be valid.');
900
901 # Note! We'll be returning an error dictionary instead of an string here.
902 dErrors = {};
903
904 for iVar in range(len(self.aoTestCaseArgs)):
905 oVar = copy.copy(self.aoTestCaseArgs[iVar]);
906 oVar.idTestCase = self.idTestCase;
907 dCurErrors = oVar.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other);
908 if not dCurErrors:
909 pass; ## @todo figure out the ID?
910 else:
911 asErrors = [];
912 for sKey in dCurErrors:
913 asErrors.append('%s: %s' % (sKey[len('TestCaseArgs_'):], dCurErrors[sKey]));
914 dErrors[iVar] = '<br>\n'.join(asErrors)
915 aoNewValues.append(oVar);
916
917 for iVar in range(len(self.aoTestCaseArgs)):
918 sArgs = self.aoTestCaseArgs[iVar].sArgs;
919 for iVar2 in range(iVar + 1, len(self.aoTestCaseArgs)):
920 if self.aoTestCaseArgs[iVar2].sArgs == sArgs:
921 sMsg = 'Duplicate argument variation "%s".' % (sArgs);
922 if iVar in dErrors: dErrors[iVar] += '<br>\n' + sMsg;
923 else: dErrors[iVar] = sMsg;
924 if iVar2 in dErrors: dErrors[iVar2] += '<br>\n' + sMsg;
925 else: dErrors[iVar2] = sMsg;
926 break;
927
928 return (aoNewValues, dErrors if dErrors else None);
929
930 return (aoNewValues, None if not asErrors else ' <br>'.join(asErrors));
931
932 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
933 dErrors = TestCaseData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
934
935 # Validate dependencies a wee bit for paranoid reasons. The scheduler
936 # queue generation code does the real validation here!
937 if not dErrors and self.idTestCase is not None:
938 for oDep in self.aoDepTestCases:
939 if oDep.idTestCase == self.idTestCase:
940 if self.ksParam_aoDepTestCases in dErrors:
941 dErrors[self.ksParam_aoDepTestCases] += ' Depending on itself!';
942 else:
943 dErrors[self.ksParam_aoDepTestCases] = 'Depending on itself!';
944 return dErrors;
945
946
947
948
949
950class TestCaseLogic(ModelLogicBase):
951 """
952 Test case management logic.
953 """
954
955 def __init__(self, oDb):
956 ModelLogicBase.__init__(self, oDb)
957 self.dCache = None;
958
959 def getAll(self):
960 """
961 Fetches all test case records from DB (TestCaseData).
962 """
963 self._oDb.execute('SELECT *\n'
964 'FROM TestCases\n'
965 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
966 'ORDER BY idTestCase ASC;')
967
968 aaoRows = self._oDb.fetchAll()
969 aoRet = [];
970 for aoRow in aaoRows:
971 aoRet.append(TestCaseData().initFromDbRow(aoRow))
972 return aoRet
973
974 def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None):
975 """
976 Fetches test cases.
977
978 Returns an array (list) of TestCaseDataEx items, empty list if none.
979 Raises exception on error.
980 """
981 _ = aiSortColumns;
982 if tsNow is None:
983 self._oDb.execute('SELECT *\n'
984 'FROM TestCases\n'
985 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
986 'ORDER BY sName ASC\n'
987 'LIMIT %s OFFSET %s\n'
988 , (cMaxRows, iStart, ));
989 else:
990 self._oDb.execute('SELECT *\n'
991 'FROM TestCases\n'
992 'WHERE tsExpire > %s\n'
993 ' AND tsEffective <= %s\n'
994 'ORDER BY sName ASC\n'
995 'LIMIT %s OFFSET %s\n'
996 , (tsNow, tsNow, cMaxRows, iStart, ));
997
998 aoRows = [];
999 for aoRow in self._oDb.fetchAll():
1000 aoRows.append(TestCaseDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow));
1001 return aoRows;
1002
1003 def fetchForChangeLog(self, idTestCase, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals
1004 """
1005 Fetches change log entries for a testbox.
1006
1007 Returns an array of ChangeLogEntry instance and an indicator whether
1008 there are more entries.
1009 Raises exception on error.
1010 """
1011
1012 if tsNow is None:
1013 tsNow = self._oDb.getCurrentTimestamp();
1014
1015 # 1. Get a list of the relevant change times.
1016 self._oDb.execute('( SELECT tsEffective, uidAuthor FROM TestCases WHERE idTestCase = %s AND tsEffective <= %s )\n'
1017 'UNION\n'
1018 '( SELECT tsEffective, uidAuthor FROM TestCaseArgs WHERE idTestCase = %s AND tsEffective <= %s )\n'
1019 'UNION\n'
1020 '( SELECT tsEffective, uidAuthor FROM TestCaseDeps WHERE idTestCase = %s AND tsEffective <= %s )\n'
1021 'UNION\n'
1022 '( SELECT tsEffective, uidAuthor FROM TestCaseGlobalRsrcDeps \n' \
1023 ' WHERE idTestCase = %s AND tsEffective <= %s )\n'
1024 'ORDER BY tsEffective DESC\n'
1025 'LIMIT %s OFFSET %s\n'
1026 , ( idTestCase, tsNow,
1027 idTestCase, tsNow,
1028 idTestCase, tsNow,
1029 idTestCase, tsNow,
1030 cMaxRows + 1, iStart, ));
1031 aaoChanges = self._oDb.fetchAll();
1032
1033 # 2. Collect data sets for each of those points.
1034 # (Doing it the lazy + inefficient way for now.)
1035 aoRows = [];
1036 for aoChange in aaoChanges:
1037 aoRows.append(TestCaseDataEx().initFromDbWithId(self._oDb, idTestCase, aoChange[0]));
1038
1039 # 3. Calculate the changes.
1040 aoEntries = [];
1041 for i in range(0, len(aoRows) - 1):
1042 oNew = aoRows[i];
1043 oOld = aoRows[i + 1];
1044 (tsEffective, uidAuthor) = aaoChanges[i];
1045 (tsExpire, _) = aaoChanges[i - 1] if i > 0 else (oNew.tsExpire, None)
1046 assert self._oDb.isTsInfinity(tsEffective) != self._oDb.isTsInfinity(tsExpire) or tsEffective < tsExpire, \
1047 '%s vs %s' % (tsEffective, tsExpire);
1048
1049 aoChanges = [];
1050
1051 # The testcase object.
1052 if oNew.tsEffective != oOld.tsEffective:
1053 for sAttr in oNew.getDataAttributes():
1054 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', \
1055 'aoTestCaseArgs', 'aoDepTestCases', 'aoDepGlobalResources']:
1056 oOldAttr = getattr(oOld, sAttr);
1057 oNewAttr = getattr(oNew, sAttr);
1058 if oOldAttr != oNewAttr:
1059 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
1060
1061 # The argument variations.
1062 iChildOld = 0;
1063 for oChildNew in oNew.aoTestCaseArgs:
1064 # Locate the old entry, emitting removed markers for old items we have to skip.
1065 while iChildOld < len(oOld.aoTestCaseArgs) \
1066 and oOld.aoTestCaseArgs[iChildOld].idTestCaseArgs < oChildNew.idTestCaseArgs:
1067 oChildOld = oOld.aoTestCaseArgs[iChildOld];
1068 aoChanges.append(AttributeChangeEntry('Variation #%s' % (oChildOld.idTestCaseArgs,),
1069 None, oChildOld, 'Removed', str(oChildOld)));
1070 iChildOld += 1;
1071
1072 if iChildOld < len(oOld.aoTestCaseArgs) \
1073 and oOld.aoTestCaseArgs[iChildOld].idTestCaseArgs == oChildNew.idTestCaseArgs:
1074 oChildOld = oOld.aoTestCaseArgs[iChildOld];
1075 if oChildNew.tsEffective != oChildOld.tsEffective:
1076 for sAttr in oChildNew.getDataAttributes():
1077 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestCase', ]:
1078 oOldAttr = getattr(oChildOld, sAttr);
1079 oNewAttr = getattr(oChildNew, sAttr);
1080 if oOldAttr != oNewAttr:
1081 aoChanges.append(AttributeChangeEntry('Variation[#%s].%s'
1082 % (oChildOld.idTestCaseArgs, sAttr,),
1083 oNewAttr, oOldAttr,
1084 str(oNewAttr), str(oOldAttr)));
1085 iChildOld += 1;
1086 else:
1087 aoChanges.append(AttributeChangeEntry('Variation #%s' % (oChildNew.idTestCaseArgs,),
1088 oChildNew, None,
1089 str(oChildNew), 'Did not exist'));
1090
1091 # The testcase dependencies.
1092 iChildOld = 0;
1093 for oChildNew in oNew.aoDepTestCases:
1094 # Locate the old entry, emitting removed markers for old items we have to skip.
1095 while iChildOld < len(oOld.aoDepTestCases) \
1096 and oOld.aoDepTestCases[iChildOld].idTestCase < oChildNew.idTestCase:
1097 oChildOld = oOld.aoDepTestCases[iChildOld];
1098 aoChanges.append(AttributeChangeEntry('Dependency #%s' % (oChildOld.idTestCase,),
1099 None, oChildOld, 'Removed',
1100 '%s (#%u)' % (oChildOld.sName, oChildOld.idTestCase,)));
1101 iChildOld += 1;
1102 if iChildOld < len(oOld.aoDepTestCases) \
1103 and oOld.aoDepTestCases[iChildOld].idTestCase == oChildNew.idTestCase:
1104 iChildOld += 1;
1105 else:
1106 aoChanges.append(AttributeChangeEntry('Dependency #%s' % (oChildNew.idTestCase,),
1107 oChildNew, None,
1108 '%s (#%u)' % (oChildNew.sName, oChildNew.idTestCase,),
1109 'Did not exist'));
1110
1111 # The global resource dependencies.
1112 iChildOld = 0;
1113 for oChildNew in oNew.aoDepGlobalResources:
1114 # Locate the old entry, emitting removed markers for old items we have to skip.
1115 while iChildOld < len(oOld.aoDepGlobalResources) \
1116 and oOld.aoDepGlobalResources[iChildOld].idGlobalRsrc < oChildNew.idGlobalRsrc:
1117 oChildOld = oOld.aoDepGlobalResources[iChildOld];
1118 aoChanges.append(AttributeChangeEntry('Global Resource #%s' % (oChildOld.idGlobalRsrc,),
1119 None, oChildOld, 'Removed',
1120 '%s (#%u)' % (oChildOld.sName, oChildOld.idGlobalRsrc,)));
1121 iChildOld += 1;
1122 if iChildOld < len(oOld.aoDepGlobalResources) \
1123 and oOld.aoDepGlobalResources[iChildOld].idGlobalRsrc == oChildNew.idGlobalRsrc:
1124 iChildOld += 1;
1125 else:
1126 aoChanges.append(AttributeChangeEntry('Global Resource #%s' % (oChildNew.idGlobalRsrc,),
1127 oChildNew, None,
1128 '%s (#%u)' % (oChildNew.sName, oChildNew.idGlobalRsrc,),
1129 'Did not exist'));
1130
1131 # Done.
1132 aoEntries.append(ChangeLogEntry(uidAuthor, None, tsEffective, tsExpire, oNew, oOld, aoChanges));
1133
1134 # If we're at the end of the log, add the initial entry.
1135 if len(aoRows) <= cMaxRows and aoRows:
1136 oNew = aoRows[-1];
1137 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None,
1138 aaoChanges[-1][0], aaoChanges[-2][0] if len(aaoChanges) > 1 else oNew.tsExpire,
1139 oNew, None, []));
1140
1141 return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aoRows) > cMaxRows);
1142
1143
1144 def addEntry(self, oData, uidAuthor, fCommit = False):
1145 """
1146 Add a new testcase to the DB.
1147 """
1148
1149 #
1150 # Validate the input first.
1151 #
1152 assert isinstance(oData, TestCaseDataEx);
1153 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add);
1154 if dErrors:
1155 raise TMInvalidData('Invalid input data: %s' % (dErrors,));
1156
1157 #
1158 # Add the testcase.
1159 #
1160 self._oDb.callProc('TestCaseLogic_addEntry',
1161 ( uidAuthor, oData.sName, oData.sDescription, oData.fEnabled, oData.cSecTimeout,
1162 oData.sTestBoxReqExpr, oData.sBuildReqExpr, oData.sBaseCmd, oData.sValidationKitZips,
1163 oData.sComment ));
1164 oData.idTestCase = self._oDb.fetchOne()[0];
1165
1166 # Add testcase dependencies.
1167 for oDep in oData.aoDepTestCases:
1168 self._oDb.execute('INSERT INTO TestCaseDeps (idTestCase, idTestCasePreReq, uidAuthor) VALUES (%s, %s, %s)'
1169 , (oData.idTestCase, oDep.idTestCase, uidAuthor))
1170
1171 # Add global resource dependencies.
1172 for oDep in oData.aoDepGlobalResources:
1173 self._oDb.execute('INSERT INTO TestCaseGlobalRsrcDeps (idTestCase, idGlobalRsrc, uidAuthor) VALUES (%s, %s, %s)'
1174 , (oData.idTestCase, oDep.idGlobalRsrc, uidAuthor))
1175
1176 # Set Test Case Arguments variations
1177 for oVar in oData.aoTestCaseArgs:
1178 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1179 ' idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1180 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1181 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)'
1182 , ( oData.idTestCase, uidAuthor, oVar.sArgs, oVar.cSecTimeout,
1183 oVar.sTestBoxReqExpr, oVar.sBuildReqExpr, oVar.cGangMembers, oVar.sSubName, ));
1184
1185 self._oDb.maybeCommit(fCommit);
1186 return True;
1187
1188 def editEntry(self, oData, uidAuthor, fCommit = False): # pylint: disable=too-many-locals
1189 """
1190 Edit a testcase entry (extended).
1191 Caller is expected to rollback the database transactions on exception.
1192 """
1193
1194 #
1195 # Validate the input.
1196 #
1197 assert isinstance(oData, TestCaseDataEx);
1198 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
1199 if dErrors:
1200 raise TMInvalidData('Invalid input data: %s' % (dErrors,));
1201
1202 #
1203 # Did anything change? If not return straight away.
1204 #
1205 oOldDataEx = TestCaseDataEx().initFromDbWithId(self._oDb, oData.idTestCase);
1206 if oOldDataEx.isEqual(oData):
1207 self._oDb.maybeCommit(fCommit);
1208 return True;
1209
1210 #
1211 # Make the necessary changes.
1212 #
1213
1214 # The test case itself.
1215 if not TestCaseData().initFromOther(oOldDataEx).isEqual(oData):
1216 self._oDb.callProc('TestCaseLogic_editEntry', ( uidAuthor, oData.idTestCase, oData.sName, oData.sDescription,
1217 oData.fEnabled, oData.cSecTimeout, oData.sTestBoxReqExpr,
1218 oData.sBuildReqExpr, oData.sBaseCmd, oData.sValidationKitZips,
1219 oData.sComment ));
1220 oData.idGenTestCase = self._oDb.fetchOne()[0];
1221
1222 #
1223 # Its dependencies on other testcases.
1224 #
1225 aidNewDeps = [oDep.idTestCase for oDep in oData.aoDepTestCases];
1226 aidOldDeps = [oDep.idTestCase for oDep in oOldDataEx.aoDepTestCases];
1227
1228 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseDeps\n'
1229 'SET tsExpire = CURRENT_TIMESTAMP\n'
1230 'WHERE idTestCase = %s\n'
1231 ' AND tsExpire = \'infinity\'::timestamp\n'
1232 , (oData.idTestCase,));
1233 asKeepers = [];
1234 for idDep in aidOldDeps:
1235 if idDep in aidNewDeps:
1236 asKeepers.append(str(idDep));
1237 if asKeepers:
1238 sQuery += ' AND idTestCasePreReq NOT IN (' + ', '.join(asKeepers) + ')\n';
1239 self._oDb.execute(sQuery);
1240
1241 for idDep in aidNewDeps:
1242 if idDep not in aidOldDeps:
1243 self._oDb.execute('INSERT INTO TestCaseDeps (idTestCase, idTestCasePreReq, uidAuthor)\n'
1244 'VALUES (%s, %s, %s)\n'
1245 , (oData.idTestCase, idDep, uidAuthor) );
1246
1247 #
1248 # Its dependencies on global resources.
1249 #
1250 aidNewDeps = [oDep.idGlobalRsrc for oDep in oData.aoDepGlobalResources];
1251 aidOldDeps = [oDep.idGlobalRsrc for oDep in oOldDataEx.aoDepGlobalResources];
1252
1253 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseGlobalRsrcDeps\n'
1254 'SET tsExpire = CURRENT_TIMESTAMP\n'
1255 'WHERE idTestCase = %s\n'
1256 ' AND tsExpire = \'infinity\'::timestamp\n'
1257 , (oData.idTestCase,));
1258 asKeepers = [];
1259 for idDep in aidOldDeps:
1260 if idDep in aidNewDeps:
1261 asKeepers.append(str(idDep));
1262 if asKeepers:
1263 sQuery = ' AND idGlobalRsrc NOT IN (' + ', '.join(asKeepers) + ')\n';
1264 self._oDb.execute(sQuery);
1265
1266 for idDep in aidNewDeps:
1267 if idDep not in aidOldDeps:
1268 self._oDb.execute('INSERT INTO TestCaseGlobalRsrcDeps (idTestCase, idGlobalRsrc, uidAuthor)\n'
1269 'VALUES (%s, %s, %s)\n'
1270 , (oData.idTestCase, idDep, uidAuthor) );
1271
1272 #
1273 # Update Test Case Args
1274 # Note! Primary key is idTestCase, tsExpire, sArgs.
1275 #
1276
1277 # Historize rows that have been removed.
1278 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseArgs\n'
1279 'SET tsExpire = CURRENT_TIMESTAMP\n'
1280 'WHERE idTestCase = %s\n'
1281 ' AND tsExpire = \'infinity\'::TIMESTAMP'
1282 , (oData.idTestCase, ));
1283 for oNewVar in oData.aoTestCaseArgs:
1284 asKeepers.append(self._oDb.formatBindArgs('%s', (oNewVar.sArgs,)));
1285 if asKeepers:
1286 sQuery += ' AND sArgs NOT IN (' + ', '.join(asKeepers) + ')\n';
1287 self._oDb.execute(sQuery);
1288
1289 # Add new TestCaseArgs records if necessary, reusing old IDs when possible.
1290 from testmanager.core.testcaseargs import TestCaseArgsData;
1291 for oNewVar in oData.aoTestCaseArgs:
1292 self._oDb.execute('SELECT *\n'
1293 'FROM TestCaseArgs\n'
1294 'WHERE idTestCase = %s\n'
1295 ' AND sArgs = %s\n'
1296 'ORDER BY tsExpire DESC\n'
1297 'LIMIT 1\n'
1298 , (oData.idTestCase, oNewVar.sArgs,));
1299 aoRow = self._oDb.fetchOne();
1300 if aoRow is None:
1301 # New
1302 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1303 ' idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1304 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1305 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)'
1306 , ( oData.idTestCase, uidAuthor, oNewVar.sArgs, oNewVar.cSecTimeout,
1307 oNewVar.sTestBoxReqExpr, oNewVar.sBuildReqExpr, oNewVar.cGangMembers, oNewVar.sSubName));
1308 else:
1309 oCurVar = TestCaseArgsData().initFromDbRow(aoRow);
1310 if self._oDb.isTsInfinity(oCurVar.tsExpire):
1311 # Existing current entry, updated if changed.
1312 if oNewVar.cSecTimeout == oCurVar.cSecTimeout \
1313 and oNewVar.sTestBoxReqExpr == oCurVar.sTestBoxReqExpr \
1314 and oNewVar.sBuildReqExpr == oCurVar.sBuildReqExpr \
1315 and oNewVar.cGangMembers == oCurVar.cGangMembers \
1316 and oNewVar.sSubName == oCurVar.sSubName:
1317 oNewVar.idTestCaseArgs = oCurVar.idTestCaseArgs;
1318 oNewVar.idGenTestCaseArgs = oCurVar.idGenTestCaseArgs;
1319 continue; # Unchanged.
1320 self._oDb.execute('UPDATE TestCaseArgs SET tsExpire = CURRENT_TIMESTAMP WHERE idGenTestCaseArgs = %s\n'
1321 , (oCurVar.idGenTestCaseArgs, ));
1322 else:
1323 # Existing old entry, re-use the ID.
1324 pass;
1325 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1326 ' idTestCaseArgs, idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1327 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1328 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
1329 'RETURNING idGenTestCaseArgs\n'
1330 , ( oCurVar.idTestCaseArgs, oData.idTestCase, uidAuthor, oNewVar.sArgs, oNewVar.cSecTimeout,
1331 oNewVar.sTestBoxReqExpr, oNewVar.sBuildReqExpr, oNewVar.cGangMembers, oNewVar.sSubName));
1332 oNewVar.idGenTestCaseArgs = self._oDb.fetchOne()[0];
1333
1334 self._oDb.maybeCommit(fCommit);
1335 return True;
1336
1337 def removeEntry(self, uidAuthor, idTestCase, fCascade = False, fCommit = False):
1338 """ Deletes the test case if possible. """
1339 self._oDb.callProc('TestCaseLogic_delEntry', (uidAuthor, idTestCase, fCascade));
1340 self._oDb.maybeCommit(fCommit);
1341 return True
1342
1343
1344 def getTestCasePreReqIds(self, idTestCase, tsEffective = None, cMax = None):
1345 """
1346 Returns an array of prerequisite testcases (IDs) for the given testcase.
1347 May raise exception on database error or if the result exceeds cMax.
1348 """
1349 if tsEffective is None:
1350 self._oDb.execute('SELECT idTestCasePreReq\n'
1351 'FROM TestCaseDeps\n'
1352 'WHERE idTestCase = %s\n'
1353 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1354 'ORDER BY idTestCasePreReq\n'
1355 , (idTestCase,) );
1356 else:
1357 self._oDb.execute('SELECT idTestCasePreReq\n'
1358 'FROM TestCaseDeps\n'
1359 'WHERE idTestCase = %s\n'
1360 ' AND tsExpire > %s\n'
1361 ' AND tsEffective <= %s\n'
1362 'ORDER BY idTestCasePreReq\n'
1363 , (idTestCase, tsEffective, tsEffective) );
1364
1365
1366 if cMax is not None and self._oDb.getRowCount() > cMax:
1367 raise TMExceptionBase('Too many prerequisites for testcase %s: %s, max %s'
1368 % (idTestCase, cMax, self._oDb.getRowCount(),));
1369
1370 aidPreReqs = [];
1371 for aoRow in self._oDb.fetchAll():
1372 aidPreReqs.append(aoRow[0]);
1373 return aidPreReqs;
1374
1375
1376 def cachedLookup(self, idTestCase):
1377 """
1378 Looks up the most recent TestCaseDataEx object for idTestCase
1379 via an object cache.
1380
1381 Returns a shared TestCaseDataEx object. None if not found.
1382 Raises exception on DB error.
1383 """
1384 if self.dCache is None:
1385 self.dCache = self._oDb.getCache('TestCaseDataEx');
1386 oEntry = self.dCache.get(idTestCase, None);
1387 if oEntry is None:
1388 fNeedTsNow = False;
1389 self._oDb.execute('SELECT *\n'
1390 'FROM TestCases\n'
1391 'WHERE idTestCase = %s\n'
1392 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1393 , (idTestCase, ));
1394 if self._oDb.getRowCount() == 0:
1395 # Maybe it was deleted, try get the last entry.
1396 self._oDb.execute('SELECT *\n'
1397 'FROM TestCases\n'
1398 'WHERE idTestCase = %s\n'
1399 'ORDER BY tsExpire DESC\n'
1400 'LIMIT 1\n'
1401 , (idTestCase, ));
1402 fNeedTsNow = True;
1403 elif self._oDb.getRowCount() > 1:
1404 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestCase));
1405
1406 if self._oDb.getRowCount() == 1:
1407 aaoRow = self._oDb.fetchOne();
1408 oEntry = TestCaseDataEx();
1409 tsNow = oEntry.initFromDbRow(aaoRow).tsEffective if fNeedTsNow else None;
1410 oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow);
1411 self.dCache[idTestCase] = oEntry;
1412 return oEntry;
1413
1414
1415
1416#
1417# Unit testing.
1418#
1419
1420# pylint: disable=missing-docstring
1421class TestCaseGlobalRsrcDepDataTestCase(ModelDataBaseTestCase):
1422 def setUp(self):
1423 self.aoSamples = [TestCaseGlobalRsrcDepData(),];
1424
1425class TestCaseDataTestCase(ModelDataBaseTestCase):
1426 def setUp(self):
1427 self.aoSamples = [TestCaseData(),];
1428
1429 def testEmptyExpr(self):
1430 self.assertEqual(TestCaseData.validateTestBoxReqExpr(None), None);
1431 self.assertEqual(TestCaseData.validateTestBoxReqExpr(''), None);
1432
1433 def testSimpleExpr(self):
1434 self.assertEqual(TestCaseData.validateTestBoxReqExpr('cMbMemory > 10'), None);
1435 self.assertEqual(TestCaseData.validateTestBoxReqExpr('cMbScratch < 10'), None);
1436 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu'), None);
1437 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is True'), None);
1438 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is False'), None);
1439 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is None'), None);
1440 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(fChipsetIoMmu, bool)'), None);
1441 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(iTestBoxScriptRev, int)'), None);
1442 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(cMbScratch, long)'), None);
1443
1444 def testBadExpr(self):
1445 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('this is an bad expression, surely it must be'), None);
1446 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('x = 1 + 1'), None);
1447 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('__import__(\'os\').unlink(\'/tmp/no/such/file\')'), None);
1448 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('print "foobar"'), None);
1449
1450class TestCaseDataExTestCase(ModelDataBaseTestCase):
1451 def setUp(self):
1452 self.aoSamples = [TestCaseDataEx(),];
1453
1454if __name__ == '__main__':
1455 unittest.main();
1456 # not reached.
1457
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