]> SALOME platform Git repositories - modules/shaper.git/commitdiff
Salome HOME
code refactoring and bug fixing
authormbs <martin.bernhard@opencascade.com>
Fri, 19 Jan 2024 21:48:55 +0000 (21:48 +0000)
committermbs <martin.bernhard@opencascade.com>
Fri, 19 Jan 2024 23:11:08 +0000 (23:11 +0000)
* backupDoc returns now an error code instead of bool
* added check of dumped python script
* show warning on error
* refactored test and validation scripts

check_validity.py
src/SHAPERGUI/SHAPERGUI.cpp
src/SHAPERGUI/SHAPERGUI.h
src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp
test_backup.py

index 90ddf6b15b582f8a997e61f6b545bbeb3808ca9d..8dc1110920b3f97d43f7d4d71f2fd67036fe2875 100755 (executable)
@@ -3,35 +3,21 @@ import sys
 import os
 import SalomePyQt
 import ModelAPI
-#import PartSetAPI
-#from salome.shaper import model
+from salome.shaper import model
 
-errCode = 0
-
-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:
+#------------------------------------------------------------------------
+def validateSession(logFile):
+  """
+  Iterate through and write all feature names of each part in the active study
+  to the logFile.
+  """
+  errCode = 0
   try:
-    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")
+    logFile.write("getting SHAPER session\n")
     session = ModelAPI.ModelAPI_Session.get()
-    aFactory = session.validators()
+    # aFactory = session.validators()
+    logFile.write("getting PartSet\n")
     aPartSet = session.moduleDocument()
     numParts = aPartSet.size("Parts")
     for partIdx in range(numParts+1):
@@ -40,28 +26,121 @@ with open(os.path.join(outFolder, base+"_valid.log"), "w") as f:
         continue
       aPart.setActive(True)
       if partIdx == 0:
-        f.write("---PartSet:------------------\n")
+        logFile.write("---PartSet:------------------\n")
       else:
-        f.write(f"---Part_{partIdx}:------------------\n")
-
-      ## Simulate an exception during execution:
-      ##raise Exception("study failed to validate")
-      ## Cause an exception
-      #x = 1/0
+        logFile.write(f"---Part_{partIdx}:------------------\n")
 
       for aFeat in aPart.allFeatures():
         if aFeat.isInHistory():
-          f.write("  * {} --> [{}]\n".format(aFeat.getKind(), aFeat.data().name()))
+          logFile.write("  * {} --> [{}]\n".format(aFeat.getKind(), aFeat.data().name()))
+        else:
+          logFile.write("    - {}\n".format(aFeat.data().name()))
+  except:
+    errCode = 128
+
+  return errCode
+
+
+#------------------------------------------------------------------------
+def checkHDF(logFile, hdfFile):
+  """
+  Open and validate a HDF document
+  """
+  errCode = 0
+  try:
+    logFile.write("-----CHECKING HDF DOCUMENT-------\n")
+    salome.standalone()
+    logFile.write("initializing SALOME\n")
+    salome.salome_init(hdfFile, embedded=True, forced=True)
+    logFile.write("StudyName = {}\n".format(salome.myStudyName))
+
+    logFile.write("getting KERNEL session\n")
+    session = salome.naming_service.Resolve('/Kernel/Session')
+    logFile.write("connecting to study\n")
+    session.emitMessage("connect_to_study")
+
+    logFile.write("activating SHAPER module\n")
+    sg = SalomePyQt.SalomePyQt()
+    sg.activateModule("Shaper")
+
+    errCode = validateSession(logFile)
+  except:
+    errCode = 129
+
+  return errCode
+
+
+#------------------------------------------------------------------------
+def checkPyScript(logFile, pyFile):
+  """
+  Load and validate a Python script
+  """
+  errCode = 0
+  try:
+    logFile.write("-----CHECKING PYTHON SCRIPT-------\n")
+    logFile.write("executing dumped script\n")
+    exec(compile(open(pyFile, 'rb').read(), pyFile, 'exec'))
+
+    errCode = validateSession(logFile)
+  except:
+    errCode = 130
+
+  return errCode
+
+
+#------------------------------------------------------------------------
+def validateBackup(fullName):
+  """
+  Open the backed up file and validate its content.
+  This test script will be called once for the stored HDF file
+  end once for the dumped Python script.
+  """
+  errCode = 0
+  try:
+    outFolder,filename = os.path.split(fullName)
+    base,ext = os.path.splitext(filename)
+    ext = ext[1:].lower() # remove the dot from the extension
+
+    # Create a log file in the backup folder starting with the same base name
+    logFile = os.path.join(outFolder, base+"_valid.log")
+    if os.path.exists(logFile):
+        append_write = 'a' # append if already exists
+    else:
+        append_write = 'w' # make a new file if not
+
+    with open(logFile, append_write) as f:
+      try:
+        f.write(f"fullName = {fullName}\n")
+        if ext == "hdf":
+          errCode = checkHDF(f, fullName)
+        elif ext == "py":
+          errCode = checkPyScript(f, fullName)
         else:
