VirtualBox

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