VirtualBox

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

Last change on this file since 66998 was 65980, checked in by vboxsync, 8 years ago

testmanager/core: pylint 2.0.0 fixes.

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

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