-          f.write("    - {}\n".format(aFeat.data().name()))
-  except Exception as ex:
-    f.write("Exception caught: {}\n".format(ex))
-    errCode = 88
-
-  f.write("errCode = {}\n".format(errCode))
-  if errCode == 0:
-    f.write("HDF Test - PASSED\n")
+          f.write(f"-----UNSUPPORTED FILE TYPE [{ext}]-------\n")
+          errCode = 131
+        f.write(f"errCode = {errCode}\n")
+        if errCode == 0:
+          f.write("{} Test - PASSED\n\n".format(ext.upper()))
+        else:
+          f.write("{} Test - FAILED\n\n".format(ext.upper()))
+      except:
+        errCode = 132
+        f.write("Exception caught\n")
+  except:
+    errCode = 133
+
+  return errCode
+
+
+#------------------------------------------------------------------------
+errCode = 0
+try:
+  if (len(sys.argv) != 2):
+    errCode = 134
   else:
-    f.write("HDF Test - FAILED\n")
+    fullName = sys.argv[1]
+    errCode = validateBackup(fullName)
+except:
+  errCode = 135
 
 exit(errCode)
index cca76f434e97b52cd64084f91fad2a776ba29a4f..0566798661bbff2d05cb5d6fa98bb1c52ed3b5d4 100644 (file)
@@ -80,6 +80,7 @@
 #include <QToolBar>
 #include <QFileInfo>
 #include <QDir>
+#include <QMessageBox>
 
 #include <chrono>
 #include <future>
@@ -175,7 +176,7 @@ SHAPERGUI::SHAPERGUI()
   myBackupTimer = new QTimer( this );
   myBackupTimer->setSingleShot( true );
   connect( myBackupTimer, SIGNAL( timeout() ), this, SLOT( onBackupDoc() ) );
-  connect( this, SIGNAL( backupDone(QString,bool) ), this, SLOT( onBackupDone(QString,bool) ));
+  connect( this, SIGNAL( backupDone(QString,int) ), this, SLOT( onBackupDone(QString,int) ));
 
   // It will be called in XGUI_Workshop::startApplication
   // ModuleBase_Preferences::loadCustomProps();
@@ -411,12 +412,11 @@ bool SHAPERGUI::activateModule(SUIT_Study* theStudy)
   connect(getApp()->action(LightApp_Application::FileSaveAsId), SIGNAL(triggered(bool)),
           this, SLOT(onSaveAsDocByShaper()));
 
-  //MBS:
   SUIT_ResourceMgr* aResMgr = application()->resourceMgr();
   if ( aResMgr && application()->activeStudy() ) {
-    bool useBackup = aResMgr->booleanValue( "General", "use_auto_backup", true );
+    bool useBackup = aResMgr->booleanValue( ModuleBase_Preferences::GENERAL_SECTION, "use_auto_backup", true );
     if (useBackup) {
-      int backupInterval = aResMgr->integerValue( "General", "backup_interval", 1 );
+      int backupInterval = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "backup_interval", 5 );
       if ( backupInterval > 0 ){
 #ifdef DBG_BACKUP_INTERVAL
         backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging
@@ -784,21 +784,22 @@ public:
 };
 
 //******************************************************
-bool SHAPERGUI::backupDoc()
+int 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;
+    return 32;
   }
 
+  int aResult = 0;
   bool isOk = false;
   SUIT_Study* study = application()->activeStudy();
   if ( !study ) {
     myBackupError = tr("There is no active study");
-    return false;
+    return 33;
   }
 
   LockBackupState lockBackup(myWorkshop);
