VirtualBox

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

Last change on this file since 62548 was 62484, checked in by vboxsync, 8 years ago

(C) 2016

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