VirtualBox

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

Last change on this file since 96407 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

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