VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 8 weeks ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 65.8 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testcase.py 106061 2024-09-16 14:03:52Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Test Manager - Test Case.
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2012-2024 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: 106061 $"
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 = {}
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 'fNativeApi': False,
659 'cMbMemory': 985034,
660 'cMbScratch': 1234089,
661 'iTestBoxScriptRev': 1,
662 'sName': 'emanon',
663 'uuidSystem': '8FF81BE5-3901-4AB1-8A65-B48D511C0321',
664 },
665 {
666 'sOs': 'linux',
667 'sOsVersion': '3.1',
668 'sCpuVendor': 'VirtualBox',
669 'sCpuArch': 'amd64',
670 'cCpus': 8191,
671 'fCpuHwVirt': True,
672 'fCpuNestedPaging': True,
673 'fCpu64BitGuest': True,
674 'fChipsetIoMmu': True,
675 'fRawMode': True,
676 'fNativeApi': True,
677 'cMbMemory': 9999999999,
678 'cMbScratch': 9999999999999,
679 'iTestBoxScriptRev': 9999999,
680 'sName': 'emanon',
681 'uuidSystem': '00000000-0000-0000-0000-000000000000',
682 },
683 ];
684 return TestCaseData._safelyValidateReqExpr(sExpr, adTestBoxes);
685
686 @staticmethod
687 def matchesTestBoxPropsEx(oTestBoxData, sExpr):
688 """ Worker for TestCaseData.matchesTestBoxProps and TestCaseArgsDataEx.matchesTestBoxProps. """
689 if sExpr is None:
690 return True;
691 dLocals = \
692 {
693 'sOs': oTestBoxData.sOs,
694 'sOsVersion': oTestBoxData.sOsVersion,
695 'sCpuVendor': oTestBoxData.sCpuVendor,
696 'sCpuArch': oTestBoxData.sCpuArch,
697 'iCpuFamily': oTestBoxData.getCpuFamily(),
698 'iCpuModel': oTestBoxData.getCpuModel(),
699 'cCpus': oTestBoxData.cCpus,
700 'fCpuHwVirt': oTestBoxData.fCpuHwVirt,
701 'fCpuNestedPaging': oTestBoxData.fCpuNestedPaging,
702 'fCpu64BitGuest': oTestBoxData.fCpu64BitGuest,
703 'fChipsetIoMmu': oTestBoxData.fChipsetIoMmu,
704 'fRawMode': oTestBoxData.fRawMode,
705 'fNativeApi': oTestBoxData.fNativeApi,
706 'cMbMemory': oTestBoxData.cMbMemory,
707 'cMbScratch': oTestBoxData.cMbScratch,
708 'iTestBoxScriptRev': oTestBoxData.iTestBoxScriptRev,
709 'iPythonHexVersion': oTestBoxData.iPythonHexVersion,
710 'sName': oTestBoxData.sName,
711 'uuidSystem': oTestBoxData.uuidSystem,
712 };
713 return TestCaseData._safelyEvalExpr(sExpr, dLocals);
714
715 @staticmethod
716 def validateBuildReqExpr(sExpr):
717 """
718 Validates a testbox expression, returning None on success and an error
719 string on failure.
720 """
721 adBuilds = \
722 [
723 {
724 'sProduct': 'VirtualBox',
725 'sBranch': 'trunk',
726 'sType': 'release',
727 'asOsArches': ['win.amd64', 'win.x86'],
728 'sVersion': '1.0',
729 'iRevision': 1234,
730 'uidAuthor': None,
731 'idBuild': 953,
732 },
733 {
734 'sProduct': 'VirtualBox',
735 'sBranch': 'VBox-4.1',
736 'sType': 'release',
737 'asOsArches': ['linux.x86',],
738 'sVersion': '4.2.15',
739 'iRevision': 89876,
740 'uidAuthor': None,
741 'idBuild': 945689,
742 },
743 {
744 'sProduct': 'VirtualBox',
745 'sBranch': 'VBox-4.1',
746 'sType': 'strict',
747 'asOsArches': ['solaris.x86', 'solaris.amd64',],
748 'sVersion': '4.3.0_RC3',
749 'iRevision': 97939,
750 'uidAuthor': 33,
751 'idBuild': 9456893,
752 },
753 ];
754 return TestCaseData._safelyValidateReqExpr(sExpr, adBuilds);
755
756 @staticmethod
757 def matchesBuildPropsEx(oBuildDataEx, sExpr):
758 """
759 Checks if the all of the build related test requirements matches the
760 given build.
761 """
762 if sExpr is None:
763 return True;
764 dLocals = \
765 {
766 'sProduct': oBuildDataEx.oCat.sProduct,
767 'sBranch': oBuildDataEx.oCat.sBranch,
768 'sType': oBuildDataEx.oCat.sType,
769 'asOsArches': oBuildDataEx.oCat.asOsArches,
770 'sVersion': oBuildDataEx.sVersion,
771 'iRevision': oBuildDataEx.iRevision,
772 'uidAuthor': oBuildDataEx.uidAuthor,
773 'idBuild': oBuildDataEx.idBuild,
774 };
775 return TestCaseData._safelyEvalExpr(sExpr, dLocals);
776
777
778
779
780class TestCaseDataEx(TestCaseData):
781 """
782 Test case data.
783 """
784
785 ksParam_aoTestCaseArgs = 'TestCase_aoTestCaseArgs';
786 ksParam_aoDepTestCases = 'TestCase_aoDepTestCases';
787 ksParam_aoDepGlobalResources = 'TestCase_aoDepGlobalResources';
788
789 # Use [] instead of None.
790 kasAltArrayNull = [ 'aoTestCaseArgs', 'aoDepTestCases', 'aoDepGlobalResources' ];
791
792
793 def __init__(self):
794 TestCaseData.__init__(self);
795
796 # List of objects of type TestCaseData (or TestCaseDataEx, we don't
797 # care) on which current Test Case depends.
798 self.aoDepTestCases = [];
799
800 # List of objects of type GlobalResourceData on which current Test Case depends.
801 self.aoDepGlobalResources = [];
802
803 # List of objects of type TestCaseArgsData.
804 self.aoTestCaseArgs = [];
805
806 def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None):
807 """
808 Worker shared by the initFromDb* methods.
809 Returns self. Raises exception if no row or database error.
810 """
811 _ = sPeriodBack; ## @todo sPeriodBack
812 from testmanager.core.testcaseargs import TestCaseArgsLogic;
813 self.aoDepTestCases = TestCaseDependencyLogic(oDb).getDepTestCaseData(self.idTestCase, tsNow);
814 self.aoDepGlobalResources = TestCaseGlobalRsrcDepLogic(oDb).getDepGlobalResourceData(self.idTestCase, tsNow);
815 self.aoTestCaseArgs = TestCaseArgsLogic(oDb).getTestCaseArgs(self.idTestCase, tsNow);
816 # Note! The above arrays are sorted by their relvant IDs for fetchForChangeLog's sake.
817 return self;
818
819 def initFromDbRowEx(self, aoRow, oDb, tsNow = None):
820 """
821 Reinitialize from a SELECT * FROM TestCases row. Will query the
822 necessary additional data from oDb using tsNow.
823 Returns self. Raises exception if no row or database error.
824 """
825 TestCaseData.initFromDbRow(self, aoRow);
826 return self._initExtraMembersFromDb(oDb, tsNow);
827
828 def initFromDbWithId(self, oDb, idTestCase, tsNow = None, sPeriodBack = None):
829 """
830 Initialize the object from the database.
831 """
832 TestCaseData.initFromDbWithId(self, oDb, idTestCase, tsNow, sPeriodBack);
833 return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack);
834
835 def initFromDbWithGenId(self, oDb, idGenTestCase, tsNow = None):
836 """
837 Initialize the object from the database.
838 """
839 TestCaseData.initFromDbWithGenId(self, oDb, idGenTestCase);
840 if tsNow is None and not oDb.isTsInfinity(self.tsExpire):
841 tsNow = self.tsEffective;
842 return self._initExtraMembersFromDb(oDb, tsNow);
843
844 def getAttributeParamNullValues(self, sAttr):
845 if sAttr in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
846 return [[], ''];
847 return TestCaseData.getAttributeParamNullValues(self, sAttr);
848
849 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
850 """For dealing with the arrays."""
851 if sAttr not in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
852 return TestCaseData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict);
853
854 aoNewValues = [];
855 if sAttr == 'aoDepTestCases':
856 for idTestCase in oDisp.getListOfIntParams(sParam, 1, 0x7ffffffe, []):
857 oDep = TestCaseData();
858 oDep.idTestCase = str(idTestCase);
859 aoNewValues.append(oDep);
860
861 elif sAttr == 'aoDepGlobalResources':
862 for idGlobalRsrc in oDisp.getListOfIntParams(sParam, 1, 0x7ffffffe, []):
863 oGlobalRsrc = GlobalResourceData();
864 oGlobalRsrc.idGlobalRsrc = str(idGlobalRsrc);
865 aoNewValues.append(oGlobalRsrc);
866
867 elif sAttr == 'aoTestCaseArgs':
868 from testmanager.core.testcaseargs import TestCaseArgsData;
869 for sArgKey in oDisp.getStringParam(TestCaseDataEx.ksParam_aoTestCaseArgs, sDefault = '').split(','):
870 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (TestCaseDataEx.ksParam_aoTestCaseArgs, sArgKey,))
871 aoNewValues.append(TestCaseArgsData().initFromParams(oDispWrapper, fStrict = False));
872 return aoNewValues;
873
874 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): # pylint: disable=too-many-locals
875 """
876 Validate special arrays and requirement expressions.
877
878 For the two dependency arrays we have to supply missing bits by
879 looking them up in the database. In the argument variation case we
880 need to validate each item.
881 """
882 if sAttr not in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']:
883 return TestCaseData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
884
885 asErrors = [];
886 aoNewValues = [];
887 if sAttr == 'aoDepTestCases':
888 for oTestCase in self.aoDepTestCases:
889 if utils.isString(oTestCase.idTestCase): # Stored as string convertParamToAttribute.
890 oTestCase = copy.copy(oTestCase);
891 try:
892 oTestCase.idTestCase = int(oTestCase.idTestCase);
893 oTestCase.initFromDbWithId(oDb, oTestCase.idTestCase);
894 except Exception as oXcpt:
895 asErrors.append('Test case dependency #%s: %s' % (oTestCase.idTestCase, oXcpt));
896 aoNewValues.append(oTestCase);
897
898 elif sAttr == 'aoDepGlobalResources':
899 for oGlobalRsrc in self.aoDepGlobalResources:
900 if utils.isString(oGlobalRsrc.idGlobalRsrc): # Stored as string convertParamToAttribute.
901 oGlobalRsrc = copy.copy(oGlobalRsrc);
902 try:
903 oGlobalRsrc.idTestCase = int(oGlobalRsrc.idGlobalRsrc);
904 oGlobalRsrc.initFromDbWithId(oDb, oGlobalRsrc.idGlobalRsrc);
905 except Exception as oXcpt:
906 asErrors.append('Resource dependency #%s: %s' % (oGlobalRsrc.idGlobalRsrc, oXcpt));
907 aoNewValues.append(oGlobalRsrc);
908
909 else:
910 assert sAttr == 'aoTestCaseArgs';
911 if not self.aoTestCaseArgs:
912 return (None, 'The testcase requires at least one argument variation to be valid.');
913
914 # Note! We'll be returning an error dictionary instead of an string here.
915 dErrors = {};
916
917 for iVar, oVar in enumerate(self.aoTestCaseArgs):
918 oVar = copy.copy(oVar);
919 oVar.idTestCase = self.idTestCase;
920 dCurErrors = oVar.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other);
921 if not dCurErrors:
922 pass; ## @todo figure out the ID?
923 else:
924 asErrors = [];
925 for sKey in dCurErrors:
926 asErrors.append('%s: %s' % (sKey[len('TestCaseArgs_'):], dCurErrors[sKey]));
927 dErrors[iVar] = '<br>\n'.join(asErrors)
928 aoNewValues.append(oVar);
929
930 for iVar, oVar in enumerate(self.aoTestCaseArgs):
931 sArgs = oVar.sArgs;
932 for iVar2 in range(iVar + 1, len(self.aoTestCaseArgs)):
933 if self.aoTestCaseArgs[iVar2].sArgs == sArgs:
934 sMsg = 'Duplicate argument variation "%s".' % (sArgs);
935 if iVar in dErrors: dErrors[iVar] += '<br>\n' + sMsg;
936 else: dErrors[iVar] = sMsg;
937 if iVar2 in dErrors: dErrors[iVar2] += '<br>\n' + sMsg;
938 else: dErrors[iVar2] = sMsg;
939 break;
940
941 return (aoNewValues, dErrors if dErrors else None);
942
943 return (aoNewValues, None if not asErrors else ' <br>'.join(asErrors));
944
945 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
946 dErrors = TestCaseData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
947
948 # Validate dependencies a wee bit for paranoid reasons. The scheduler
949 # queue generation code does the real validation here!
950 if not dErrors and self.idTestCase is not None:
951 for oDep in self.aoDepTestCases:
952 if oDep.idTestCase == self.idTestCase:
953 if self.ksParam_aoDepTestCases in dErrors:
954 dErrors[self.ksParam_aoDepTestCases] += ' Depending on itself!';
955 else:
956 dErrors[self.ksParam_aoDepTestCases] = 'Depending on itself!';
957 return dErrors;
958
959
960
961
962
963class TestCaseLogic(ModelLogicBase):
964 """
965 Test case management logic.
966 """
967
968 def __init__(self, oDb):
969 ModelLogicBase.__init__(self, oDb)
970 self.dCache = None;
971
972 def getAll(self):
973 """
974 Fetches all test case records from DB (TestCaseData).
975 """
976 self._oDb.execute('SELECT *\n'
977 'FROM TestCases\n'
978 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
979 'ORDER BY idTestCase ASC;')
980
981 aaoRows = self._oDb.fetchAll()
982 aoRet = [];
983 for aoRow in aaoRows:
984 aoRet.append(TestCaseData().initFromDbRow(aoRow))
985 return aoRet
986
987 def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None):
988 """
989 Fetches test cases.
990
991 Returns an array (list) of TestCaseDataEx items, empty list if none.
992 Raises exception on error.
993 """
994 _ = aiSortColumns;
995 if tsNow is None:
996 self._oDb.execute('SELECT *\n'
997 'FROM TestCases\n'
998 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
999 'ORDER BY sName ASC\n'
1000 'LIMIT %s OFFSET %s\n'
1001 , (cMaxRows, iStart, ));
1002 else:
1003 self._oDb.execute('SELECT *\n'
1004 'FROM TestCases\n'
1005 'WHERE tsExpire > %s\n'
1006 ' AND tsEffective <= %s\n'
1007 'ORDER BY sName ASC\n'
1008 'LIMIT %s OFFSET %s\n'
1009 , (tsNow, tsNow, cMaxRows, iStart, ));
1010
1011 aoRows = [];
1012 for aoRow in self._oDb.fetchAll():
1013 aoRows.append(TestCaseDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow));
1014 return aoRows;
1015
1016 def fetchForChangeLog(self, idTestCase, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals
1017 """
1018 Fetches change log entries for a testbox.
1019
1020 Returns an array of ChangeLogEntry instance and an indicator whether
1021 there are more entries.
1022 Raises exception on error.
1023 """
1024
1025 if tsNow is None:
1026 tsNow = self._oDb.getCurrentTimestamp();
1027
1028 # 1. Get a list of the relevant change times.
1029 self._oDb.execute('( SELECT tsEffective, uidAuthor FROM TestCases WHERE idTestCase = %s AND tsEffective <= %s )\n'
1030 'UNION\n'
1031 '( SELECT tsEffective, uidAuthor FROM TestCaseArgs WHERE idTestCase = %s AND tsEffective <= %s )\n'
1032 'UNION\n'
1033 '( SELECT tsEffective, uidAuthor FROM TestCaseDeps WHERE idTestCase = %s AND tsEffective <= %s )\n'
1034 'UNION\n'
1035 '( SELECT tsEffective, uidAuthor FROM TestCaseGlobalRsrcDeps \n' \
1036 ' WHERE idTestCase = %s AND tsEffective <= %s )\n'
1037 'ORDER BY tsEffective DESC\n'
1038 'LIMIT %s OFFSET %s\n'
1039 , ( idTestCase, tsNow,
1040 idTestCase, tsNow,
1041 idTestCase, tsNow,
1042 idTestCase, tsNow,
1043 cMaxRows + 1, iStart, ));
1044 aaoChanges = self._oDb.fetchAll();
1045
1046 # 2. Collect data sets for each of those points.
1047 # (Doing it the lazy + inefficient way for now.)
1048 aoRows = [];
1049 for aoChange in aaoChanges:
1050 aoRows.append(TestCaseDataEx().initFromDbWithId(self._oDb, idTestCase, aoChange[0]));
1051
1052 # 3. Calculate the changes.
1053 aoEntries = [];
1054 for i in range(0, len(aoRows) - 1):
1055 oNew = aoRows[i];
1056 oOld = aoRows[i + 1];
1057 (tsEffective, uidAuthor) = aaoChanges[i];
1058 (tsExpire, _) = aaoChanges[i - 1] if i > 0 else (oNew.tsExpire, None)
1059 assert self._oDb.isTsInfinity(tsEffective) != self._oDb.isTsInfinity(tsExpire) or tsEffective < tsExpire, \
1060 '%s vs %s' % (tsEffective, tsExpire);
1061
1062 aoChanges = [];
1063
1064 # The testcase object.
1065 if oNew.tsEffective != oOld.tsEffective:
1066 for sAttr in oNew.getDataAttributes():
1067 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', \
1068 'aoTestCaseArgs', 'aoDepTestCases', 'aoDepGlobalResources']:
1069 oOldAttr = getattr(oOld, sAttr);
1070 oNewAttr = getattr(oNew, sAttr);
1071 if oOldAttr != oNewAttr:
1072 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
1073
1074 # The argument variations.
1075 iChildOld = 0;
1076 for oChildNew in oNew.aoTestCaseArgs:
1077 # Locate the old entry, emitting removed markers for old items we have to skip.
1078 while iChildOld < len(oOld.aoTestCaseArgs) \
1079 and oOld.aoTestCaseArgs[iChildOld].idTestCaseArgs < oChildNew.idTestCaseArgs:
1080 oChildOld = oOld.aoTestCaseArgs[iChildOld];
1081 aoChanges.append(AttributeChangeEntry('Variation #%s' % (oChildOld.idTestCaseArgs,),
1082 None, oChildOld, 'Removed', str(oChildOld)));
1083 iChildOld += 1;
1084
1085 if iChildOld < len(oOld.aoTestCaseArgs) \
1086 and oOld.aoTestCaseArgs[iChildOld].idTestCaseArgs == oChildNew.idTestCaseArgs:
1087 oChildOld = oOld.aoTestCaseArgs[iChildOld];
1088 if oChildNew.tsEffective != oChildOld.tsEffective:
1089 for sAttr in oChildNew.getDataAttributes():
1090 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestCase', ]:
1091 oOldAttr = getattr(oChildOld, sAttr);
1092 oNewAttr = getattr(oChildNew, sAttr);
1093 if oOldAttr != oNewAttr:
1094 aoChanges.append(AttributeChangeEntry('Variation[#%s].%s'
1095 % (oChildOld.idTestCaseArgs, sAttr,),
1096 oNewAttr, oOldAttr,
1097 str(oNewAttr), str(oOldAttr)));
1098 iChildOld += 1;
1099 else:
1100 aoChanges.append(AttributeChangeEntry('Variation #%s' % (oChildNew.idTestCaseArgs,),
1101 oChildNew, None,
1102 str(oChildNew), 'Did not exist'));
1103
1104 # The testcase dependencies.
1105 iChildOld = 0;
1106 for oChildNew in oNew.aoDepTestCases:
1107 # Locate the old entry, emitting removed markers for old items we have to skip.
1108 while iChildOld < len(oOld.aoDepTestCases) \
1109 and oOld.aoDepTestCases[iChildOld].idTestCase < oChildNew.idTestCase:
1110 oChildOld = oOld.aoDepTestCases[iChildOld];
1111 aoChanges.append(AttributeChangeEntry('Dependency #%s' % (oChildOld.idTestCase,),
1112 None, oChildOld, 'Removed',
1113 '%s (#%u)' % (oChildOld.sName, oChildOld.idTestCase,)));
1114 iChildOld += 1;
1115 if iChildOld < len(oOld.aoDepTestCases) \
1116 and oOld.aoDepTestCases[iChildOld].idTestCase == oChildNew.idTestCase:
1117 iChildOld += 1;
1118 else:
1119 aoChanges.append(AttributeChangeEntry('Dependency #%s' % (oChildNew.idTestCase,),
1120 oChildNew, None,
1121 '%s (#%u)' % (oChildNew.sName, oChildNew.idTestCase,),
1122 'Did not exist'));
1123
1124 # The global resource dependencies.
1125 iChildOld = 0;
1126 for oChildNew in oNew.aoDepGlobalResources:
1127 # Locate the old entry, emitting removed markers for old items we have to skip.
1128 while iChildOld < len(oOld.aoDepGlobalResources) \
1129 and oOld.aoDepGlobalResources[iChildOld].idGlobalRsrc < oChildNew.idGlobalRsrc:
1130 oChildOld = oOld.aoDepGlobalResources[iChildOld];
1131 aoChanges.append(AttributeChangeEntry('Global Resource #%s' % (oChildOld.idGlobalRsrc,),
1132 None, oChildOld, 'Removed',
1133 '%s (#%u)' % (oChildOld.sName, oChildOld.idGlobalRsrc,)));
1134 iChildOld += 1;
1135 if iChildOld < len(oOld.aoDepGlobalResources) \
1136 and oOld.aoDepGlobalResources[iChildOld].idGlobalRsrc == oChildNew.idGlobalRsrc:
1137 iChildOld += 1;
1138 else:
1139 aoChanges.append(AttributeChangeEntry('Global Resource #%s' % (oChildNew.idGlobalRsrc,),
1140 oChildNew, None,
1141 '%s (#%u)' % (oChildNew.sName, oChildNew.idGlobalRsrc,),
1142 'Did not exist'));
1143
1144 # Done.
1145 aoEntries.append(ChangeLogEntry(uidAuthor, None, tsEffective, tsExpire, oNew, oOld, aoChanges));
1146
1147 # If we're at the end of the log, add the initial entry.
1148 if len(aoRows) <= cMaxRows and aoRows:
1149 oNew = aoRows[-1];
1150 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None,
1151 aaoChanges[-1][0], aaoChanges[-2][0] if len(aaoChanges) > 1 else oNew.tsExpire,
1152 oNew, None, []));
1153
1154 return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aoRows) > cMaxRows);
1155
1156
1157 def addEntry(self, oData, uidAuthor, fCommit = False):
1158 """
1159 Add a new testcase to the DB.
1160 """
1161
1162 #
1163 # Validate the input first.
1164 #
1165 assert isinstance(oData, TestCaseDataEx);
1166 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add);
1167 if dErrors:
1168 raise TMInvalidData('Invalid input data: %s' % (dErrors,));
1169
1170 #
1171 # Add the testcase.
1172 #
1173 self._oDb.callProc('TestCaseLogic_addEntry',
1174 ( uidAuthor, oData.sName, oData.sDescription, oData.fEnabled, oData.cSecTimeout,
1175 oData.sTestBoxReqExpr, oData.sBuildReqExpr, oData.sBaseCmd, oData.sValidationKitZips,
1176 oData.sComment ));
1177 oData.idTestCase = self._oDb.fetchOne()[0];
1178
1179 # Add testcase dependencies.
1180 for oDep in oData.aoDepTestCases:
1181 self._oDb.execute('INSERT INTO TestCaseDeps (idTestCase, idTestCasePreReq, uidAuthor) VALUES (%s, %s, %s)'
1182 , (oData.idTestCase, oDep.idTestCase, uidAuthor))
1183
1184 # Add global resource dependencies.
1185 for oDep in oData.aoDepGlobalResources:
1186 self._oDb.execute('INSERT INTO TestCaseGlobalRsrcDeps (idTestCase, idGlobalRsrc, uidAuthor) VALUES (%s, %s, %s)'
1187 , (oData.idTestCase, oDep.idGlobalRsrc, uidAuthor))
1188
1189 # Set Test Case Arguments variations
1190 for oVar in oData.aoTestCaseArgs:
1191 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1192 ' idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1193 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1194 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)'
1195 , ( oData.idTestCase, uidAuthor, oVar.sArgs, oVar.cSecTimeout,
1196 oVar.sTestBoxReqExpr, oVar.sBuildReqExpr, oVar.cGangMembers, oVar.sSubName, ));
1197
1198 self._oDb.maybeCommit(fCommit);
1199 return True;
1200
1201 def editEntry(self, oData, uidAuthor, fCommit = False): # pylint: disable=too-many-locals
1202 """
1203 Edit a testcase entry (extended).
1204 Caller is expected to rollback the database transactions on exception.
1205 """
1206
1207 #
1208 # Validate the input.
1209 #
1210 assert isinstance(oData, TestCaseDataEx);
1211 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
1212 if dErrors:
1213 raise TMInvalidData('Invalid input data: %s' % (dErrors,));
1214
1215 #
1216 # Did anything change? If not return straight away.
1217 #
1218 oOldDataEx = TestCaseDataEx().initFromDbWithId(self._oDb, oData.idTestCase);
1219 if oOldDataEx.isEqual(oData):
1220 self._oDb.maybeCommit(fCommit);
1221 return True;
1222
1223 #
1224 # Make the necessary changes.
1225 #
1226
1227 # The test case itself.
1228 if not TestCaseData().initFromOther(oOldDataEx).isEqual(oData):
1229 self._oDb.callProc('TestCaseLogic_editEntry', ( uidAuthor, oData.idTestCase, oData.sName, oData.sDescription,
1230 oData.fEnabled, oData.cSecTimeout, oData.sTestBoxReqExpr,
1231 oData.sBuildReqExpr, oData.sBaseCmd, oData.sValidationKitZips,
1232 oData.sComment ));
1233 oData.idGenTestCase = self._oDb.fetchOne()[0];
1234
1235 #
1236 # Its dependencies on other testcases.
1237 #
1238 aidNewDeps = [oDep.idTestCase for oDep in oData.aoDepTestCases];
1239 aidOldDeps = [oDep.idTestCase for oDep in oOldDataEx.aoDepTestCases];
1240
1241 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseDeps\n'
1242 'SET tsExpire = CURRENT_TIMESTAMP\n'
1243 'WHERE idTestCase = %s\n'
1244 ' AND tsExpire = \'infinity\'::timestamp\n'
1245 , (oData.idTestCase,));
1246 asKeepers = [];
1247 for idDep in aidOldDeps:
1248 if idDep in aidNewDeps:
1249 asKeepers.append(str(idDep));
1250 if asKeepers:
1251 sQuery += ' AND idTestCasePreReq NOT IN (' + ', '.join(asKeepers) + ')\n';
1252 self._oDb.execute(sQuery);
1253
1254 for idDep in aidNewDeps:
1255 if idDep not in aidOldDeps:
1256 self._oDb.execute('INSERT INTO TestCaseDeps (idTestCase, idTestCasePreReq, uidAuthor)\n'
1257 'VALUES (%s, %s, %s)\n'
1258 , (oData.idTestCase, idDep, uidAuthor) );
1259
1260 #
1261 # Its dependencies on global resources.
1262 #
1263 aidNewDeps = [oDep.idGlobalRsrc for oDep in oData.aoDepGlobalResources];
1264 aidOldDeps = [oDep.idGlobalRsrc for oDep in oOldDataEx.aoDepGlobalResources];
1265
1266 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseGlobalRsrcDeps\n'
1267 'SET tsExpire = CURRENT_TIMESTAMP\n'
1268 'WHERE idTestCase = %s\n'
1269 ' AND tsExpire = \'infinity\'::timestamp\n'
1270 , (oData.idTestCase,));
1271 asKeepers = [];
1272 for idDep in aidOldDeps:
1273 if idDep in aidNewDeps:
1274 asKeepers.append(str(idDep));
1275 if asKeepers:
1276 sQuery = ' AND idGlobalRsrc NOT IN (' + ', '.join(asKeepers) + ')\n';
1277 self._oDb.execute(sQuery);
1278
1279 for idDep in aidNewDeps:
1280 if idDep not in aidOldDeps:
1281 self._oDb.execute('INSERT INTO TestCaseGlobalRsrcDeps (idTestCase, idGlobalRsrc, uidAuthor)\n'
1282 'VALUES (%s, %s, %s)\n'
1283 , (oData.idTestCase, idDep, uidAuthor) );
1284
1285 #
1286 # Update Test Case Args
1287 # Note! Primary key is idTestCase, tsExpire, sArgs.
1288 #
1289
1290 # Historize rows that have been removed.
1291 sQuery = self._oDb.formatBindArgs('UPDATE TestCaseArgs\n'
1292 'SET tsExpire = CURRENT_TIMESTAMP\n'
1293 'WHERE idTestCase = %s\n'
1294 ' AND tsExpire = \'infinity\'::TIMESTAMP'
1295 , (oData.idTestCase, ));
1296 for oNewVar in oData.aoTestCaseArgs:
1297 asKeepers.append(self._oDb.formatBindArgs('%s', (oNewVar.sArgs,)));
1298 if asKeepers:
1299 sQuery += ' AND sArgs NOT IN (' + ', '.join(asKeepers) + ')\n';
1300 self._oDb.execute(sQuery);
1301
1302 # Add new TestCaseArgs records if necessary, reusing old IDs when possible.
1303 from testmanager.core.testcaseargs import TestCaseArgsData;
1304 for oNewVar in oData.aoTestCaseArgs:
1305 self._oDb.execute('SELECT *\n'
1306 'FROM TestCaseArgs\n'
1307 'WHERE idTestCase = %s\n'
1308 ' AND sArgs = %s\n'
1309 'ORDER BY tsExpire DESC\n'
1310 'LIMIT 1\n'
1311 , (oData.idTestCase, oNewVar.sArgs,));
1312 aoRow = self._oDb.fetchOne();
1313 if aoRow is None:
1314 # New
1315 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1316 ' idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1317 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1318 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)'
1319 , ( oData.idTestCase, uidAuthor, oNewVar.sArgs, oNewVar.cSecTimeout,
1320 oNewVar.sTestBoxReqExpr, oNewVar.sBuildReqExpr, oNewVar.cGangMembers, oNewVar.sSubName));
1321 else:
1322 oCurVar = TestCaseArgsData().initFromDbRow(aoRow);
1323 if self._oDb.isTsInfinity(oCurVar.tsExpire):
1324 # Existing current entry, updated if changed.
1325 if oNewVar.cSecTimeout == oCurVar.cSecTimeout \
1326 and oNewVar.sTestBoxReqExpr == oCurVar.sTestBoxReqExpr \
1327 and oNewVar.sBuildReqExpr == oCurVar.sBuildReqExpr \
1328 and oNewVar.cGangMembers == oCurVar.cGangMembers \
1329 and oNewVar.sSubName == oCurVar.sSubName:
1330 oNewVar.idTestCaseArgs = oCurVar.idTestCaseArgs;
1331 oNewVar.idGenTestCaseArgs = oCurVar.idGenTestCaseArgs;
1332 continue; # Unchanged.
1333 self._oDb.execute('UPDATE TestCaseArgs SET tsExpire = CURRENT_TIMESTAMP WHERE idGenTestCaseArgs = %s\n'
1334 , (oCurVar.idGenTestCaseArgs, ));
1335 else:
1336 # Existing old entry, re-use the ID.
1337 pass;
1338 self._oDb.execute('INSERT INTO TestCaseArgs (\n'
1339 ' idTestCaseArgs, idTestCase, uidAuthor, sArgs, cSecTimeout,\n'
1340 ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n'
1341 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
1342 'RETURNING idGenTestCaseArgs\n'
1343 , ( oCurVar.idTestCaseArgs, oData.idTestCase, uidAuthor, oNewVar.sArgs, oNewVar.cSecTimeout,
1344 oNewVar.sTestBoxReqExpr, oNewVar.sBuildReqExpr, oNewVar.cGangMembers, oNewVar.sSubName));
1345 oNewVar.idGenTestCaseArgs = self._oDb.fetchOne()[0];
1346
1347 self._oDb.maybeCommit(fCommit);
1348 return True;
1349
1350 def removeEntry(self, uidAuthor, idTestCase, fCascade = False, fCommit = False):
1351 """ Deletes the test case if possible. """
1352 self._oDb.callProc('TestCaseLogic_delEntry', (uidAuthor, idTestCase, fCascade));
1353 self._oDb.maybeCommit(fCommit);
1354 return True
1355
1356
1357 def getTestCasePreReqIds(self, idTestCase, tsEffective = None, cMax = None):
1358 """
1359 Returns an array of prerequisite testcases (IDs) for the given testcase.
1360 May raise exception on database error or if the result exceeds cMax.
1361 """
1362 if tsEffective is None:
1363 self._oDb.execute('SELECT idTestCasePreReq\n'
1364 'FROM TestCaseDeps\n'
1365 'WHERE idTestCase = %s\n'
1366 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1367 'ORDER BY idTestCasePreReq\n'
1368 , (idTestCase,) );
1369 else:
1370 self._oDb.execute('SELECT idTestCasePreReq\n'
1371 'FROM TestCaseDeps\n'
1372 'WHERE idTestCase = %s\n'
1373 ' AND tsExpire > %s\n'
1374 ' AND tsEffective <= %s\n'
1375 'ORDER BY idTestCasePreReq\n'
1376 , (idTestCase, tsEffective, tsEffective) );
1377
1378
1379 if cMax is not None and self._oDb.getRowCount() > cMax:
1380 raise TMExceptionBase('Too many prerequisites for testcase %s: %s, max %s'
1381 % (idTestCase, cMax, self._oDb.getRowCount(),));
1382
1383 aidPreReqs = [];
1384 for aoRow in self._oDb.fetchAll():
1385 aidPreReqs.append(aoRow[0]);
1386 return aidPreReqs;
1387
1388
1389 def cachedLookup(self, idTestCase):
1390 """
1391 Looks up the most recent TestCaseDataEx object for idTestCase
1392 via an object cache.
1393
1394 Returns a shared TestCaseDataEx object. None if not found.
1395 Raises exception on DB error.
1396 """
1397 if self.dCache is None:
1398 self.dCache = self._oDb.getCache('TestCaseDataEx');
1399 oEntry = self.dCache.get(idTestCase, None);
1400 if oEntry is None:
1401 fNeedTsNow = False;
1402 self._oDb.execute('SELECT *\n'
1403 'FROM TestCases\n'
1404 'WHERE idTestCase = %s\n'
1405 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1406 , (idTestCase, ));
1407 if self._oDb.getRowCount() == 0:
1408 # Maybe it was deleted, try get the last entry.
1409 self._oDb.execute('SELECT *\n'
1410 'FROM TestCases\n'
1411 'WHERE idTestCase = %s\n'
1412 'ORDER BY tsExpire DESC\n'
1413 'LIMIT 1\n'
1414 , (idTestCase, ));
1415 fNeedTsNow = True;
1416 elif self._oDb.getRowCount() > 1:
1417 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestCase));
1418
1419 if self._oDb.getRowCount() == 1:
1420 aaoRow = self._oDb.fetchOne();
1421 oEntry = TestCaseDataEx();
1422 tsNow = oEntry.initFromDbRow(aaoRow).tsEffective if fNeedTsNow else None;
1423 oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow);
1424 self.dCache[idTestCase] = oEntry;
1425 return oEntry;
1426
1427
1428
1429#
1430# Unit testing.
1431#
1432
1433# pylint: disable=missing-docstring
1434class TestCaseGlobalRsrcDepDataTestCase(ModelDataBaseTestCase):
1435 def setUp(self):
1436 self.aoSamples = [TestCaseGlobalRsrcDepData(),];
1437
1438class TestCaseDataTestCase(ModelDataBaseTestCase):
1439 def setUp(self):
1440 self.aoSamples = [TestCaseData(),];
1441
1442 def testEmptyExpr(self):
1443 self.assertEqual(TestCaseData.validateTestBoxReqExpr(None), None);
1444 self.assertEqual(TestCaseData.validateTestBoxReqExpr(''), None);
1445
1446 def testSimpleExpr(self):
1447 self.assertEqual(TestCaseData.validateTestBoxReqExpr('cMbMemory > 10'), None);
1448 self.assertEqual(TestCaseData.validateTestBoxReqExpr('cMbScratch < 10'), None);
1449 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu'), None);
1450 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is True'), None);
1451 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is False'), None);
1452 self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is None'), None);
1453 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(fChipsetIoMmu, bool)'), None);
1454 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(iTestBoxScriptRev, int)'), None);
1455 self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(cMbScratch, long)'), None);
1456
1457 def testBadExpr(self):
1458 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('this is an bad expression, surely it must be'), None);
1459 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('x = 1 + 1'), None);
1460 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('__import__(\'os\').unlink(\'/tmp/no/such/file\')'), None);
1461 self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('print "foobar"'), None);
1462
1463class TestCaseDataExTestCase(ModelDataBaseTestCase):
1464 def setUp(self):
1465 self.aoSamples = [TestCaseDataEx(),];
1466
1467if __name__ == '__main__':
1468 unittest.main();
1469 # not reached.
1470
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