From 3dcd7e18306f57fdd49064748fcfee8175f6bb88 Mon Sep 17 00:00:00 2001 From: Michael Wetter Date: Wed, 9 Nov 2022 12:31:08 -0800 Subject: [PATCH] Added option to specify timeout For #495 --- buildingspy/development/dymola_run.template | 83 ++++ .../development/dymola_run_all.template | 11 + buildingspy/development/regressiontest.py | 403 ++++++++++-------- buildingspy/templates/regressiontest_conf.py | 9 +- 4 files changed, 326 insertions(+), 180 deletions(-) create mode 100644 buildingspy/development/dymola_run.template create mode 100644 buildingspy/development/dymola_run_all.template diff --git a/buildingspy/development/dymola_run.template b/buildingspy/development/dymola_run.template new file mode 100644 index 00000000..4f97000c --- /dev/null +++ b/buildingspy/development/dymola_run.template @@ -0,0 +1,83 @@ +def _add_exception(return_dict, e, cmd): + import subprocess + + return_dict['success'] = False + +# if isinstance(e, subprocess.CalledProcessError): +# # Check if simulation terminated, and if so, get the error +# return_dict['stdout'] = e.output.decode("utf-8") +# output = return_dict['stdout'] +# for line in output.split('\n'): +# if 'terminated' in line: +# # Found terminated string. Cut everything after the '|' character that OpenModelica writes. +# idx=line.rfind('|') +# msg=line[idx+1:].strip() +# # The solver terminated. Add this information to a custom exception message. +# return_dict['exception'] = f"'{' '.join(cmd)}' caused '{msg}'." +# pass + + if not 'exception' in return_dict: + # Did not find 'terminated' in message, handle exception as usual + return_dict['exception'] = '{}: {}'.format(type(e).__name__, e) + + +def _run_process(return_dict, cmd, worDir, timeout): + import subprocess + + output = subprocess.check_output( + cmd, + cwd = worDir, + timeout=timeout, + stderr=subprocess.STDOUT, + shell=False) + + return_dict['success'] = True + if 'stdout' in return_dict: + return_dict['stdout'] += output.decode("utf-8") + else: + return_dict['stdout'] = output.decode("utf-8") + return + +def _simulate(model, timeout): + import os + import subprocess + + worDir = "{{ working_directory }}" + return_dict = {} + + try: + cmd = {{ cmd }} + return_dict['cmd'] = ' '.join(cmd) + output = _run_process(return_dict, cmd, worDir, timeout) + + except Exception as e: + _add_exception(return_dict, e, cmd) + return return_dict + +def run(): + import os + import json + import traceback + import sys + + timeout = {{ time_out }} + model = "{{ model }}" + result = {"model": model, + "working_directory": "{{ working_directory }}", + "simulation": {"success": False}} + + # Log file + log_file = "{}_buildingspy.json".format(model.replace(".", "_")) + try: + os.remove(log_file) + except OSError: + pass + + # Simulate model + result["simulation"] = _simulate(model, timeout) + + with open(log_file, "w") as log: + log.write("{}\n".format(json.dumps(result, indent=4, sort_keys=False)) ) + +if __name__=="__main__": + run() diff --git a/buildingspy/development/dymola_run_all.template b/buildingspy/development/dymola_run_all.template new file mode 100644 index 00000000..78503680 --- /dev/null +++ b/buildingspy/development/dymola_run_all.template @@ -0,0 +1,11 @@ +{% for model in models_underscore %} +def run_{{ model }}(): + import {{ model }} as m + m.run() +{% endfor %} + + +if __name__=="__main__": + {% for model in models_underscore %} + run_{{ model }}() + {% endfor %} diff --git a/buildingspy/development/regressiontest.py b/buildingspy/development/regressiontest.py index 2efa0e22..b5b5d2f8 100644 --- a/buildingspy/development/regressiontest.py +++ b/buildingspy/development/regressiontest.py @@ -1199,7 +1199,10 @@ def _verify_model_exists(model_name): self._reporter.writeError( f"{conf_file_name} specifies {con_dat['model_name']}, but there is no model file {mo_name}.") - if self._modelica_tool != 'dymola': + if self._modelica_tool == 'dymola': + for ent in self._data: + ent['dymola']['time_out'] = 300 + else: # Non-dymola def_dic = {} def_dic[self._modelica_tool] = { 'translate': True, @@ -1287,6 +1290,7 @@ def _verify_model_exists(model_name): if self._reporter.getNumberOfErrors() > 0: raise ValueError(f"Wrong specification in {conf_file_name}.") + def _checkDataDictionary(self): """ Check if the data used to run the regression tests do not have duplicate ``*.fmu`` files and ``*.mat`` names. @@ -3078,7 +3082,7 @@ def _isPresentAndTrue(key, dic): return key in dic and dic[key] def _write_runscript_dymola(self, iPro, tra_data_pro): - """Create the runAll.mos script for the current processor iPro and for Dymola, + """Create the run_modelName.mos scripts for the current processor iPro and for Dymola, and return the number of generated regression tests. :param iPro: The number of the processor. @@ -3086,6 +3090,19 @@ def _write_runscript_dymola(self, iPro, tra_data_pro): """ import platform + for tra_data in tra_data_pro: + self._write_dymola_script(iPro, tra_data) + + + def _write_dymola_script(self, iPro, tra_data): + """Create the run_modelName.mos script for the current model and for Dymola, + and return the number of generated regression tests. + + :param iPro: The number of the processor. + :param tra_data: Data for the experiment that requires translation, for processor number iPro only. + """ + import platform + ################################################################## # Internal functions def _write_translation_stats(runFil, values): @@ -3108,17 +3125,17 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): ################################################################## # Count the number of experiments that need to be simulated or exported as an FMU. # This is needed to properly close the json brackets. - nItem = 0 +# nItem = 0 # Count how many tests need to be simulated. - nTes = len(tra_data_pro) +# nTes = len(tra_data_pro) # Number of generated unit tests - nUniTes = 0 +# nUniTes = 0 - runFil = open(os.path.join(self._temDir[iPro], self.getLibraryName( - ), "runAll.mos"), mode="w", encoding="utf-8") + statistics_log = f"{tra_data['model_name']}.statistics.log" + runFil = open(os.path.join(self._temDir[iPro], self.getLibraryName(), + f"run_{tra_data['model_name']}.mos"), mode="w", encoding="utf-8") runFil.write( - f""" -// File autogenerated for process {iPro + 1} of {self._nPro} + f"""// File autogenerated for process {iPro + 1} of {self._nPro} // File created for execution by {self._modelica_tool}. Do not edit. // Disable parallel computing as this can give slightly different results. Advanced.ParallelizeCode = false; @@ -3153,87 +3170,84 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): Advanced.TranslationInCommandLog := true; // Set flag to support string parameters, which is required for the weather // data file. -Modelica.Utilities.Files.remove(\"{self._simulator_log_file}\"); -Modelica.Utilities.Files.remove(\"{self._statistics_log}\"); +//Modelica.Utilities.Files.remove(\"{self._simulator_log_file}\"); +Modelica.Utilities.Files.remove(\"{statistics_log}\"); """) runFil.write(r""" Modelica.Utilities.Streams.print("{\"testCase\" : [", "%s"); -""" % self._statistics_log) - - for i in range(nTes): - if self._isPresentAndTrue( - 'translate', - tra_data_pro[i]['dymola']) or self._isPresentAndTrue( - 'exportFMU', - tra_data_pro[i]['dymola']): - nItem = nItem + 1 - iItem = 0 - # Write unit tests for this process - for i in range(nTes): - # Check if this mos file should be simulated - if self._isPresentAndTrue( - 'translate', - tra_data_pro[i]['dymola']) or self._isPresentAndTrue( - 'exportFMU', - tra_data_pro[i]['dymola']): - isLastItem = (iItem == nItem - 1) - mosFilNam = os.path.join(self.getLibraryName(), - "Resources", "Scripts", "Dymola", - tra_data_pro[i]['ScriptFile']) - absMosFilNam = os.path.join(self._temDir[iPro], mosFilNam) - values = { - "mosWithPath": mosFilNam.replace( - "\\", - "/"), - "checkCommand": self._getModelCheckCommand(absMosFilNam).replace( - "\\", - "/"), - "checkCommandString": self._getModelCheckCommand(absMosFilNam).replace( - '\"', - r'\\\"'), - "scriptFile": tra_data_pro[i]['ScriptFile'].replace( - "\\", - "/"), - "model_name": tra_data_pro[i]['model_name'].replace( - "\\", - "/"), - "model_name_underscore": tra_data_pro[i]['model_name'].replace( - ".", - "_"), - "start_time": tra_data_pro[i]['startTime'] if 'startTime' in tra_data_pro[i] else 0, - "final_time": tra_data_pro[i]['stopTime'] if 'stopTime' in tra_data_pro[i] else 0, - "statisticsLog": self._statistics_log.replace( - "\\", - "/"), - "translationLog": os.path.join( - self._temDir[iPro], - self.getLibraryName(), - tra_data_pro[i]['model_name'] + - ".translation.log").replace( - "\\", - "/"), - "simulatorLog": self._simulator_log_file.replace( - "\\", - "/")} - if 'FMUName' in tra_data_pro[i]['dymola']: - values["FMUName"] = tra_data_pro[i]['dymola']['FMUName'] - # Delete command log, model_name.simulation.log and dslog.txt - runFil.write(f""" +""" % statistics_log) + + #if self._isPresentAndTrue( + # 'translate', + # tra_data['dymola']) or self._isPresentAndTrue( + # 'exportFMU', + # tra_data['dymola']): + #nItem = nItem + 1 + +# iItem = 0 +# Write unit tests for this process +# Check if this mos file should be simulated + if self._isPresentAndTrue( + 'translate', + tra_data['dymola']) or self._isPresentAndTrue( + 'exportFMU', + tra_data['dymola']): + isLastItem = True # (iItem == nItem - 1) + mosFilNam = os.path.join(self.getLibraryName(), + "Resources", "Scripts", "Dymola", + tra_data['ScriptFile']) + absMosFilNam = os.path.join(self._temDir[iPro], mosFilNam) + values = { + "mosWithPath": mosFilNam.replace( + "\\", + "/"), + "checkCommand": self._getModelCheckCommand(absMosFilNam).replace( + "\\", + "/"), + "checkCommandString": self._getModelCheckCommand(absMosFilNam).replace( + '\"', + r'\\\"'), + "scriptFile": tra_data['ScriptFile'].replace( + "\\", + "/"), + "model_name": tra_data['model_name'].replace( + "\\", + "/"), + "model_name_underscore": tra_data['model_name'].replace( + ".", + "_"), + "start_time": tra_data['startTime'] if 'startTime' in tra_data else 0, + "final_time": tra_data['stopTime'] if 'stopTime' in tra_data else 0, + "statisticsLog": statistics_log, + "translationLog": os.path.join( + self._temDir[iPro], + self.getLibraryName(), + tra_data['model_name'] + + ".translation.log").replace( + "\\", + "/"), + "simulatorLog": self._simulator_log_file.replace( + "\\", + "/")} + if 'FMUName' in tra_data['dymola']: + values["FMUName"] = tra_data['dymola']['FMUName'] + # Delete command log, model_name.simulation.log and dslog.txt + runFil.write(f""" Modelica.Utilities.Files.remove(\"{values["model_name"]}.translation.log\"); Modelica.Utilities.Files.remove(\"dslog.txt\"); clearlog(); """) - ######################################################################## - # Write line for model check - model_name = values["model_name"] - if model_name.startswith("Obsolete.", model_name.find(".") + 1): - # This model is in IBPSA.Obsolete, or Buildings.Obsolete etc. - values["set_non_pedantic"] = "Advanced.PedanticModelica = false;\n" - values["set_pedantic"] = "Advanced.PedanticModelica = true;\n" - else: # Set to empty string as for non-obsolete models, we don't switch to non-pedantic mode - values["set_non_pedantic"] = "" - values["set_pedantic"] = "" - template = r""" + ######################################################################## + # Write line for model check + model_name = values["model_name"] + if model_name.startswith("Obsolete.", model_name.find(".") + 1): + # This model is in IBPSA.Obsolete, or Buildings.Obsolete etc. + values["set_non_pedantic"] = "Advanced.PedanticModelica = false;\n" + values["set_pedantic"] = "Advanced.PedanticModelica = true;\n" + else: # Set to empty string as for non-obsolete models, we don't switch to non-pedantic mode + values["set_non_pedantic"] = "" + values["set_pedantic"] = "" + template = r""" {set_non_pedantic} rCheck = {checkCommand}; {set_pedantic} @@ -3244,18 +3258,17 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): Modelica.Utilities.Streams.print(" \"result\" : " + String(rCheck), "{statisticsLog}"); Modelica.Utilities.Streams.print(" }},", "{statisticsLog}"); """ - runFil.write(template.format(**values)) - ########################################################################## - # Write commands for checking translation and simulation results. - - # Only translation requested, but no simulation. - ########################################################################## - if self._isPresentAndTrue( - 'translate', - tra_data_pro[i]['dymola']) and not self._isPresentAndTrue( - 'simulate', - tra_data_pro[i]['dymola']): - template = r""" + runFil.write(template.format(**values)) + ########################################################################## + # Write commands for checking translation and simulation results. + # Only translation requested, but no simulation. + ########################################################################## + if self._isPresentAndTrue( + 'translate', + tra_data['dymola']) and not self._isPresentAndTrue( + 'simulate', + tra_data['dymola']): + template = r""" {set_non_pedantic} retVal = translateModel("{model_name}"); Modelica.Utilities.Streams.print("Translated {model_name} successfully: " + String(retVal)); @@ -3278,30 +3291,30 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): Modelica.Utilities.Streams.print("{model_name}.dslog.log was not generated.", "{model_name}.log"); end if; """ - runFil.write(template.format(**values)) - template = r""" + runFil.write(template.format(**values)) + template = r""" Modelica.Utilities.Streams.print(" \"translate\" : {{", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"command\" :\"translateModel(\\\"{model_name}\\\");\",", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"translationLog\" : \"{translationLog}\",", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"result\" : " + String(iSuc > 0), "{statisticsLog}"); """ - runFil.write(template.format(**values)) - _write_translation_stats(runFil, values) - _print_end_of_json(isLastItem, - runFil, - self._statistics_log) - # Simulation requested - if self._isPresentAndTrue('simulate', tra_data_pro[i]['dymola']): - # Remove dslog.txt, run a simulation, rename dslog.txt, and - # scan this log file for errors. - # This is needed as RunScript returns true even if the simulation failed. - # We read to dslog file line by line as very long files can lead to - # Out of memory for strings - # It could due to too large matrices, infinite recursion, or uninitialized variables. - # You can increase the size of 'Stringbuffer' in dymola/source/matrixop.h. - # The stack of functions is: - # Modelica.Utilities.Streams.readFile - template = r""" + runFil.write(template.format(**values)) + _write_translation_stats(runFil, values) + _print_end_of_json(isLastItem, + runFil, + statistics_log) + # Simulation requested + if self._isPresentAndTrue('simulate', tra_data['dymola']): + # Remove dslog.txt, run a simulation, rename dslog.txt, and + # scan this log file for errors. + # This is needed as RunScript returns true even if the simulation failed. + # We read to dslog file line by line as very long files can lead to + # Out of memory for strings + # It could due to too large matrices, infinite recursion, or uninitialized variables. + # You can increase the size of 'Stringbuffer' in dymola/source/matrixop.h. + # The stack of functions is: + # Modelica.Utilities.Streams.readFile + template = r""" {set_non_pedantic} rScript=RunScript("Resources/Scripts/Dymola/{scriptFile}"); {set_pedantic} @@ -3362,8 +3375,8 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): Modelica.Utilities.Streams.print("{model_name}.dslog.log was not generated.", "{model_name}.log"); end if; """ - runFil.write(template.format(**values)) - template = r""" + runFil.write(template.format(**values)) + template = r""" Modelica.Utilities.Streams.print(" \"simulate\" : {{", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"command\" : \"RunScript(\\\"Resources/Scripts/Dymola/{scriptFile}\\\");\",", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"translationLog\" : \"{translationLog}\",", "{statisticsLog}"); @@ -3374,15 +3387,15 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): Modelica.Utilities.Streams.print(" \"final_time\" :" + String({final_time}) + ",", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"result\" : " + String(iSuc > 0), "{statisticsLog}"); """ - runFil.write(template.format(**values)) - _write_translation_stats(runFil, values) - _print_end_of_json(isLastItem, - runFil, - self._statistics_log) - ########################################################################## - # FMU export - if tra_data_pro[i]['dymola']['exportFMU']: - template = r""" + runFil.write(template.format(**values)) + _write_translation_stats(runFil, values) + _print_end_of_json(isLastItem, + runFil, + statistics_log) + ########################################################################## + # FMU export + if tra_data['dymola']['exportFMU']: + template = r""" Modelica.Utilities.Files.removeFile("{FMUName}"); RunScript("Resources/Scripts/Dymola/{scriptFile}"); savelog("{model_name}.translation.log"); @@ -3403,27 +3416,27 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): Modelica.Utilities.Streams.print("{model_name}.dslog.log was not generated.", "{model_name}.log"); end if; """ - runFil.write(template.format(**values)) - template = r""" + runFil.write(template.format(**values)) + template = r""" Modelica.Utilities.Streams.print(" \"FMUExport\" : {{", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"command\" :\"RunScript(\\\"Resources/Scripts/Dymola/{scriptFile}\\\");\",", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"translationLog\" : \"{translationLog}\",", "{statisticsLog}"); Modelica.Utilities.Streams.print(" \"result\" : " + String(iSuc > 0), "{statisticsLog}"); """ - runFil.write(template.format(**values)) - _write_translation_stats(runFil, values) - _print_end_of_json(isLastItem, - runFil, - self._statistics_log) - - if not (tra_data_pro[i]['dymola']['exportFMU'] - or tra_data_pro[i]['dymola']['translate']): - print( - "****** {} neither requires a simulation nor an FMU export.".format(tra_data_pro[i]['ScriptFile'])) - self._removePlotCommands(absMosFilNam) - self._updateResultFile(absMosFilNam, tra_data_pro[i]['model_name']) - nUniTes = nUniTes + 1 - iItem = iItem + 1 + runFil.write(template.format(**values)) + _write_translation_stats(runFil, values) + _print_end_of_json(isLastItem, + runFil, + statistics_log) + + if not (tra_data['dymola']['exportFMU'] + or tra_data['dymola']['translate']): + print( + "****** {} neither requires a simulation nor an FMU export.".format(tra_data['ScriptFile'])) + self._removePlotCommands(absMosFilNam) + self._updateResultFile(absMosFilNam, tra_data['model_name']) +# nUniTes = nUniTes + 1 +# iItem = iItem + 1 if platform.system() == 'Windows': # Reset DDE to original settings @@ -3438,10 +3451,10 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): exit(); """) runFil.close() - return nUniTes + return def _write_runscripts(self): - """Create the runAll.mos scripts, one per processor (self._nPro). + """Create the run_modelName.mos scripts, one per model. The commands in the script depend on the tool: 'openmodelica', 'dymola' or 'optimica' """ @@ -3499,10 +3512,10 @@ def _write_runscripts(self): if self._modelica_tool == 'dymola': # Case for dymola - nUniTes = nUniTes + self._write_runscript_dymola(iPro, tra_data_pro) - else: - # Case for non-dymola - nUniTes = nUniTes + self._write_runscript_non_dymola(iPro, tra_data_pro) + self._write_runscript_dymola(iPro, tra_data_pro) + + nUniTes = nUniTes + self._write_python_runscripts(iPro, tra_data_pro) + self._write_run_all_script(iPro, tra_data_pro) if nUniTes == 0: raise RuntimeError(f"Wrong invocation, generated {nUniTes} unit tests.") @@ -3517,8 +3530,8 @@ def _get_set_of_result_variables(list_of_result_variables): s.add(ele) return s - def _write_runscript_non_dymola(self, iPro, tra_data_pro): - """ Write the OpenModelica or OPTIMICA runfile for all experiments in tra_data_pro. + def _write_run_all_script(self, iPro, tra_data_pro): + """ Write the OpenModelica or OPTIMICA top-level runfile for all experiments in tra_data_pro. :param iPro: The number of the processor. :param tra_data_pro: A list with the data for the experiments that require translation, for this processor only. @@ -3543,6 +3556,22 @@ def _write_runscript_non_dymola(self, iPro, tra_data_pro): txt += " import os;\n" fil.write(txt) + + def _write_python_runscripts(self, iPro, tra_data_pro): + """ Write the Python runfiles for all experiments in tra_data_pro. + + :param iPro: The number of the processor. + :param tra_data_pro: A list with the data for the experiments that require translation, for this processor only. + """ + import inspect + import buildingspy.development.regressiontest as r + import jinja2 + + directory = self._temDir[iPro] + + path_to_template = os.path.dirname(inspect.getfile(r)) + env = jinja2.Environment(loader=jinja2.FileSystemLoader(path_to_template)) + tem_mod = env.get_template("{}_run.template".format(self._modelica_tool)) for dat in tra_data_pro: @@ -3578,7 +3607,7 @@ def _write_runscript_non_dymola(self, iPro, tra_data_pro): time_out=dat[self._modelica_tool]['time_out'], filter=filter ) - else: + elif self._modelica_tool == 'optimica': txt = tem_mod.render( model=model, ncp=dat[self._modelica_tool]['ncp'], @@ -3595,6 +3624,28 @@ def _write_runscript_non_dymola(self, iPro, tra_data_pro): lambda m: '[{}]'.format(m.group()), re.sub(' ', '', x)) for x in result_variables] ) + else: # dymola + # assemble command + cmd = list() + cmd.append(f"{self.getModelicaCommand()}") + cmd.append(f"{self.getLibraryName()}{os.path.sep}run_{model}.mos") + if not self._showGUI: + cmd.append("/nowindow") + + txt = tem_mod.render( + model=model, + working_directory=directory, + library_name=self.getLibraryName(), + #ncp=dat[self._modelica_tool]['ncp'], + #rtol=dat[self._modelica_tool]['rtol'], + #solver=dat[self._modelica_tool]['solver'], + #start_time='mod.get_default_experiment_start_time()', + #final_time='mod.get_default_experiment_stop_time()', + #simulate=dat[self._modelica_tool]['simulate'], + time_out=dat[self._modelica_tool]['time_out'], + cmd = cmd + ) + file_name = os.path.join(directory, "{}.py".format(model.replace(".", "_"))) with open(file_name, mode="w", encoding="utf-8") as fil: fil.write(txt) @@ -3775,18 +3826,14 @@ def run(self): tem_dir = [] libNam = self.getLibraryName() for di in self._temDir: - if self._modelica_tool == 'dymola': - tem_dir.append(os.path.join(di, libNam)) - else: - tem_dir.append(di) + #if self._modelica_tool == 'dymola': + # tem_dir.append(os.path.join(di, libNam)) + #else: + # tem_dir.append(di) + tem_dir.append(di) if not self._useExistingResults: - if self._modelica_tool == 'dymola': - if self._showGUI: - cmd = [self.getModelicaCommand(), "runAll.mos"] - else: - cmd = [self.getModelicaCommand(), "runAll.mos", "/nowindow"] - elif self._modelica_tool == 'openmodelica': + if self._modelica_tool == 'dymola' or self._modelica_tool == 'openmodelica': # OS X invokes python 2.7 if the command below is python, despite of having # alias python=python3 in .bashrc. # Hence, we invoke python3 for OS X. @@ -3796,8 +3843,9 @@ def run(self): cmd = ["python3", "./run.py"] else: cmd = ["python", "./run.py"] - elif self._modelica_tool != 'dymola': + else: cmd = [self.getModelicaCommand(), "run.py"] + if self._nPro > 1: po = multiprocessing.Pool(self._nPro) po.map(functools.partial(runSimulation, @@ -3831,23 +3879,24 @@ def run(self): with open(self._statistics_log, mode="w", encoding="utf-8") as logFil: stat = list() for d in self._temDir: - temLogFilNam = os.path.join(d, self.getLibraryName(), self._statistics_log) - if os.path.exists(temLogFilNam): - with open(temLogFilNam.replace('Temp\tmp', 'Temp\\tmp'), mode="r", encoding="utf-8-sig") as temSta: - try: - jsonLog = json.load(temSta) - cas = jsonLog["testCase"] - # Iterate over all test cases of this output file - for ele in cas: - stat.append(ele) - except ValueError as e: + for temLogFilNam in glob.glob( + os.path.join(d, self.getLibraryName(), '*.statistics.log')): + if os.path.exists(temLogFilNam): + with open(temLogFilNam.replace('Temp\tmp', 'Temp\\tmp'), mode="r", encoding="utf-8-sig") as temSta: + try: + jsonLog = json.load(temSta) + cas = jsonLog["testCase"] + # Iterate over all test cases of this output file + for ele in cas: + stat.append(ele) + except ValueError as e: + self._reporter.writeError( + "Decoding '%s' failed: %s" % (temLogFilNam, e)) + raise + else: self._reporter.writeError( - "Decoding '%s' failed: %s" % (temLogFilNam, e)) - raise - else: - self._reporter.writeError( - "Log file '" + temLogFilNam + "' does not exist.\n") - retVal = 1 + "Log file '" + temLogFilNam + "' does not exist.\n") + retVal = 1 # Dump an array of testCase objects # dump to a string first using json.dumps instead of json.dump json_string = json.dumps({"testCase": stat}, diff --git a/buildingspy/templates/regressiontest_conf.py b/buildingspy/templates/regressiontest_conf.py index 321c3388..2d0a9da8 100644 --- a/buildingspy/templates/regressiontest_conf.py +++ b/buildingspy/templates/regressiontest_conf.py @@ -5,7 +5,8 @@ "schema": { "translate": {"type": "boolean"}, "simulate": {"type": "boolean"}, - "comment": {"type": "string"} + "comment": {"type": "string"}, + "time_out": {"type": "number"} } }, "optimica": { @@ -16,7 +17,8 @@ "comment": {"type": "string"}, "solver": {"type": "string"}, "rtol": {"type": "number"}, - "ncp": {"type": "integer", "min": 500} + "ncp": {"type": "integer", "min": 500}, + "time_out": {"type": "number"} } }, "openmodelica": { @@ -27,7 +29,8 @@ "comment": {"type": "string"}, "solver": {"type": "string"}, "rtol": {"type": "number"}, - "ncp": {"type": "integer", "min": 500} + "ncp": {"type": "integer", "min": 500}, + "time_out": {"type": "number"} } } }