@@ -810,14 +811,13 @@ bool SHAPERGUI::backupDoc()
     SHOW(aName);
     if ( aName.isEmpty() ) {
       myBackupError = tr("Study name is empty");
-      return false;
+      return 34;
     }
 
     const QChar aSep = QDir::separator();
-    //MBS:
     SUIT_ResourceMgr* aResMgr = application()->resourceMgr();
     if ( aResMgr && application()->activeStudy() ) {
-      aFolder = aResMgr->path( "General", "backup_folder", "" );
+      aFolder = aResMgr->path( ModuleBase_Preferences::GENERAL_SECTION, "backup_folder", "" );
     }
     if (aFolder.isEmpty()) {
 #ifdef HAVE_SALOME
@@ -838,7 +838,7 @@ bool SHAPERGUI::backupDoc()
       aDir.mkdir(aFolder);
       if (!aDir.exists()) {
         myBackupError = tr("Cannot create backup folder");
-        return false;
+        return 35;
       }
     }
 
@@ -854,21 +854,21 @@ bool SHAPERGUI::backupDoc()
     isOk = study->saveDocumentAs( aFullName, true );
     if (!isOk){
       myBackupError = tr("Cannot backup study document");
-      return false;
+      return 36;
     }
 
     // Now, dump the python script
     LightApp_Study *lightStudy = dynamic_cast<LightApp_Study*>(study);
     if (!lightStudy) {
       myBackupError = tr("Study is not dumpable");
-      return false;
+      return 37;
     }
 
     aFullName = aFolder + aSep + aName + QString(".py");
     isOk = lightStudy->dump(aFullName, true, false, false);
     if (!isOk){
       myBackupError = tr("Cannot backup python script");
-      return false;
+      return 38;
     }
 
     // Finally start another salome process and reload the saved document & script for verification
@@ -881,75 +881,111 @@ bool SHAPERGUI::backupDoc()
         << QString("salome")
         << testBackup;
     QString testScript = dirs.join( QDir::separator() );
-    int aResult = checkBackup.run(testScript);
-    isOk = (aResult == 0);
+    aResult = checkBackup.run(testScript);
   }
   catch (std::exception &ex)
   {
     myBackupError = tr("std::exception caught");
-    isOk = false;
+    aResult = 39;
   }
   catch (...)
   {
     myBackupError = tr("unknown exception caught");
-    isOk = false;
+    aResult = 40;
   }
 
 
   MSGEL("...emit backupDone signal");
   SHOW(aFolder);
-  SHOW(isOk);
-  emit backupDone(aFolder, isOk);
-  return isOk;
+  SHOW(aResult);
+  emit backupDone(aFolder, aResult);
+  return aResult;
 }
 
 //******************************************************
