VirtualBox

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

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

testmanager: Adding sComment and fRawMode fields to TestBoxes and moves the strings into a separate shared string table.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 64.9 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testcase.py 61468 2016-06-05 02:55:32Z 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.215389.xyz. 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: 61468 $"
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 'fRawMode': False,
639 'cMbMemory': 985034,
640 'cMbScratch': 1234089,
641 'iTestBoxScriptRev': 1,
642 'sName': 'emanon',
643 'uuidSystem': '8FF81BE5-3901-4AB1-8A65-B48D511C0321',
644 },
645 {
646 'sOs': 'linux',
647 'sOsVersion': '3.1',
648 'sCpuVendor': 'VirtualBox',
649 'sCpuArch': 'amd64',
650 'cCpus': 8191,
651 'fCpuHwVirt': True,
652 'fCpuNestedPaging': True,
653 'fCpu64BitGuest': True,
654 'fChipsetIoMmu': True,
655 'fRawMode': True,
656 'cMbMemory': 9999999999,
657 'cMbScratch': 9999999999999,
658 'iTestBoxScriptRev': 9999999,
659 'sName': 'emanon',
660 'uuidSystem': '00000000-0000-0000-0000-000000000000',
661 },
662 ];
663 return TestCaseData._safelyValidateReqExpr(sExpr, adTestBoxes);
664
665 @staticmethod
666 def matchesTestBoxPropsEx(oTestBoxData, sExpr):
667 """ Worker for TestCaseData.matchesTestBoxProps and TestCaseArgsDataEx.matchesTestBoxProps. """
668 if sExpr is None:
669 return True;
670 dLocals = \
671 {
672 'sOs': oTestBoxData.sOs,
673 'sOsVersion': oTestBoxData.sOsVersion,
674 'sCpuVendor': oTestBoxData.sCpuVendor,
675 'sCpuArch': oTestBoxData.sCpuArch,
676 'cCpus': oTestBoxData.cCpus,
677 'fCpuHwVirt': oTestBoxData.fCpuHwVirt,
678 'fCpuNestedPaging': oTestBoxData.fCpuNestedPaging,
679 'fCpu64BitGuest': oTestBoxData.fCpu64BitGuest,
680 'fChipsetIoMmu': oTestBoxData.fChipsetIoMmu,
681 'fRawMode': oTestBoxData.fRawMode,
682 'cMbMemory': oTestBoxData.cMbMemory,
683 'cMbScratch': oTestBoxData.cMbScratch,
684 'iTestBoxScriptRev': oTestBoxData.iTestBoxScriptRev,
685 'iPythonHexVersion': oTestBoxData.iPythonHexVersion,
686 'sName': oTestBoxData.sName,
687 'uuidSystem': oTestBoxData.uuidSystem,
688 };
689 return TestCaseData._safelyEvalExpr(sExpr, dLocals);
690
691 @staticmethod
692 def validateBuildReqExpr(sExpr):
693 """
694 Validates a testbox expression, returning None on success and an error
695 string on failure.
696 """
697 adBuilds = \
698 [
699 {
700 'sProduct': 'VirtualBox',
701 'sBranch': 'trunk',
702 'sType': 'release',
703 'asOsArches': ['win.amd64', 'win.x86'],
704 'sVersion': '1.0',
705 'iRevision': 1234,
706 'uidAuthor': None,
707 'idBuild': 953,
708 },
709 {
710 'sProduct': 'VirtualBox',
711 'sBranch': 'VBox-4.1',
712 'sType': 'release',
713 'asOsArches': ['linux.x86',],
714 'sVersion': '4.2.15',
715 'iRevision': 89876,
716 'uidAuthor': None,
717 'idBuild': 945689,
718 },
719 {
720 'sProduct': 'VirtualBox',
721 'sBranch': 'VBox-4.1',
722 'sType': 'strict',
723 'asOsArches': ['solaris.x86', 'solaris.amd64',],
724 'sVersion': '4.3.0_RC3',
725 'iRevision': 97939,
726 'uidAuthor': 33,
727 'idBuild': 9456893,
728 },
729 ];
730 return TestCaseData._safelyValidateReqExpr(sExpr, adBuilds);
731
732 @staticmethod
733 def matchesBuildPropsEx(oBuildDataEx, sExpr):
734 """
735 Checks if the all of the build related test requirements matches the
736 given build.
737 """
738 if sExpr is None:
739 return True;
740 dLocals = \
741 {
742 'sProduct': oBuildDataEx.oCat.sProduct,
743 'sBranch': oBuildDataEx.oCat.sBranch,
744 'sType': oBuildDataEx.oCat.sType,
745 'asOsArches': oBuildDataEx.oCat.asOsArches,
746 'sVersion': oBuildDataEx.sVersion,
747 'iRevision': oBuildDataEx.iRevision,
748 'uidAuthor': oBuildDataEx.uidAuthor,
749 'idBuild': oBuildDataEx.idBuild,
750 };
751 return TestCaseData._safelyEvalExpr(sExpr, dLocals);
752
753
754
755
756class TestCaseDataEx(TestCaseData):
757 """
758 Test case data.
759 """
760
761 ksParam_aoTestCaseArgs = 'TestCase_aoTestCaseArgs';
762 ksParam_aoDepTestCases = 'TestCase_aoDepTestCases';
763 ksParam_aoDepGlobalResources = 'TestCase_aoDepGlobalResources';
764
765 # Use [] instead of None.
766 kasAltArrayNull = [ 'aoTestCaseArgs', 'aoDepTestCases', 'aoDepGlobalResources' ];
767
768
769 def __init__(self):
770 TestCaseData.__init__(self);
771
772 # List of objects of type TestCaseData (or TestCaseDataEx, we don't
773 # care) on which current Test Case depends.
774 self.aoDepTestCases = [];
775
776 # List of objects of type GlobalResourceData on which current Test Case depends.
777 self.aoDepGlobalResources = [];
778
779 # List of objects of type TestCaseArgsData.
780 self.aoTestCaseArgs = [];
781
782 def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None):
783 """
784 Worker shared by the initFromDb* methods.
785 Returns self. Raises exception if no row or database error.
786 """
787 _ = sPeriodBack; ## @todo sPeriodBack
788 from testmanager.core.testcaseargs import TestCaseArgsLogic;
789 self.aoDepTestCases = TestCaseDependencyLogic(oDb).getDepTestCaseData(self.idTestCase, tsNow);
790 self.aoDepGlobalResources = TestCaseGlobalRsrcDepLogic(oDb).getDepGlobalResourceData(self.idTestCase, tsNow);
791 self.aoTestCaseArgs = TestCaseArgsLogic(oDb).getTestCaseArgs(self.idTestCase, tsNow);
792 # Note! The above arrays are sorted by their relvant IDs for fetchForChangeLog's sake.
793 return self;
794
795 def initFromDbRowEx(self, aoRow, oDb, tsNow = None):
796 """
797 Reinitialize from a SELECT * FROM TestCases row. Will query the
798 necessary additional data from oDb using tsNow.
799 Returns self. Raises exception if no row or database error.
800 """
801 TestCaseData.initFromDbRow(self, aoRow);
802 return self._initExtraMembersFromDb(oDb, tsNow);
803
804 def initFromDbWithId(self, oDb, idTestCase, tsNow = None, sPeriodBack = None):
805 """
806 Initialize the object from the database.
807 """
808 TestCaseData.initFromDbWithId(self, oDb, idTestCase, tsNow, sPeriodBack);
809 return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack);
810
811 def initFromDbWithGenId(self, oDb, idGenTestCase, tsNow = None):
812 """
813 Initialize the object from the database.
814 """
815 TestCaseData.initFromDbWithGenId(self, oDb, idGenTestCase);
816 if tsNow is None and not oDb.isTsInfinity(self.tsExpire):
817 tsNow = self.tsEffective;
818 return self._initExtraMembersFromDb(oDb, tsNow);
819
820 def getAttributeParamNullValues(self, sAttr):
821 if sAttr in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
822 return [[], ''];
823 return TestCaseData.getAttributeParamNullValues(self, sAttr);
824
825 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
826 """For dealing with the arrays."""
827 if sAttr not in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
828 return TestCaseData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict);
829
830 aoNewValues = [];
831 if sAttr == 'aoDepTestCases':
832 for idTestCase in oDisp.getListOfIntParams(sParam, 1, 0x7ffffffe, []):
833 oDep = TestCaseData();
834 oDep.idTestCase = str(idTestCase);
835 aoNewValues.append(oDep);
836
837 elif sAttr == 'aoDepGlobalResources':
838 for idGlobalRsrc in oDisp.getListOfIntParams(sParam, 1, 0x7ffffffe, []):
839 oGlobalRsrc = GlobalResourceData();
840 oGlobalRsrc.idGlobalRsrc = str(idGlobalRsrc);
841 aoNewValues.append(oGlobalRsrc);
842
843 elif sAttr == 'aoTestCaseArgs':
844 from testmanager.core.testcaseargs import TestCaseArgsData;
845 for sArgKey in oDisp.getStringParam(TestCaseDataEx.ksParam_aoTestCaseArgs, sDefault = '').split(','):
846 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (TestCaseDataEx.ksParam_aoTestCaseArgs, sArgKey,))
847 aoNewValues.append(TestCaseArgsData().initFromParams(oDispWrapper, fStrict = False));
848 return aoNewValues;
849
850 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): # pylint: disable=R0914
851 """
852 Validate special arrays and requirement expressions.
853
854 For the two dependency arrays we have to supply missing bits by
855 looking them up in the database. In the argument variation case we
856 need to validate each item.
857 """
858 if sAttr not in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
859 return TestCaseData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
860
861 asErrors = [];
862 aoNewValues = [];
863 if sAttr == 'aoDepTestCases':
864 for oTestCase in self.aoDepTestCases:
865 if utils.isString(oTestCase.idTestCase): # Stored as string convertParamToAttribute.
866 oTestCase = copy.copy(oTestCase);
867 try:
868 oTestCase.idTestCase = int(oTestCase.idTestCase);
869 oTestCase.initFromDbWithId(oDb, oTestCase.idTestCase);
870 except Exception, oXcpt:
871 asErrors.append('Test case dependency #%s: %s' % (oTestCase.idTestCase, oXcpt));
872 aoNewValues.append(oTestCase);
873
874 elif sAttr == 'aoDepGlobalResources':
875 for oGlobalRsrc in self.aoDepGlobalResources:
876 if utils.isString(oGlobalRsrc.idGlobalRsrc): # Stored as string convertParamToAttribute.
877 oGlobalRsrc = copy.copy(oGlobalRsrc);
878 try:
879 oGlobalRsrc.idTestCase = int(oGlobalRsrc.idGlobalRsrc);
880 oGlobalRsrc.initFromDbWithId(oDb, oGlobalRsrc.idGlobalRsrc);
881 except Exception, oXcpt:
882 asErrors.append('Resource dependency #%s: %s' % (oGlobalRsrc.idGlobalRsrc, oXcpt));
883 aoNewValues.append(oGlobalRsrc);
884
885 else:
886 assert sAttr == 'aoTestCaseArgs';
887 if self.aoTestCaseArgs is None or len(self.aoTestCaseArgs) == 0:
888 return (None, 'The testcase requires at least one argument variation to be valid.');
889
890 # Note! We'll be returning an error dictionary instead of an string here.
891 dErrors = {};
892
893 for iVar in range(len(self.aoTestCaseArgs)):
894 oVar = copy.copy(self.aoTestCaseArgs[iVar]);
895 oVar.idTestCase = self.idTestCase;
896 dCurErrors = oVar.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other);
897 if len(dCurErrors) == 0:
898 pass; ## @todo figure out the ID?
899 else:
900 asErrors = [];
901 for sKey in dCurErrors:
902 asErrors.append('%s: %s' % (sKey[len('TestCaseArgs_'):], dCurErrors[sKey]));
903 dErrors[iVar] = '<br>\n'.join(asErrors)
904 aoNewValues.append(oVar);
905
906 for iVar in range(len(self.aoTestCaseArgs)):
907 sArgs = self.aoTestCaseArgs[iVar].sArgs;
908 for iVar2 in range(iVar + 1, len(self.aoTestCaseArgs)):
909 if self.aoTestCaseArgs[iVar2].sArgs == sArgs:
910 sMsg = 'Duplicate argument variation "%s".' % (sArgs);
911 if iVar in dErrors: dErrors[iVar] += '<br>\n' + sMsg;
912 else: dErrors[iVar] = sMsg;
913 if iVar2 in dErrors: dErrors[iVar2] += '<br>\n' + sMsg;
914 else: dErrors[iVar2] = sMsg;
915 break;
916
917 return (aoNewValues, dErrors if len(dErrors) > 0 else None);
918
919 return (aoNewValues, None if len(asErrors) == 0 else ' <br>'.join(asErrors));
920
921 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
922 dErrors = TestCaseData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
923
924 # Validate dependencies a wee bit for paranoid reasons. The scheduler
925 # queue generation code does the real validation here!
926 if len(dErrors) == 0 and self.idTestCase is not None:
927 for oDep in self.aoDepTestCases:
928 if oDep.idTestCase == self.idTestCase:
929 if self.ksParam_aoDepTestCases in dErrors:
930 dErrors[self.ksParam_aoDepTestCases] += ' Depending on itself!';
931 else:
932 dErrors[self.ksParam_aoDepTestCases] = 'Depending on itself!';
933 return dErrors;
934
935
936
937
938
939class TestCaseLogic(ModelLogicBase):
940 """
941 Test case management logic.
942 """
943
944 def __init__(self, oDb):
945 ModelLogicBase.__init__(self, oDb)
946 self.dCache = None;
947
948 def getAll(self):
949 """
950 Fetches all test case records from DB (TestCaseData).
951 """
952 self._oDb.execute('SELECT *\n'
953 'FROM TestCases\n'
954 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
955 'ORDER BY idTestCase ASC;')
956
957 aaoRows = self._oDb.fetchAll()
958 aoRet = [];
959 for aoRow in aaoRows:
960 aoRet.append(TestCaseData().initFromDbRow(aoRow))
961 return aoRet
962
963 def fetchForListing(self, iStart, cMaxRows, tsNow):
964 """
965 Fetches test cases.
966
967 Returns an array (list) of TestCaseDataEx items, empty list if none.
968 Raises exception on error.
969 """
970 if tsNow is None:
971 self._oDb.execute('SELECT *\n'
972 'FROM TestCases\n'
973 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
974 'ORDER BY sName ASC\n'
975 'LIMIT %s OFFSET %s\n'
976 , (cMaxRows, iStart, ));
977 else:
978 self._oDb.execute('SELECT *\n'
979 'FROM TestCases\n'
980 'WHERE tsExpire > %s\n'
981 ' AND tsEffective <= %s\n'
982 'ORDER BY sName ASC\n'
983 'LIMIT %s OFFSET %s\n'
984 , (tsNow, tsNow, cMaxRows, iStart, ));
985
986 aoRows = [];
987 for aoRow in self._oDb.fetchAll():
988 aoRows.append(TestCaseDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow));
989 return aoRows;
990
991 def fetchForChangeLog(self, idTestCase, iStart, cMaxRows, tsNow): # pylint: disable=R0914
992 """
993 Fetches change log entries for a testbox.
994
995 Returns an array of ChangeLogEntry instance and an indicator whether
996 there are more entries.
997 Raises exception on error.
998 """
999
1000 if tsNow is None:
1001 tsNow = self._oDb.getCurrentTimestamp();
1002
1003 # 1. Get a list of the relevant change times.
1004 self._oDb.execute('( SELECT tsEffective, uidAuthor FROM TestCases WHERE idTestCase = %s AND tsEffective <= %s )\n'
1005 'UNION\n'
1006 '( SELECT tsEffective, uidAuthor FROM TestCaseArgs WHERE idTestCase = %s AND tsEffective <= %s )\n'
1007 'UNION\n'
1008 '( SELECT tsEffective, uidAuthor FROM TestCaseDeps WHERE idTestCase = %s AND tsEffective <= %s )\n'
1009 'UNION\n'
1010 '( SELECT tsEffective, uidAuthor FROM TestCaseGlobalRsrcDeps \n' \
1011 ' WHERE idTestCase = %s AND tsEffective <= %s )\n'
1012 'ORDER BY tsEffective DESC\n'
1013 'LIMIT %s OFFSET %s\n'
1014 , ( idTestCase, tsNow,
1015 idTestCase, tsNow,
1016 idTestCase, tsNow,
1017 idTestCase, tsNow,
1018 cMaxRows + 1, iStart, ));
1019 aaoChanges = self._oDb.fetchAll();
1020
1021 # 2. Collect data sets for each of those points.
1022 # (Doing it the lazy + inefficient way for now.)
1023 aoRows = [];
1024 for aoChange in aaoChanges:
1025 aoRows.append(TestCaseDataEx().initFromDbWithId(self._oDb, idTestCase, aoChange[0]));
1026
1027 # 3. Calculate the changes.
1028 aoEntries = [];
1029 for i in range(0, len(aoRows) - 1):
1030 oNew = aoRows[i];
1031 oOld = aoRows[i + 1];
1032 (tsEffective, uidAuthor) = aaoChanges[i];
1033 (tsExpire, _) = aaoChanges[i - 1] if i > 0 else (oNew.tsExpire, None)
1034 assert self._oDb.isTsInfinity(tsEffective) != self._oDb.isTsInfinity(tsExpire) or tsEffective < tsExpire, \
1035 '%s vs %s' % (tsEffective, tsExpire);
1036
1037 aoChanges = [];
1038
1039 # The testcase object.
1040 if oNew.tsEffective != oOld.tsEffective:
1041 for sAttr in oNew.getDataAttributes():
1042 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', \
1043 'aoTestCaseArgs', 'aoDepTestCases', 'aoDepGlobalResources']:
1044 oOldAttr = getattr(oOld, sAttr);
1045 oNewAttr = getattr(oNew, sAttr);
1046 if oOldAttr != oNewAttr:
1047 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
1048
1049 # The argument variations.
1050 iChildOld = 0;
1051 for oChildNew in oNew.aoTestCaseArgs:
1052 # Locate the old entry, emitting removed markers for old items we have to skip.
1053 while iChildOld < len(oOld.aoTestCaseArgs) \
1054 and oOld.aoTestCaseArgs[iChildOld].idTestCaseArgs < oChildNew.idTestCaseArgs:
1055 oChildOld = oOld.aoTestCaseArgs[iChildOld];
1056 aoChanges.append(AttributeChangeEntry('Variation #%s' % (oChildOld.idTestCaseArgs,),
1057 None, oChildOld, 'Removed', str(oChildOld)));
1058 iChildOld += 1;
1059
1060 if iChildOld < len(oOld.aoTestCaseArgs) \
1061 and oOld.aoTestCaseArgs[iChildOld].idTestCaseArgs == oChildNew.idTestCaseArgs:
1062 oChildOld = oOld.aoTestCaseArgs[iChildOld];
1063 if oChildNew.tsEffective != oChildOld.tsEffective:
1064 for sAttr in oChildNew.getDataAttributes():
1065 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestCase', ]:
1066 oOldAttr = getattr(oChildOld, sAttr);
1067 oNewAttr = getattr(oChildNew, sAttr);
1068 if oOldAttr != oNewAttr:
1069 aoChanges.append(AttributeChangeEntry('Variation[#%s].%s'
1070 % (oChildOld.idTestCaseArgs, sAttr,),
1071 oNewAttr, oOldAttr,
1072 str(oNewAttr), str(oOldAttr)));
1073 iChildOld += 1;
1074 else:
1075 aoChanges.append(AttributeChangeEntry('Variation #%s' % (oChildNew.idTestCaseArgs,),
1076 oChildNew, None,
1077 str(oChildNew), 'Did not exist'));
1078
1079 # The testcase dependencies.
1080 iChildOld = 0;
1081 for oChildNew in oNew.aoDepTestCases:
1082 # Locate the old entry, emitting removed markers for old items we have to skip.
1083 while iChildOld < len(oOld.aoDepTestCases) \
1084 and oOld.aoDepTestCases[iChildOld].idTestCase < oChildNew.idTestCase:
1085 oChildOld = oOld.aoDepTestCases[iChildOld];
1086 aoChanges.append(AttributeChangeEntry('Dependency #%s' % (oChildOld.idTestCase,),
1087 None, oChildOld, 'Removed',
1088 '%s (#%u)' % (oChildOld.sName, oChildOld.idTestCase,)));
1089 iChildOld += 1;
1090 if iChildOld < len(oOld.aoDepTestCases) \
1091 and oOld.aoDepTestCases[iChildOld].idTestCase == oChildNew.idTestCase:
1092 iChildOld += 1;
1093 else:
1094 aoChanges.append(AttributeChangeEntry('Dependency #%s' % (oChildNew.idTestCase,),
1095 oChildNew, None,
1096 '%s (#%u)' % (oChildNew.sName, oChildNew.idTestCase,),
1097 'Did not exist'));
1098
1099 # The global resource dependencies.
1100 iChildOld = 0;
1101 for oChildNew in oNew.aoDepGlobalResources:
1102 # Locate the old entry, emitting removed markers for old items we have to skip.
1103 while iChildOld < len(oOld.aoDepGlobalResources) \
1104 and oOld.aoDepGlobalResources[iChildOld].idGlobalRsrc < oChildNew.idGlobalRsrc:
1105 oChildOld = oOld.aoDepGlobalResources[iChildOld];
1106 aoChanges.append(AttributeChangeEntry('Global Resource #%s' % (oChildOld.idGlobalRsrc,),
1107 None, oChildOld, 'Removed',
1108 '%s (#%u)' % (oChildOld.sName, oChildOld.idGlobalRsrc,)));
1109 iChildOld += 1;
1110 if iChildOld < len(oOld.aoDepGlobalResources) \
1111 and oOld.aoDepGlobalResources[iChildOld].idGlobalRsrc == oChildNew.idGlobalRsrc:
1112 iChildOld += 1;
1113 else:
1114 aoChanges.append(AttributeChangeEntry('Global Resource #%s' % (oChildNew.idGlobalRsrc,),
1115 oChildNew, None,
1116 '%s (#%u)' % (oChildNew.sName, oChildNew.idGlobalRsrc,),
1117 'Did not exist'));
1118
1119 # Done.
1120 aoEntries.append(ChangeLogEntry(uidAuthor, None, tsEffective, tsExpire, oNew, oOld, aoChanges));
1121
1122 # If we're at the end of the log, add the initial entry.
1123 if len(aoRows) <= cMaxRows and len(aoRows) > 0:
1124 oNew = aoRows[-1];
1125 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None,
1126 aaoChanges[-1][0], aaoChanges[-2][0] if len(aaoChanges) > 1 else oNew.tsExpire,
1127 oNew, None, []));
1128
1129 return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aoRows) > cMaxRows);
1130
1131
1132 def addEntry(self, oData, uidAuthor, fCommit = False):
1133 """
1134 Add a new testcase to the DB.
1135 """
1136
1137 #
1138 # Validate the input first.
1139 #
1140 assert isinstance(oData, TestCaseDataEx);
1141 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add);
1142 if len(dErrors) > 0:
1143 raise TMInvalidData('Invalid input data: %s' % (dErrors,));
1144
1145 #
1146 # Add the testcase.
1147 #
1148 self._oDb.callProc('TestCaseLogic_addEntry',
1149 ( uidAuthor, oData.sName, oData.sDescription, oData.fEnabled, oData.cSecTimeout,
1150 oData.sTestBoxReqExpr, oData.sBuildReqExpr, oData.sBaseCmd, oData.sValidationKitZips ));
1151 oData.idTestCase = self._oDb.fetchOne()[0];
1152
1153 # Add testcase dependencies.
1154 for oDep in oData.aoDepTestCases:
1155 self._oDb.execute('INSERT INTO TestCaseDeps (idTestCase, idTestCasePreReq, uidAuthor) VALUES (%s, %s, %s)'
1156 , (oData.idTestCase, oDep.idTestCase, uidAuthor))
1157
1158 # Add global resource dependencies.
1159 for oDep in oData.aoDepGlobalResources:
1160 self._oDb.execute('INSERT INTO TestCaseGlobalRsrcDeps (idTestCase, idGlobalRsrc, uidAuthor) VALUES (%s, %s, %s)'
1161 , (oData.idTestCase, oDep.idGlobalRsrc, uidAuthor))
1162
1163 # Set Test Case Arguments variations
1164 for oVar in oData.aoTestCaseArgs:
1165 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1166 ' idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1167 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1168 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)'
1169 , ( oData.idTestCase, uidAuthor, oVar.sArgs, oVar.cSecTimeout,
1170 oVar.sTestBoxReqExpr, oVar.sBuildReqExpr, oVar.cGangMembers, oVar.sSubName, ));
1171
1172 self._oDb.maybeCommit(fCommit);
1173 return True;
1174
1175 def editEntry(self, oData, uidAuthor, fCommit = False): # pylint: disable=R0914
1176 """
1177 Edit a testcase entry (extended).
1178 Caller is expected to rollback the database transactions on exception.
1179 """
1180
1181 #
1182 # Validate the input.
1183 #
1184 assert isinstance(oData, TestCaseDataEx);
1185 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
1186 if len(dErrors) > 0:
1187 raise TMInvalidData('Invalid input data: %s' % (dErrors,));
1188
1189 #
1190 # Did anything change? If not return straight away.
1191 #
1192 oOldDataEx = TestCaseDataEx().initFromDbWithId(self._oDb, oData.idTestCase);
1193 if oOldDataEx.isEqual(oData):
1194 self._oDb.maybeCommit(fCommit);
1195 return True;
1196
1197 #
1198 # Make the necessary changes.
1199 #
1200
1201 # The test case itself.
1202 if not TestCaseData().initFromOther(oOldDataEx).isEqual(oData):
1203 self._oDb.callProc('TestCaseLogic_editEntry', ( uidAuthor, oData.idTestCase, oData.sName, oData.sDescription,
1204 oData.fEnabled, oData.cSecTimeout, oData.sTestBoxReqExpr,
1205 oData.sBuildReqExpr, oData.sBaseCmd, oData.sValidationKitZips ));
1206 oData.idGenTestCase = self._oDb.fetchOne()[0];
1207
1208 #
1209 # Its dependencies on other testcases.
1210 #
1211 aidNewDeps = [oDep.idTestCase for oDep in oData.aoDepTestCases];
1212 aidOldDeps = [oDep.idTestCase for oDep in oOldDataEx.aoDepTestCases];
1213
1214 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseDeps\n'
1215 'SET tsExpire = CURRENT_TIMESTAMP\n'
1216 'WHERE idTestCase = %s\n'
1217 ' AND tsExpire = \'infinity\'::timestamp\n'
1218 , (oData.idTestCase,));
1219 asKeepers = [];
1220 for idDep in aidOldDeps:
1221 if idDep in aidNewDeps:
1222 asKeepers.append(str(idDep));
1223 if len(asKeepers) > 0:
1224 sQuery += ' AND idTestCasePreReq NOT IN (' + ', '.join(asKeepers) + ')\n';
1225 self._oDb.execute(sQuery);
1226
1227 for idDep in aidNewDeps:
1228 if idDep not in aidOldDeps:
1229 self._oDb.execute('INSERT INTO TestCaseDeps (idTestCase, idTestCasePreReq, uidAuthor)\n'
1230 'VALUES (%s, %s, %s)\n'
1231 , (oData.idTestCase, idDep, uidAuthor) );
1232
1233 #
1234 # Its dependencies on global resources.
1235 #
1236 aidNewDeps = [oDep.idGlobalRsrc for oDep in oData.aoDepGlobalResources];
1237 aidOldDeps = [oDep.idGlobalRsrc for oDep in oOldDataEx.aoDepGlobalResources];
1238
1239 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseGlobalRsrcDeps\n'
1240 'SET tsExpire = CURRENT_TIMESTAMP\n'
1241 'WHERE idTestCase = %s\n'
1242 ' AND tsExpire = \'infinity\'::timestamp\n'
1243 , (oData.idTestCase,));
1244 asKeepers = [];
1245 for idDep in aidOldDeps:
1246 if idDep in aidNewDeps:
1247 asKeepers.append(str(idDep));
1248 if len(asKeepers) > 0:
1249 sQuery = ' AND idGlobalRsrc NOT IN (' + ', '.join(asKeepers) + ')\n';
1250 self._oDb.execute(sQuery);
1251
1252 for idDep in aidNewDeps:
1253 if idDep not in aidOldDeps:
1254 self._oDb.execute('INSERT INTO TestCaseGlobalRsrcDeps (idTestCase, idGlobalRsrc, uidAuthor)\n'
1255 'VALUES (%s, %s, %s)\n'
1256 , (oData.idTestCase, idDep, uidAuthor) );
1257
1258 #
1259 # Update Test Case Args
1260 # Note! Primary key is idTestCase, tsExpire, sArgs.
1261 #
1262
1263 # Historize rows that have been removed.
1264 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseArgs\n'
1265 'SET tsExpire = CURRENT_TIMESTAMP\n'
1266 'WHERE idTestCase = %s\n'
1267 ' AND tsExpire = \'infinity\'::TIMESTAMP'
1268 , (oData.idTestCase, ));
1269 for oNewVar in oData.aoTestCaseArgs:
1270 asKeepers.append(self._oDb.formatBindArgs('%s', (oNewVar.sArgs,)));
1271 if len(asKeepers) > 0:
1272 sQuery += ' AND sArgs NOT IN (' + ', '.join(asKeepers) + ')\n';
1273 self._oDb.execute(sQuery);
1274
1275 # Add new TestCaseArgs records if necessary, reusing old IDs when possible.
1276 from testmanager.core.testcaseargs import TestCaseArgsData;
1277 for oNewVar in oData.aoTestCaseArgs:
1278 self._oDb.execute('SELECT *\n'
1279 'FROM TestCaseArgs\n'
1280 'WHERE idTestCase = %s\n'
1281 ' AND sArgs = %s\n'
1282 'ORDER BY tsExpire DESC\n'
1283 'LIMIT 1\n'
1284 , (oData.idTestCase, oNewVar.sArgs,));
1285 aoRow = self._oDb.fetchOne();
1286 if aoRow is None:
1287 # New
1288 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1289 ' idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1290 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1291 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)'
1292 , ( oData.idTestCase, uidAuthor, oNewVar.sArgs, oNewVar.cSecTimeout,
1293 oNewVar.sTestBoxReqExpr, oNewVar.sBuildReqExpr, oNewVar.cGangMembers, oNewVar.sSubName));
1294 else:
1295 oCurVar = TestCaseArgsData().initFromDbRow(aoRow);
1296 if self._oDb.isTsInfinity(oCurVar.tsExpire):
1297 # Existing current entry, updated if changed.
1298 if oNewVar.cSecTimeout == oCurVar.cSecTimeout \
1299 and oNewVar.sTestBoxReqExpr == oCurVar.sTestBoxReqExpr \
1300 and oNewVar.sBuildReqExpr == oCurVar.sBuildReqExpr \
1301 and oNewVar.cGangMembers == oCurVar.cGangMembers \
1302 and oNewVar.sSubName == oCurVar.sSubName:
1303 oNewVar.idTestCaseArgs = oCurVar.idTestCaseArgs;
1304 oNewVar.idGenTestCaseArgs = oCurVar.idGenTestCaseArgs;
1305 continue; # Unchanged.
1306 self._oDb.execute('UPDATE TestCaseArgs SET tsExpire = CURRENT_TIMESTAMP WHERE idGenTestCaseArgs = %s\n'
1307 , (oCurVar.idGenTestCaseArgs, ));
1308 else:
1309 # Existing old entry, re-use the ID.
1310 pass;
1311 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1312 ' idTestCaseArgs, idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1313 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1314 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
1315 'RETURNING idGenTestCaseArgs\n'
1316 , ( oCurVar.idTestCaseArgs, oData.idTestCase, uidAuthor, oNewVar.sArgs, oNewVar.cSecTimeout,
1317 oNewVar.sTestBoxReqExpr, oNewVar.sBuildReqExpr, oNewVar.cGangMembers, oNewVar.sSubName));
1318 oNewVar.idGenTestCaseArgs = self._oDb.fetchOne()[0];
1319
1320 self._oDb.maybeCommit(fCommit);
1321 return True;
1322
1323 def removeEntry(self, uidAuthor, idTestCase, fCascade = False, fCommit = False):
1324 """ Deletes the test case if possible. """
1325 self._oDb.callProc('TestCaseLogic_delEntry', (uidAuthor, idTestCase, fCascade));
1326 self._oDb.maybeCommit(fCommit);
1327 return True
1328
1329
1330 def getTestCasePreReqIds(self, idTestCase, tsEffective = None, cMax = None):
1331 """
1332 Returns an array of prerequisite testcases (IDs) for the given testcase.
1333 May raise exception on database error or if the result exceeds cMax.
1334 """
1335 if tsEffective is None:
1336 self._oDb.execute('SELECT idTestCasePreReq\n'
1337 'FROM TestCaseDeps\n'
1338 'WHERE idTestCase = %s\n'
1339 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1340 'ORDER BY idTestCasePreReq\n'
1341 , (idTestCase,) );
1342 else:
1343 self._oDb.execute('SELECT idTestCasePreReq\n'
1344 'FROM TestCaseDeps\n'
1345 'WHERE idTestCase = %s\n'
1346 ' AND tsExpire > %s\n'
1347 ' AND tsEffective <= %s\n'
1348 'ORDER BY idTestCasePreReq\n'
1349 , (idTestCase, tsEffective, tsEffective) );
1350
1351
1352 if cMax is not None and self._oDb.getRowCount() > cMax:
1353 raise TMExceptionBase('Too many prerequisites for testcase %s: %s, max %s'
1354 % (idTestCase, cMax, self._oDb.getRowCount(),));
1355
1356 aidPreReqs = [];
1357 for aoRow in self._oDb.fetchAll():
1358 aidPreReqs.append(aoRow[0]);
1359 return aidPreReqs;
1360
1361
1362 def cachedLookup(self, idTestCase):
1363 """
1364 Looks up the most recent TestCaseDataEx object for idTestCase
1365 via an object cache.
1366
1367 Returns a shared TestCaseDataEx object. None if not found.
1368 Raises exception on DB error.
1369 """
1370 if self.dCache is None:
1371 self.dCache = self._oDb.getCache('TestCaseDataEx');
1372 oEntry = self.dCache.get(idTestCase, None);
1373 if oEntry is None:
1374 fNeedTsNow = False;
1375 self._oDb.execute('SELECT *\n'
1376 'FROM TestCases\n'
1377 'WHERE idTestCase = %s\n'
1378 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1379 , (idTestCase, ));
1380 if self._oDb.getRowCount() == 0:
1381 # Maybe it was deleted, try get the last entry.
1382 self._oDb.execute('SELECT *\n'
1383 'FROM TestCases\n'
1384 'WHERE idTestCase = %s\n'
1385 'ORDER BY tsExpire DESC\n'
1386 'LIMIT 1\n'
1387 , (idTestCase, ));
1388 fNeedTsNow = True;
1389 elif self._oDb.getRowCount() > 1:
1390 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestCase));
1391
1392 if self._oDb.getRowCount() == 1:
1393 aaoRow = self._oDb.fetchOne();
1394 oEntry = TestCaseDataEx();
1395 tsNow = oEntry.initFromDbRow(aaoRow).tsEffective if fNeedTsNow else None;
1396 oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow);
1397 self.dCache[idTestCase] = oEntry;
1398 return oEntry;
1399
1400
1401
1402#
1403# Unit testing.
1404#
1405
1406# pylint: disable=C0111
1407class TestCaseGlobalRsrcDepDataTestCase(ModelDataBaseTestCase):
1408 def setUp(self):
1409 self.aoSamples = [TestCaseGlobalRsrcDepData(),];
1410
1411class TestCaseDataTestCase(ModelDataBaseTestCase):
1412 def setUp(self):
1413 self.aoSamples = [TestCaseData(),];
1414
1415 def testEmptyExpr(self):
1416 self.assertEqual(TestCaseData.validateTestBoxReqExpr(None), None);
1417 self.assertEqual(TestCaseData.validateTestBoxReqExpr(''), None);
1418
1419 def testSimpleExpr(self):
1420 self.assertEqual(TestCaseData.validateTestBoxReqExpr('cMbMemory > 10'), None);
1421 self.assertEqual(TestCaseData.validateTestBoxReqExpr('cMbScratch < 10'), None);
1422 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu'), None);
1423 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is True'), None);
1424 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is False'), None);
1425 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is None'), None);
1426 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(fChipsetIoMmu, bool)'), None);
1427 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(iTestBoxScriptRev, int)'), None);
1428 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(cMbScratch, long)'), None);
1429
1430 def testBadExpr(self):
1431 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('this is an bad expression, surely it must be'), None);
1432 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('x = 1 + 1'), None);
1433 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('__import__(\'os\').unlink(\'/tmp/no/such/file\')'), None);
1434 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('print "foobar"'), None);
1435
1436class TestCaseDataExTestCase(ModelDataBaseTestCase):
1437 def setUp(self):
1438 self.aoSamples = [TestCaseDataEx(),];
1439
1440if __name__ == '__main__':
1441 unittest.main();
1442 # not reached.
1443
Note: See TracBrowser for help on using the repository browser.

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