- Timestamp:
- Mar 24, 2020 2:46:06 PM (5 years ago)
- Location:
- trunk/src/VBox/ValidationKit/testmanager/core
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/ValidationKit/testmanager/core/base.py
r83359 r83384 404 404 enmValidateFor = enmValidateFor); 405 405 406 def validateAndConvertEx(self, asAllowNullAttributes, oDb, enmValidateFor = ksValidateFor_Other): 407 """ 408 Same as validateAndConvert but with custom allow-null list. 409 """ 410 return self._validateAndConvertWorker(asAllowNullAttributes, oDb, enmValidateFor = enmValidateFor); 411 406 412 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict): 407 413 """ -
trunk/src/VBox/ValidationKit/testmanager/core/schedgroup.py
r83364 r83384 35 35 # Validation Kit imports. 36 36 from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \ 37 TMRowInUse, TMInvalidData, TMRowAlreadyExists, TMRowNotFound; 37 TMRowInUse, TMInvalidData, TMRowAlreadyExists, TMRowNotFound, \ 38 ChangeLogEntry, AttributeChangeEntry, AttributeChangeEntryPre; 38 39 from testmanager.core.buildsource import BuildSourceData; 40 from testmanager.core import db; 39 41 from testmanager.core.testcase import TestCaseData; 40 42 from testmanager.core.testcaseargs import TestCaseArgsData; 41 43 from testmanager.core.testbox import TestBoxLogic, TestBoxDataForSchedGroup; 42 44 from testmanager.core.testgroup import TestGroupData; 45 from testmanager.core.useraccount import UserAccountLogic; 43 46 44 47 … … 144 147 dErrors[self.ksParam_idTestGroup] = str(oXcpt); 145 148 return dErrors; 149 146 150 147 151 … … 300 304 # 301 305 # Test groups. 306 # The fetchForChangeLog method makes ASSUMPTIONS about sorting! 302 307 # 303 308 oDb.execute('SELECT SchedGroupMembers.*, TestGroups.*\n' … … 307 312 + self.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix = 'SchedGroupMembers.') 308 313 + self.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix = 'TestGroups.') + 309 'ORDER BY SchedGroupMembers.idTestGroupPreReq, SchedGroupMembers.idTestGroup\n' 314 'ORDER BY SchedGroupMembers.idTestGroupPreReq ASC NULLS FIRST,\n' 315 ' TestGroups.sName,\n' 316 ' SchedGroupMembers.idTestGroup\n' 310 317 , (self.idSchedGroup,)); 311 318 for aoRow in oDb.fetchAll(): … … 351 358 oMember = SchedGroupMemberDataEx().initFromParams(oDispWrapper, fStrict = False); 352 359 if idTestGroup in aidSelected: 360 oMember.idTestGroup = idTestGroup; 353 361 aoNewValue.append(oMember); 354 362 elif sAttr == 'aoTestBoxes': … … 359 367 except: pass; 360 368 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (SchedGroupDataEx.ksParam_aoTestBoxes, idTestBox,)) 361 o TestBox= TestBoxDataForSchedGroup().initFromParams(oDispWrapper, fStrict = False);369 oBoxInGrp = TestBoxDataForSchedGroup().initFromParams(oDispWrapper, fStrict = False); 362 370 if idTestBox in aidSelected: 363 aoNewValue.append(oTestBox); 371 oBoxInGrp.idTestBox = idTestBox; 372 aoNewValue.append(oBoxInGrp); 364 373 else: 365 374 return SchedGroupData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict); … … 370 379 return SchedGroupData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); 371 380 381 if oValue in aoNilValues: 382 return ([], None); 383 372 384 asErrors = []; 373 385 aoNewMembers = []; 374 386 if sAttr == 'aoMembers': 387 asAllowNulls = ['bmHourlySchedule', 'idTestGroupPreReq', 'tsEffective', 'tsExpire', 'uidAuthor', ]; 388 if self.idSchedGroup in [None, '-1', -1]: 389 asAllowNulls.append('idSchedGroup'); # Probably new group, so allow null scheduling group. 390 375 391 for oOldMember in oValue: 376 392 oNewMember = SchedGroupMemberDataEx().initFromOther(oOldMember); 377 393 aoNewMembers.append(oNewMember); 378 394 379 dErrors = oNewMember.validateAndConvert (oDb, ModelDataBase.ksValidateFor_Other);395 dErrors = oNewMember.validateAndConvertEx(asAllowNulls, oDb, ModelDataBase.ksValidateFor_Other); 380 396 if dErrors: 381 397 asErrors.append(str(dErrors)); … … 389 405 break; 390 406 else: 407 asAllowNulls = list(TestBoxDataForSchedGroup.kasAllowNullAttributes); 408 if self.idSchedGroup in [None, '-1', -1]: 409 asAllowNulls.append('idSchedGroup'); # Probably new group, so allow null scheduling group. 410 391 411 for oOldMember in oValue: 392 412 oNewMember = TestBoxDataForSchedGroup().initFromOther(oOldMember); 393 413 aoNewMembers.append(oNewMember); 394 414 395 dErrors = oNewMember.validateAndConvert (oDb, ModelDataBase.ksValidateFor_Other);415 dErrors = oNewMember.validateAndConvertEx(asAllowNulls, oDb, ModelDataBase.ksValidateFor_Other); 396 416 if dErrors: 397 417 asErrors.append(str(dErrors)); … … 402 422 for j in range(i + 1, len(aoNewMembers)): 403 423 if aoNewMembers[j].idTestBox == idTestBox: 404 asErrors.append('Duplicate test group#%d!' % (idTestBox, ));424 asErrors.append('Duplicate test box #%d!' % (idTestBox, )); 405 425 break; 406 407 426 408 427 return (aoNewMembers, None if not asErrors else '<br>\n'.join(asErrors)); … … 482 501 aoRet.append(SchedGroupDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow)); 483 502 return aoRet; 503 504 def fetchForChangeLog(self, idSchedGroup, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals 505 """ 506 Fetches change log entries for a scheduling group. 507 508 Returns an array of ChangeLogEntry instance and an indicator whether 509 there are more entries. 510 Raises exception on error. 511 """ 512 513 ## @todo calc changes to scheduler group! 514 515 if tsNow is None: 516 tsNow = self._oDb.getCurrentTimestamp(); 517 518 # 519 # First gather the change log timeline using the effective dates. 520 # (ASSUMES that we'll always have a separate delete entry, rather 521 # than just setting tsExpire.) 522 # 523 self._oDb.execute(''' 524 ( 525 SELECT tsEffective, 526 uidAuthor 527 FROM SchedGroups 528 WHERE idSchedGroup = %s 529 AND tsEffective <= %s 530 ORDER BY tsEffective DESC 531 ) UNION ( 532 SELECT tsEffective, 533 uidAuthor 534 FROM SchedGroupMembers 535 WHERE idSchedGroup = %s 536 AND tsEffective <= %s 537 ORDER BY tsEffective DESC 538 ) UNION ( 539 SELECT tsEffective, 540 uidAuthor 541 FROM TestBoxesInSchedGroups 542 WHERE idSchedGroup = %s 543 AND tsEffective <= %s 544 ORDER BY tsEffective DESC 545 ) 546 ORDER BY tsEffective DESC 547 LIMIT %s OFFSET %s 548 ''', (idSchedGroup, tsNow, idSchedGroup, tsNow, idSchedGroup, tsNow, cMaxRows + 1, iStart, )); 549 550 aoEntries = [] # type: list[ChangeLogEntry] 551 tsPrevious = tsNow; 552 for aoDbRow in self._oDb.fetchAll(): 553 (tsEffective, uidAuthor) = aoDbRow; 554 aoEntries.append(ChangeLogEntry(uidAuthor, None, tsEffective, tsPrevious, None, None, [])); 555 tsPrevious = db.dbTimestampPlusOneTick(tsEffective); 556 557 if True: 558 # 559 # Fetch data for each for each change log entry point. 560 # 561 # We add one tick to the timestamp here to skip past delete records 562 # that only there to record the user doing the deletion. 563 # 564 for iEntry, oEntry in enumerate(aoEntries): 565 oEntry.oNewRaw = SchedGroupDataEx().initFromDbWithId(self._oDb, idSchedGroup, 566 db.dbTimestampPlusOneTick(oEntry.tsEffective)); 567 if iEntry > 0: 568 aoEntries[iEntry - 1].oOldRaw = oEntry.oNewRaw; 569 570 # Chop off the +1 entry, if any. 571 fMore = len(aoEntries) > cMaxRows; 572 if fMore: 573 aoEntries = aoEntries[:-1]; 574 575 # Figure out the changes. 576 for oEntry in aoEntries: 577 oOld = oEntry.oOldRaw; 578 if not oOld: 579 break; 580 oNew = oEntry.oNewRaw; 581 aoChanges = oEntry.aoChanges; 582 for sAttr in oNew.getDataAttributes(): 583 if sAttr in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]: 584 continue; 585 oOldAttr = getattr(oOld, sAttr); 586 oNewAttr = getattr(oNew, sAttr); 587 if oOldAttr == oNewAttr: 588 continue; 589 if sAttr in [ 'aoMembers', 'aoTestBoxes', ]: 590 iNew = 0; 591 iOld = 0; 592 asNewAttr = []; 593 asOldAttr = []; 594 if sAttr == 'aoMembers': 595 # ASSUMES aoMembers is sorted by idTestGroupPreReq (nulls first), oTestGroup.sName, idTestGroup! 596 while iNew < len(oNewAttr) and iOld < len(oOldAttr): 597 if oNewAttr[iNew].idTestGroup == oOldAttr[iOld].idTestGroup: 598 if oNewAttr[iNew].idTestGroupPreReq != oOldAttr[iOld].idTestGroupPreReq: 599 if oNewAttr[iNew].idTestGroupPreReq is None: 600 asOldAttr.append('Dropped test group #%s (%s) dependency on #%s' 601 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, 602 oOldAttr[iOld].idTestGroupPreReq)); 603 elif oOldAttr[iOld].idTestGroupPreReq is None: 604 asNewAttr.append('Added test group #%s (%s) dependency on #%s' 605 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, 606 oNewAttr[iOld].idTestGroupPreReq)); 607 else: 608 asNewAttr.append('Test group #%s (%s) dependency on #%s' 609 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, 610 oNewAttr[iNew].idTestGroupPreReq)); 611 asOldAttr.append('Test group #%s (%s) dependency on #%s' 612 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, 613 oOldAttr[iOld].idTestGroupPreReq)); 614 if oNewAttr[iNew].iSchedPriority != oOldAttr[iOld].iSchedPriority: 615 asNewAttr.append('Test group #%s (%s) priority %s' 616 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, 617 oNewAttr[iNew].iSchedPriority)); 618 asOldAttr.append('Test group #%s (%s) priority %s' 619 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, 620 oOldAttr[iOld].iSchedPriority)); 621 iNew += 1; 622 iOld += 1; 623 elif oNewAttr[iNew].oTestGroup.sName < oOldAttr[iOld].oTestGroup.sName \ 624 or ( oNewAttr[iNew].oTestGroup.sName == oOldAttr[iOld].oTestGroup.sName 625 and oNewAttr[iNew].idTestGroup < oOldAttr[iOld].idTestGroup): 626 asNewAttr.append('New test group #%s - %s' 627 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName)); 628 iNew += 1; 629 else: 630 asOldAttr.append('Removed test group #%s - %s' 631 % (oOldAttr[iOld].idTestGroup, oOldAttr[iOld].oTestGroup.sName)); 632 iOld += 1; 633 while iNew < len(oNewAttr): 634 asNewAttr.append('New test group #%s - %s' 635 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName)); 636 iNew += 1; 637 while iOld < len(oOldAttr): 638 asOldAttr.append('Removed test group #%s - %s' 639 % (oOldAttr[iOld].idTestGroup, oOldAttr[iOld].oTestGroup.sName)); 640 iOld += 1; 641 else: 642 dNewIds = { oBoxInGrp.idTestBox: oBoxInGrp for oBoxInGrp in oNewAttr }; 643 dOldIds = { oBoxInGrp.idTestBox: oBoxInGrp for oBoxInGrp in oOldAttr }; 644 hCommonIds = set(dNewIds.keys()) & set(dOldIds.keys()); 645 for idTestBox in hCommonIds: 646 oNewBoxInGrp = dNewIds[idTestBox]; 647 oOldBoxInGrp = dOldIds[idTestBox]; 648 if oNewBoxInGrp.iSchedPriority != oOldBoxInGrp.iSchedPriority: 649 asNewAttr.append('Test box \'%s\' (#%s) priority %s' 650 % (getattr(oNewBoxInGrp.oTestBox, 'sName', '[Partial DB]'), 651 oNewBoxInGrp.idTestBox, oNewBoxInGrp.iSchedPriority)); 652 asOldAttr.append('Test box \'%s\' (#%s) priority %s' 653 % (getattr(oOldBoxInGrp.oTestBox, 'sName', '[Partial DB]'), 654 oOldBoxInGrp.idTestBox, oOldBoxInGrp.iSchedPriority)); 655 asNewAttr = sorted(asNewAttr); 656 asOldAttr = sorted(asOldAttr); 657 for idTestBox in set(dNewIds.keys()) - hCommonIds: 658 oNewBoxInGrp = dNewIds[idTestBox]; 659 asNewAttr.append('New test box \'%s\' (#%s) priority %s' 660 % (getattr(oNewBoxInGrp.oTestBox, 'sName', '[Partial DB]'), 661 oNewBoxInGrp.idTestBox, oNewBoxInGrp.iSchedPriority)); 662 for idTestBox in set(dOldIds.keys()) - hCommonIds: 663 oOldBoxInGrp = dOldIds[idTestBox]; 664 asOldAttr.append('Removed test box \'%s\' (#%s) priority %s' 665 % (getattr(oOldBoxInGrp.oTestBox, 'sName', '[Partial DB]'), 666 oOldBoxInGrp.idTestBox, oOldBoxInGrp.iSchedPriority)); 667 668 if asNewAttr or asOldAttr: 669 aoChanges.append(AttributeChangeEntryPre(sAttr, oNewAttr, oOldAttr, 670 '\n'.join(asNewAttr), '\n'.join(asOldAttr))); 671 else: 672 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); 673 674 else: 675 ## 676 ## @todo Incomplete: A more complicate apporach, probably faster though. 677 ## 678 def findEntry(tsEffective, iPrev = 0): 679 """ Find entry with matching effective + expiration time """ 680 self._oDb.dprint('findEntry: iPrev=%s len(aoEntries)=%s tsEffective=%s' % (iPrev, len(aoEntries), tsEffective)); 681 while iPrev < len(aoEntries): 682 self._oDb.dprint('%s iPrev=%u' % (aoEntries[iPrev].tsEffective, iPrev, )); 683 if aoEntries[iPrev].tsEffective > tsEffective: 684 iPrev += 1; 685 elif aoEntries[iPrev].tsEffective == tsEffective: 686 self._oDb.dprint('hit %u' % (iPrev,)); 687 return iPrev; 688 else: 689 break; 690 self._oDb.dprint('%s not found!' % (tsEffective,)); 691 return -1; 692 693 fMore = True; 694 695 # 696 # Track scheduling group changes. Not terribly efficient for large cMaxRows 697 # values, but not in the mood for figure out if there is any way to optimize that. 698 # 699 self._oDb.execute(''' 700 SELECT * 701 FROM SchedGroups 702 WHERE idSchedGroup = %s 703 AND tsEffective <= %s 704 ORDER BY tsEffective DESC 705 LIMIT %s''', (idSchedGroup, aoEntries[0].tsEffective, cMaxRows + 1,)); 706 707 iEntry = 0; 708 aaoRows = self._oDb.fetchAll(); 709 for iRow, oRow in enumerate(aaoRows): 710 oNew = SchedGroupData().initFromDbRow(oRow); 711 iEntry = findEntry(oNew.tsEffective, iEntry); 712 self._oDb.dprint('iRow=%s iEntry=%s' % (iRow, iEntry)); 713 if iEntry < 0: 714 break; 715 oEntry = aoEntries[iEntry]; 716 aoChanges = oEntry.aoChanges; 717 oEntry.oNewRaw = oNew; 718 if iRow + 1 < len(aaoRows): 719 oOld = SchedGroupData().initFromDbRow(aaoRows[iRow + 1]); 720 self._oDb.dprint('oOld=%s' % (oOld,)); 721 for sAttr in oNew.getDataAttributes(): 722 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]: 723 oOldAttr = getattr(oOld, sAttr); 724 oNewAttr = getattr(oNew, sAttr); 725 if oOldAttr != oNewAttr: 726 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); 727 else: 728 self._oDb.dprint('New'); 729 730 # 731 # ... 732 # 733 734 # FInally 735 UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries); 736 return (aoEntries, fMore); 737 484 738 485 739 def addEntry(self, oData, uidAuthor, fCommit = False): -
trunk/src/VBox/ValidationKit/testmanager/core/testbox.py
r83364 r83384 59 59 ksParam_iSchedPriority = 'TestBoxInSchedGroup_iSchedPriority'; 60 60 61 kasAllowNullAttributes = [ ' idTestBox', 'tsEffective', 'tsExpire', 'uidAuthor', ]61 kasAllowNullAttributes = [ 'tsEffective', 'tsExpire', 'uidAuthor', ] 62 62 63 63 kiMin_iSchedPriority = 0; … … 858 858 if not aiSortColumns: 859 859 aiSortColumns = [self.kiSortColumn_sName,]; 860 asSortColumns = [self.kdSortColumnMap[i] for i in aiSortColumns]; 861 asSortColumns.append('TestBoxesInSchedGroups.idTestBox'); 860 862 861 863 if tsNow is None: … … 869 871 WHERE TestBoxesInSchedGroups.idSchedGroup = %s 870 872 AND TestBoxesInSchedGroups.tsExpire = 'infinity'::TIMESTAMP 871 ORDER BY ''' + ', '.join([self.kdSortColumnMap[i] for i in aiSortColumns]) + ''' 872 ''', (idSchedGroup, )); 873 ORDER BY ''' + ', '.join(asSortColumns), (idSchedGroup, )); 873 874 else: 874 875 self._oDb.execute(''' … … 881 882 AND TestBoxesWithStrings.tsEffective <= %s 882 883 WHERE TestBoxesInSchedGroups.idSchedGroup = %s 883 AND TestBoxesInSchedGroups.tsExpire > % 884 AND TestBoxesInSchedGroups.tsEffective <= % 885 ORDER BY ''' + ', '.join([self.kdSortColumnMap[i] for i in aiSortColumns]) + ''' 886 ''', (tsNow, tsNow, idSchedGroup, tsNow, tsNow, )); 884 AND TestBoxesInSchedGroups.tsExpire > %s 885 AND TestBoxesInSchedGroups.tsEffective <= %s 886 ORDER BY ''' + ', '.join(asSortColumns), (tsNow, tsNow, idSchedGroup, tsNow, tsNow, )); 887 887 888 888 aoRows = [];
Note:
See TracChangeset
for help on using the changeset viewer.