VirtualBox

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

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

wuireport.py,++: Added build box failure report. When requesting details on a testcase (or more), you'll now get a report on the test case variations too. Made links include the period length+count and effective date

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

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