VirtualBox

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

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

testmanager: Generate list of test cases starting (and stopping) to fail, with failure count graph.

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