VirtualBox

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

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

fix OSE

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