-void SHAPERGUI::onBackupDone(QString aFolder, bool aResult)
+void SHAPERGUI::onBackupDone(QString aFolder, int aResult)
 {
   DBG_FUN();
   ARG(aFolder);
   ARG(aResult);
 
-  bool isOk = myBackupResult.get();
-  SHOW(isOk);
-  putInfo( isOk ? tr("Backup done in folder: %1").arg(aFolder) : tr("Failed to backup active study!"), 5000 );
+  int aErrCode = myBackupResult.get();
+  SHOW(aErrCode);
+  bool isOk = (aResult == 0);
+  if (isOk)
+  {
+    putInfo(tr("Backup done in folder: %1").arg(aFolder), 5000 );
+  }
+  else
+  {
+    QString aMsg = tr("Failed to backup active study!\nError Code: %1").arg(aResult);
+    QMessageBox::warning(application()->desktop(), tr("Automatic Backup"), aMsg);
+  }
 
   int aBackupStorage{-1};
   SUIT_ResourceMgr* aResMgr = application()->resourceMgr();
-  if ( aResMgr ) {
-    aBackupStorage = aResMgr->integerValue( "General", "auto_backup_storage", -1);
+  if ( aResMgr )
+  {
+    aBackupStorage = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "auto_backup_storage", -1);
+  }
+  else
+  {
+    MSGEL("ERR: could not get resource manager");
   }
-  if (aBackupStorage == 0/*StoreLastBackupOnly*/) {
+  if (aBackupStorage == 0/*StoreLastBackupOnly*/)
+  {
+    MSGEL("===> only keep the latest successful backup: create " << aFolder.toStdString());
     // Only keep the latest successful backup => delete the previous one, if it exists
-    if (isOk && !myLastBackupFolder.isEmpty()) {
+    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:
       //  * <FileName>.hdf       - the study itself
-      //  * <FileName>.log       - the output from the additional SALOME instance
       //  * <FileName>.py        - the python dump
       //  * <FileName>_test.log  - the output of the "test_backup.py" script
       //  * <FileName>_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() <= 5) {
+      // I am afraid of accidently removing an entire folder tree, therefore check
+      // if "dir" contains really the latest backups and nothing more
+      if (!files.isEmpty() && files.length() <= 4)
+      {
         QString baseName = files.constFirst();
         baseName = baseName.left(baseName.lastIndexOf('.'));
-        if (!baseName.isEmpty() && files.filter(baseName).length() == files.length()) {
+        if (!baseName.isEmpty() && files.filter(baseName).length() == files.length())
+        {
           MSGEL("........removing old backup folder");
-          dir.removeRecursively();
+          const bool success = dir.removeRecursively();
+          if (!success)
+          {
+            QString aMsg = tr("The previous backup folder could not be removed!");
+            QMessageBox::warning(application()->desktop(), tr("Automatic Backup"), aMsg);
+          }
         }
       }
+      else
+      {
+        QString aMsg = tr("The previous backup folder was not deleted,\nas there are more files in it than it is expected!");
+        QMessageBox::warning(application()->desktop(), tr("Automatic Backup"), aMsg);
+      }
     }
     myLastBackupFolder = aFolder;
   }
+  else
+  {
+    MSGEL("===> keep entire backup history: adding " << aFolder.toStdString());
+  }
 
   // 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 ){
+  if ( aResMgr && application()->activeStudy() )
+  {
+    bool useBackup = aResMgr->booleanValue( ModuleBase_Preferences::GENERAL_SECTION, "use_auto_backup", true );
+    if (useBackup)
+    {
+      int backupInterval = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "backup_interval", 5 );
+      if ( backupInterval > 0 )
+      {
 #ifdef DBG_BACKUP_INTERVAL
         backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging
 #endif
@@ -1414,7 +1450,7 @@ void SHAPERGUI::preferencesChanged(const QString& theSection, const QString& the
       bool useBackup = ModuleBase_Preferences::resourceMgr()->booleanValue(
         ModuleBase_Preferences::GENERAL_SECTION, "use_auto_backup", true);
       if (useBackup) {
-        int backupInterval = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "backup_interval", 1 );
+        int backupInterval = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "backup_interval", 5 );
         if ( backupInterval > 0 ){
 #ifdef DBG_BACKUP_INTERVAL
           backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging
@@ -1752,7 +1788,9 @@ void SHAPERGUI::publishToStudy()
     // update SHAPERSTUDY objects in OCC and VTK viewers
     QStringList aVMList;
     aVMList << "OCCViewer" << "VTKViewer";
+    MSGEL("publishToStudy() : updatePresentations(SHAPERSTUDY) start.");
     getApp()->updatePresentations("SHAPERSTUDY", aVMList);
+    MSGEL("publishToStudy() : updatePresentations(SHAPERSTUDY) end.");
   }
 }
 
index e3d6e76bdeaf24ac862320a95da27e51305b9d2e..199ef40f524020b19cb2defcdddae5a1a4354dfe 100644 (file)
@@ -179,7 +179,7 @@ Q_OBJECT
   virtual void updateInfoPanel();
 
  signals:
-  void backupDone(QString aName, bool aResult);
+  void backupDone(QString aName, int aResult);
 
  public slots:
   /// \brief The method is redefined to connect to the study viewer before the data
@@ -220,7 +220,7 @@ Q_OBJECT
   void onBackupDoc();
 
   /// Document has been backed-up
-  void onBackupDone(QString aName, bool aResult);
+  void onBackupDone(QString aName, int aResult);
 
   /// Obtains the current application and updates its actions
   void onUpdateCommandStatus();
@@ -239,7 +239,7 @@ Q_OBJECT
   virtual bool abortAllOperations();
 
   /// The automatic backup thread function
-  bool backupDoc();
+  int backupDoc();
 
 private slots:
   void onWhatIs(bool isToggled);
@@ -324,10 +324,10 @@ private slots:
   double myAxisArrowRate;
 
   /// Automatic backup
-  QTimer*           myBackupTimer;      // The timer which triggers the automatic backup
-  std::future<bool> 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
+  QTimer*          myBackupTimer;      // The timer which triggers the automatic backup
+  std::future<int> 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
index 728adab996e87472265e95c32d20e2d474f3ed5b..85fb2a6daeb4e83f90717e03a8f82234b16c500a 100644 (file)
@@ -52,7 +52,7 @@ int SHAPERGUI_CheckBackup::run(const QString &theTestScript)
 
   int aResult = 0;
   if (myProcess)
-    return 1;
+    return 64;
 
   QString aProgName = std::getenv("PYTHONBIN");
   if (aProgName.isEmpty())
@@ -73,7 +73,7 @@ int SHAPERGUI_CheckBackup::run(const QString &theTestScript)
 
   myProcess = new QProcess(this);
   if (!myProcess)
-    return 2;
+    return 65;
 
   // connect(myProcess, SIGNAL(started()), this, SLOT(procStarted()));
   // connect(myProcess, SIGNAL(error(QProcess::ProcessError)), this,
@@ -86,50 +86,96 @@ int SHAPERGUI_CheckBackup::run(const QString &theTestScript)
   //   * opens the previously backed up HDF study
   SHOW(aProgName);
   SHOW(args);
-  //myProcess->setStandardOutputFile((QStringList() << myFolder << myBaseName+".log").join(aSep));
   myProcess->start(aProgName, args);
   myProcess->waitForFinished(300000);
-  int exitStat = myProcess->exitStatus();
+  QProcess::ExitStatus exitStat = myProcess->exitStatus(); // 0=NormalExit, 1=CrashExit
   SHOW(exitStat);
   int exitCode = myProcess->exitCode();
   SHOW(exitCode);
-  if (exitStat == 0/*NormalExit*/ && exitCode != 0) 
+  if (exitStat == QProcess::NormalExit && exitCode != 0) 
     aResult = exitCode;
-  else if (exitStat == -1/*CrashExit*/)
-    aResult = 99;
+  else if (exitStat == QProcess::CrashExit)
+    aResult = 66;
   myProcess->deleteLater();
 
   // Check the output of the log file
-  if (exitStat == 0 && exitCode == 0)
+  if (aResult == 0)
   {
     std::ifstream log((QStringList() << myFolder << myBaseName+"_valid.log").join(aSep).toStdString().c_str());
     if (log)
     {
-      aResult = 76;
+      uint8_t testFlag = 0;
       std::string line;
       while (std::getline(log, line))
       {
         if (line.find("HDF Test - ") == 0)
         {
+          testFlag |= 0x1; // 0x01 = HDF Test performed
           std::string strResult = line.substr(11);
           if (strResult.find("PASSED") == 0)
           {
             MSGEL("HDF Test --> PASSED");
-            aResult = 0;
+            if (testFlag == 0x03)
+              break;
           }
           else if (strResult.find("FAILED") == 0)
           {
             MSGEL("HDF Test --> FAILED");
-            aResult = 75;
+            aResult = 67;
+            break;
           }
-          break;
+          else
+          {
+            MSGEL("HDF Test --> unknown result");
+            aResult = 68;
+            break;
+          }
+        }
+        else if (line.find("PY Test - ") == 0)
+        {
+          testFlag |= 0x2; // 0x02 = PY Test performed
+          std::string strResult = line.substr(10);
+          if (strResult.find("PASSED") == 0)
+          {
+            MSGEL("PY Test --> PASSED");
+            if (testFlag == 0x03)
+              break;
+          }
+          else if (strResult.find("FAILED") == 0)
+          {
+            MSGEL("PY Test --> FAILED");
+            aResult = 69;
+            break;
+          }
+          else
+          {
+            MSGEL("PY Test --> unknown result");
+            aResult = 70;
+            break;
+          }
+        }
+      }
+      if (aResult == 0 && testFlag != 0x03)
+      {
+        // Not all tests were performed or they were interrupted
+        switch (testFlag)
+        {
+          case 0x00:  MSGEL("None of the tests were performed until the end.");
+                      aResult = 71;
+                      break;
+          case 0x01:  MSGEL("The PY Test was not performed until the end.");
+                      aResult = 72;
+                      break;
+          case 0x02:  MSGEL("The HDF Test was not performed until the end.");
+                      aResult = 73;
+                      break;
         }
       }
     }
     else
     {
       std::cout << "WARNING: cannot open log file from check_validity.py script" << std::endl;
-      aResult = 77; // log file not found
+      aResult = 74; // log file not found
     }
   }
 
index d671432f0067ff41ac7de4d7b1f4e55a505b931b..f8cc2918c6d72581f5bcf61c591d3c5d72b1c008 100755 (executable)
 # 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.")
+#------------------------------------------------------------------------
+def checkFileInSubprocess(logFile, title, args):
+  errCode = 0
+  try:
+    logFile.write(f"starting {title} process:\n")
+    logFile.write("  cmd_line = '{}'\n".format(" ".join(args)))
+    proc = subprocess.Popen(args)
+    try:
+      logFile.write(f"  start communication with {title}\n")
+      proc.communicate(timeout = 500)
+      logFile.write(f"{title} terminated\n")
+      errCode = proc.returncode
+      logFile.write(f"{title} returned: {errCode}\n")
+    except subprocess.TimeoutExpired:
+      errCode = 96
+      logFile.write(f"{title} timed out\n")
+    except Exception as ex:
+      errCode = 97
+      logFile.write(f"Exception caught: {ex}\n")
+    except:
+      errCode = 98
+      logFile.write("Unknown Exception caught\n")
 
-backupFolder = sys.argv[1]
-baseName = sys.argv[2]
-checkScript = sys.argv[3]
+    logFile.write(f"errCode = {errCode}\n")
+  except:
+    errCode = 99
 
-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])
+  return errCode
+
+
+#------------------------------------------------------------------------
+def checkBackups(backupFolder, baseName, checkScript):
+  errCode = 0
   try:
-    f.write("start communication with SALOME\n")
-    proc.communicate(timeout = testTimeout)
-    f.write("SALOME terminated\n")
-    errCode = proc.returncode
-    f.write("SALOME returned: {}\n".format(errCode))
-  except subprocess.TimeoutExpired:
-    errCode = 99
-    f.write("SALOME timed out\n")
-  except Exception as ex:
-    errCode = 33
-    f.write("Exception caught: {}\n".format(ex))
-
-  f.write("errCode = {}\n".format(errCode))
-  if errCode == 0:
-    f.write("Backup Test - PASSED\n")
+    # Create a log file in the backup folder starting with the same base name
+    with open(os.path.join(backupFolder, baseName+"_test.log"), "w") as logFile:
+      logFile.write("test_backup script started\n")
+      logFile.write(f"  backupFolder = {backupFolder}\n")
+      logFile.write(f"  baseName     = {baseName}\n")
+      logFile.write(f"  checkScript  = {checkScript}\n")
+      hdfFile = os.path.join(backupFolder, baseName+".hdf")
+      logFile.write(f"  hdfFile      = {hdfFile}\n")
+
+      args = ["runSalome.py", "--modules", "SHAPER,SHAPERSTUDY", "--batch", "--splash", "0", checkScript, "args:" + hdfFile]
+      errCode = checkFileInSubprocess(logFile, "SALOME", args)
+      logFile.write(f"errCode = {errCode}\n")
+
+      if errCode == 0:
+        pyFile = os.path.join(backupFolder, baseName+".py")
+        logFile.write(f"  pyfile       = {pyFile}\n")
+
+        args = ["python", checkScript, pyFile]
+        errCode = checkFileInSubprocess(logFile, "PYTHON", args)
+        logFile.write(f"errCode = {errCode}\n")
+
+  except:
+    errCode = 100
+
+  return errCode
+
+
+#------------------------------------------------------------------------
+errCode = 0
+try:
+  if (len(sys.argv) != 4):
+    errCode = 101
   else:
-    f.write("Backup Test - FAILED\n")
+    backupFolder = sys.argv[1]
+    baseName = sys.argv[2]
+    checkScript = sys.argv[3]
+    errCode = checkBackups(backupFolder, baseName, checkScript)
+except:
+  errCode = 102
 
-exit(errCode)
\ No newline at end of file
+exit(errCode)