From ab8952177dea66cc9962914ba73b4d683faef412 Mon Sep 17 00:00:00 2001 From: mbs Date: Fri, 15 Dec 2023 11:32:20 +0000 Subject: [PATCH] updated location of test scripts --- CMakeLists.txt | 2 + check_validity.py | 66 ++++++++++ src/SHAPERGUI/SHAPERGUI.cpp | 166 ++++++++++++++++-------- src/SHAPERGUI/SHAPERGUI.h | 11 +- src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp | 20 ++- src/XGUI/XGUI_Workshop.h | 2 + test_backup.py | 57 ++++++++ 7 files changed, 257 insertions(+), 67 deletions(-) create mode 100755 check_validity.py create mode 100755 test_backup.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 61d60a42c..e39ef09c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,6 +129,8 @@ IF(${HAVE_SALOME}) # without SALOME there is another kind of documentation, separated one SALOME_INSTALL_SCRIPTS("shaper_test.py" ${SHAPER_INSTALL_PYTHON_FILES}) + SALOME_INSTALL_SCRIPTS("test_backup.py" ${SHAPER_INSTALL_PYTHON_FILES}) + SALOME_INSTALL_SCRIPTS("check_validity.py" ${SHAPER_INSTALL_PYTHON_FILES}) ELSE(${HAVE_SALOME}) SET(SHAPER_INSTALL_EXE bin CACHE INTERNAL "" FORCE) SET(SHAPER_INSTALL_ADDONS addons CACHE INTERNAL "" FORCE) diff --git a/check_validity.py b/check_validity.py new file mode 100755 index 000000000..f51d08f41 --- /dev/null +++ b/check_validity.py @@ -0,0 +1,66 @@ +import salome +import sys +import os +import SalomePyQt +import ModelAPI +#import PartSetAPI +#from salome.shaper import model + + +if len(sys.argv) < 2: + raise Exception("no study given to validate") + +outFolder,filename = os.path.split(sys.argv[1]) +base = os.path.splitext(filename)[0] + +with open(os.path.join(outFolder, base+"_valid.log"), "w") as f: + f.write("#args = {}\n".format(len(sys.argv))) + lastIdx = len(sys.argv)-1 + f.write("#arg[{}] = {}\n".format(lastIdx, sys.argv[lastIdx])) + + salome.standalone() + f.write("initializing SALOME\n") + salome.salome_init(sys.argv[1], embedded=True, forced=True) + f.write("StudyName = {}\n".format(salome.myStudyName)) + f.write("Study = {}\n".format(salome.myStudy)) + + session = salome.naming_service.Resolve('/Kernel/Session') + f.write("session = {}\n".format(session)) + session.emitMessage("connect_to_study") + f.write("session = {}\n".format(session)) + + sg = SalomePyQt.SalomePyQt() + sg.activateModule("Shaper") + session = ModelAPI.ModelAPI_Session.get() + aFactory = session.validators() + aPartSet = session.moduleDocument() + numParts = aPartSet.size("Parts") + # f.write("NumParts = {}\n".format(numParts)) + # numFeats = aPartSet.size("Features") + # f.write("NumFeats = {}\n".format(numFeats)) + # numBodies = aPartSet.size("Bodies") + # f.write("NumBodies = {}\n".format(numBodies)) + # numGroups = aPartSet.size("Groups") + # f.write("NumGroups = {}\n".format(numGroups)) + # numConsts = aPartSet.size("Construction") + # f.write("NumConstr = {}\n".format(numConsts)) + # numFolders = aPartSet.size("Folders") + # f.write("NumFolder = {}\n".format(numFolders)) + for partIdx in range(numParts+1): + aPart = session.document(partIdx) + if aPart is None: + continue + aPart.setActive(True) + if partIdx == 0: + f.write("---PartSet:------------------\n") + else: + f.write(f"---Part_{partIdx}:------------------\n") + for aFeat in aPart.allFeatures(): + if aFeat.isInHistory(): + f.write(" * {} --> [{}]\n".format(aFeat.getKind(), aFeat.data().name())) + else: + f.write(" - {}\n".format(aFeat.data().name())) + + f.write("done!\n") + +exit() diff --git a/src/SHAPERGUI/SHAPERGUI.cpp b/src/SHAPERGUI/SHAPERGUI.cpp index 4b61fa21d..8d97ebf82 100644 --- a/src/SHAPERGUI/SHAPERGUI.cpp +++ b/src/SHAPERGUI/SHAPERGUI.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,7 @@ #define MBCLASSNAME "SHAPERGUI" #include "MBDebug.h" // <-- insert includes for addtional debug headers here! +//#define DBG_BACKUP_INTERVAL 1 /*Use a short 1 min interval for auto backup for debugging*/ //--------------------------------------------------------- #if OCC_VERSION_HEX < 0x070400 @@ -152,14 +154,6 @@ private: }; -// static void DumpOperations(XGUI_Workshop *wshop) -// { -// XGUI_OperationMgr *aOpMgr = wshop->operationMgr(); -// if (!aOpMgr) return; -// QStringList operations = aOpMgr->operationList(); -// SHOW(operations); -// } - //****************************************************** SHAPERGUI::SHAPERGUI() @@ -424,7 +418,9 @@ bool SHAPERGUI::activateModule(SUIT_Study* theStudy) if (useBackup) { int backupInterval = aResMgr->integerValue( "General", "backup_interval", 1 ); if ( backupInterval > 0 ){ - backupInterval = 1; +#ifdef DBG_BACKUP_INTERVAL + backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging +#endif MSGEL("....starting BackupTimer: interval=" << backupInterval); myBackupTimer->start( backupInterval*60000 ); } @@ -610,20 +606,7 @@ void SHAPERGUI::onOperationCommitted(ModuleBase_Operation* theOperation) onOperationGeneric(theOperation, moduleName(), "committed"); - if (myWorkshop && myWorkshop->waitForBackup()) - { - XGUI_OperationMgr *operMgr = myWorkshop->operationMgr(); - if (operMgr && operMgr->hasOperation()) - { - // If the user is still running an operation, e.g. we left the line creation - // during sketch creation, do not yet create the backup - std::cout << "---> There are still active operations!" << std::endl; - return; - } - // There are no more active operations => we can now do the backup - WaitBackupResetter _resetter(myWorkshop); - onBackupDoc(); - } + checkForWaitingBackup(); } //****************************************************** @@ -634,6 +617,12 @@ void SHAPERGUI::onOperationAborted(ModuleBase_Operation* theOperation) onOperationGeneric(theOperation, moduleName(), "aborted"); + checkForWaitingBackup(); +} + +//****************************************************** +void SHAPERGUI::checkForWaitingBackup() +{ if (myWorkshop && myWorkshop->waitForBackup()) { XGUI_OperationMgr *operMgr = myWorkshop->operationMgr(); @@ -764,6 +753,8 @@ void SHAPERGUI::onBackupDoc() DBG_FUN(); MSGEL(" ...backing up current study"); + // We cannot save the study while we are still in an ongoing operation + // => so test for this case first and delay the backup to the time when operation finishes if (myWorkshop && myWorkshop->operationMgr()) { if (myWorkshop->operationMgr()->hasOperation()) @@ -797,32 +788,46 @@ bool SHAPERGUI::backupDoc() { DBG_FUN(); + if (myWorkshop->backupState()) { + // This should never happen as I restart the backup timer only when a backup has finished + myBackupError = tr("Another backup is still running"); + return false; + } + bool isOk = false; -#if 0 - MSGEL("...sleep for 5 seconds"); - std::this_thread::sleep_for(std::chrono::seconds(5)); - isOk = true; - QString aName("/some/file/name.hdf"); -#else SUIT_Study* study = application()->activeStudy(); - if ( !study ) + if ( !study ) { + myBackupError = tr("There is no active study"); return false; - - // if ( !abortAllOperations() ) - // return false; + } LockBackupState lockBackup(myWorkshop); - QString aName = study->studyName(); + QString aFolder{}; try { + QString aName = study->studyName(); SHOW(aName); - if ( aName.isNull() ) + if ( aName.isEmpty() ) { + myBackupError = tr("Study name is empty"); return false; + } const QChar aSep = QDir::separator(); - - QString aFolder("/home/bernhard/backups"); + //MBS: + SUIT_ResourceMgr* aResMgr = application()->resourceMgr(); + if ( aResMgr && application()->activeStudy() ) { + aFolder = aResMgr->path( "General", "backup_folder", "" ); + } + if (aFolder.isEmpty()) { +#ifdef HAVE_SALOME + aFolder = XGUI_Tools::getTmpDirByEnv("SALOME_TMP_DIR").c_str(); +#else + aFolder = XGUI_Tools::getTmpDirByEnv("").c_str(); +#endif + } + if (aFolder.endsWith(aSep)) + aFolder = aFolder.remove(aFolder.length()-1,1); QDateTime now = QDateTime::currentDateTime(); aFolder += aSep + now.toString("yyyyMMdd_hhmmss"); @@ -831,6 +836,10 @@ bool SHAPERGUI::backupDoc() if (!aDir.exists()) { aDir.mkpath(aFolder); aDir.mkdir(aFolder); + if (!aDir.exists()) { + myBackupError = tr("Cannot create backup folder"); + return false; + } } if (study->isSaved()) { @@ -844,58 +853,109 @@ bool SHAPERGUI::backupDoc() // Save the study into a single HDF file isOk = study->saveDocumentAs( aFullName, true ); if (!isOk){ - MSGEL("ERR: failed to backup study document"); - return isOk; + myBackupError = tr("Cannot backup study document"); + return false; } // Now, dump the python script LightApp_Study *lightStudy = dynamic_cast(study); - if (!lightStudy) + if (!lightStudy) { + myBackupError = tr("Study is not dumpable"); return false; + } aFullName = aFolder + aSep + aName + QString(".py"); isOk = lightStudy->dump(aFullName, true, false, false); if (!isOk){ - MSGEL("ERR: failed to backup python script"); - return isOk; + myBackupError = tr("Cannot backup python script"); + return false; } // Finally start another salome process and reload the saved document & script for verification + SHOW(aFolder); SHAPERGUI_CheckBackup checkBackup(aFolder, aName); - QString testScript("/home/bernhard/backups/checkValidity.py"); + QString testBackup("check_validity.py"); + QStringList dirs; + dirs << QString(std::getenv("SHAPER_ROOT_DIR")) + << QString("bin") + << QString("salome") + << testBackup; + QString testScript = dirs.join( QDir::separator() ); int aResult = checkBackup.run(testScript); isOk = (aResult == 0); } catch (std::exception &ex) { - std::cout << "ERROR: std::exception caught" << std::endl; + myBackupError = tr("std::exception caught"); isOk = false; } catch (...) { - std::cout << "ERROR: unknown exception caught" << std::endl; + myBackupError = tr("unknown exception caught"); isOk = false; } - #endif + MSGEL("...emit backupDone signal"); - emit backupDone(aName, isOk); + SHOW(aFolder); + SHOW(isOk); + emit backupDone(aFolder, isOk); return isOk; } //****************************************************** -void SHAPERGUI::onBackupDone(QString aName, bool aResult) +void SHAPERGUI::onBackupDone(QString aFolder, bool aResult) { DBG_FUN(); - ARG(aName); + ARG(aFolder); ARG(aResult); bool isOk = myBackupResult.get(); SHOW(isOk); - QFileInfo fi(aName); - aName = fi.canonicalPath(); - SHOW(aName); - putInfo( isOk ? tr("Backup done in folder: %1").arg(aName) : tr("Failed to backup active study!"), 5000 ); + putInfo( isOk ? tr("Backup done in folder: %1").arg(aFolder) : tr("Failed to backup active study!"), 5000 ); + + int aBackupStorage{-1}; + SUIT_ResourceMgr* aResMgr = application()->resourceMgr(); + if ( aResMgr ) { + aBackupStorage = aResMgr->integerValue( "General", "auto_backup_storage", -1); + } + if (aBackupStorage == 0/*StoreLastBackupOnly*/) { + // Only keep the latest successful backup => delete the previous one, if it exists + if (isOk && !myLastBackupFolder.isEmpty()) { + // Delete the previous backup folder + // To avoid deleting accidently an incorrect folder, check for + // the correct content. A backup folder should have 3-5 files: + // * .hdf - the study itself + // * .log - the output from the additional SALOME instance + // * .py - the python dump + // * _test.log - the output of the "test_backup.py" script + // * _valid.log - the output of the "check_validity.py" script + QDir dir(myLastBackupFolder); + QStringList files = dir.entryList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot); + if (!files.isEmpty() && files.length() <= 3) { + QString baseName = files.constFirst(); + baseName = baseName.left(baseName.lastIndexOf('.')); + if (!baseName.isEmpty() && files.filter(baseName).length() == files.length()) + dir.removeRecursively(); + } + } + myLastBackupFolder = aFolder; + } + + // Start the timer again + if ( aResMgr && application()->activeStudy() ) { + bool useBackup = aResMgr->booleanValue( "General", "use_auto_backup", true ); + if (useBackup) { + int backupInterval = aResMgr->integerValue( "General", "backup_interval", 1 ); + if ( backupInterval > 0 ){ +#ifdef DBG_BACKUP_INTERVAL + backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging +#endif + MSGEL("....starting BackupTimer: interval=" << backupInterval); + myBackupTimer->start( backupInterval*60000 ); + } + } + } } //****************************************************** diff --git a/src/SHAPERGUI/SHAPERGUI.h b/src/SHAPERGUI/SHAPERGUI.h index 173426f82..e3d6e76bd 100644 --- a/src/SHAPERGUI/SHAPERGUI.h +++ b/src/SHAPERGUI/SHAPERGUI.h @@ -238,6 +238,7 @@ Q_OBJECT /// Abort all operations virtual bool abortAllOperations(); + /// The automatic backup thread function bool backupDoc(); private slots: @@ -277,6 +278,9 @@ private slots: void hideInternalWindows(); + // Check, whether we are waiting for a backup => if yes, do it now + void checkForWaitingBackup(); + /// List of registered nested actions QStringList myNestedActionsList; @@ -319,8 +323,11 @@ private slots: double myAxisArrowRate; - QTimer* myBackupTimer; - std::future myBackupResult; + /// Automatic backup + QTimer* myBackupTimer; // The timer which triggers the automatic backup + std::future myBackupResult; // The result (succes/failure) of the backup + QString myBackupError; // The backup error message in case of a failure + QString myLastBackupFolder; // The folder of the last backup }; #endif diff --git a/src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp b/src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp index b5e643e49..6af1d0a5a 100644 --- a/src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp +++ b/src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp @@ -61,17 +61,10 @@ int SHAPERGUI_CheckBackup::run(const QString &theTestScript) QString testBackup("test_backup.py"); QStringList dirs; -#if 0 dirs << QString(std::getenv("SHAPER_ROOT_DIR")) << QString("bin") << QString("salome") << testBackup; -#else - dirs << QString(std::getenv("HOME")) - << QString("S2") - << QString("SALOME-MBS-DB11") - << testBackup; -#endif QString scriptName = dirs.join( QDir::separator() ); QStringList args; @@ -81,12 +74,15 @@ int SHAPERGUI_CheckBackup::run(const QString &theTestScript) if (!myProcess) return 2; - connect(myProcess, SIGNAL(started()), this, SLOT(procStarted())); - connect(myProcess, SIGNAL(error(QProcess::ProcessError)), this, - SLOT(procError(QProcess::ProcessError))); - connect(myProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, - SLOT(procFinished(int, QProcess::ExitStatus))); + // connect(myProcess, SIGNAL(started()), this, SLOT(procStarted())); + // connect(myProcess, SIGNAL(error(QProcess::ProcessError)), this, + // SLOT(procError(QProcess::ProcessError))); + // connect(myProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, + // SLOT(procFinished(int, QProcess::ExitStatus))); + // Start a python script (test_backup.py), which itself + // * launches a SALOME process + // * opens the previously backed up HDF study SHOW(aProgName); SHOW(args); myProcess->setStandardOutputFile((QStringList() << myFolder << myBaseName+".log").join(aSep)); diff --git a/src/XGUI/XGUI_Workshop.h b/src/XGUI/XGUI_Workshop.h index 676a44d46..1853feb8e 100644 --- a/src/XGUI/XGUI_Workshop.h +++ b/src/XGUI/XGUI_Workshop.h @@ -356,6 +356,8 @@ Q_OBJECT void setBackupState(bool theDoBackup=true); + bool backupState() const { return myDoBackup; } + void setWaitForBackup(bool theDoWait=true); bool waitForBackup() const { return myWaitForBackup; } diff --git a/test_backup.py b/test_backup.py new file mode 100755 index 000000000..792390150 --- /dev/null +++ b/test_backup.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2023 OPEN CASCADE SAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + + +import subprocess +import sys, os +import tempfile + +testTimeout = 600 + +# if len(sys.argv) != 4: +# raise Exception("check_validity.py could not be found. Check your environment.") + +backupFolder = sys.argv[1] +baseName = sys.argv[2] +checkScript = sys.argv[3] + +errCode = 0 +with open(os.path.join(backupFolder, baseName+"_test.log"), "w") as f: + f.write("script started\n") + f.write(" backupFolder = {}\n".format(backupFolder)) + f.write(" baseName = {}\n".format(baseName)) + f.write(" checkScript = {}\n".format(checkScript)) + hdffile = os.path.join(backupFolder, baseName+".hdf") + pyfile = os.path.join(backupFolder, baseName+".py") + + f.write("starting SALOME process:\n") + proc = subprocess.Popen(["runSalome.py", "--modules", "SHAPER,SHAPERSTUDY", "--batch", "--splash", "0", checkScript, "args:" + hdffile]) + try: + f.write("start communication with SALOME\n") + proc.communicate(timeout = testTimeout) + f.write("SALOME terminated\n") + except subprocess.TimeoutExpired: + errCode = 99 + f.write("SALOME timed out\n") + + f.write("\ndone.\n") + +exit(errCode) \ No newline at end of file -- 2.39.2