VirtualBox

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

Last change on this file since 79087 was 79087, checked in by vboxsync, 5 years ago

ValKit: New pylint version - cleanup in progress.

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