From f3dbc9a3fbdbd6f9ccd7711d3e2deda2fdaa433a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 10 May 2023 15:13:14 +0200 Subject: [PATCH 001/549] Remove all CMake related technology This is the first commit moving towards the future scenario where a bash handler will replace the CMake technology. As Git history retains older code, there is no problem to radically remove whatever is going to be replaced sooner or later. This should give a cleaner state from which to start. --- CMakeLists.txt | 1168 --------------------------------- LICENSE | 41 +- README.md | 183 ------ cmake/FindPythonModules.cmake | 20 - 4 files changed, 1 insertion(+), 1411 deletions(-) delete mode 100644 CMakeLists.txt delete mode 100644 cmake/FindPythonModules.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 1b9cc0e..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,1168 +0,0 @@ -# The project name -project(annas-hybrid NONE C) - -cmake_minimum_required(VERSION 3.5.1) - -## Tell cmake where to find the modules -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") -include(FindPythonModules) - -find_package(PythonInterp 3.6 REQUIRED) -find_package(PythonLibs 3.6) - -find_python_module(numpy VERSION 1.8.2 REQUIRED) -find_python_module(matplotlib VERSION 1.3.1 REQUIRED) -find_python_module(argparse VERSION 1.1 REQUIRED) - -# Find and copy SMASH -find_program(SMASH smash PATHS ${SMASH_PATH} NO_DEFAULT_PATH) -if(NOT SMASH) - message(FATAL_ERROR - "SMASH not found. Please specify a path to the SMASH build directory " - "by passing '-DSMASH_PATH=...' to cmake.") -else() - message(STATUS "Found SMASH: ${SMASH}") - # copy executable - file(COPY ${SMASH} DESTINATION ${CMAKE_BINARY_DIR}) -endif() - -# Find and copy vHLLE -find_program(vHLLE hlle_visc PATHS ${VHLLE_PATH} NO_DEFAULT_PATH) -if(NOT vHLLE) - message(FATAL_ERROR - "vHLLE not found. Please specify a path to the vHLLE directory " - "by passing '-DVHLLE_PATH=...' to cmake.") -else() - message(STATUS "Found vHLLE: ${vHLLE}") - # copy executable - file(COPY ${vHLLE} DESTINATION ${CMAKE_BINARY_DIR}) -endif() - -# find parameters: equations of state -find_path(vHLLE_EoSs eos PATHS ${VHLLE_PARAMS_PATH} NO_DEFAULT_PATH) -if(NOT vHLLE_EoSs) - message(FATAL_ERROR - "Equations of state not found. Please specify a path to the vHLLE params " - "directory by passing '-DVHLLE_PARAMS_PATH=...' to cmake.") -else() - message(STATUS "Found EoSs: ${VHLLE_PARAMS_PATH}/eos") - # copy EoS files - file(COPY "${VHLLE_PARAMS_PATH}/eos" DESTINATION ${CMAKE_BINARY_DIR}) -endif() - -# Find and copy sampler -find_program(SAMPLER sampler PATHS ${SAMPLER_PATH} NO_DEFAULT_PATH) -if(NOT SAMPLER) - message(FATAL_ERROR - "Sampler not found. Please specify a path to the Sampler build directory " - "by passing '-DSAMPLER_PATH=...' to cmake.") -else() - message(STATUS "Found Sampler: ${SAMPLER}") - # copy executable - file(COPY ${SAMPLER} DESTINATION ${CMAKE_BINARY_DIR}) -endif() - -# Decide whether to automatically analyze the data by means of the -# SMASH-analysis. If so, the path to the analysis suite needs to be passed. -if(SMASH_ANALYSIS_PATH) - option(WITH_ANALYSIS "Automatically analyze output to yield y, mT, pT, v2, N, ... tables." ON) -endif(SMASH_ANALYSIS_PATH) - -if(WITH_ANALYSIS) - if(SMASH_ANALYSIS_PATH) - message(STATUS "Found SMASH-analysis: ${SMASH_ANALYSIS_PATH}") - message(STATUS "Automatic analysis of results is available.") - else() - message(WARNING "SMASH-analysis not found. Provide path to SMASH-analysis directory via - cmake .. -DSMASH_ANALYSIS_PATH=path/to/SMASH_ANALYSIS") - endif(SMASH_ANALYSIS_PATH) -endif(WITH_ANALYSIS) - - -# Decide whether to automatically analyze the data regarding flow (v2, v3). -# If so, the path to the archive needs to be passed. -if(ARCHIVE_PATH) - option(WITH_FLOW "Automatically analyze output for v2, v3." ON) -endif(ARCHIVE_PATH) - -if(WITH_FLOW) - if(ARCHIVE_PATH) - message(STATUS "Found archive: ${ARCHIVE_PATH}") - message(STATUS "Automatic flow analysis is available.") - else() - message(WARNING "archive. Provide path to the archive directory via - cmake .. -DARCHIVE_PATH=path/to/archive") - endif(ARCHIVE_PATH) -endif(WITH_FLOW) - - -#----------------------------------------------------------------------------# -# Function to set up hybrid evolution depending on system and energy -#----------------------------------------------------------------------------# - -function(run_one_energy - energy_and_system - ) - list(GET energy_and_system 0 energy) - list(GET energy_and_system 1 system) - message(STATUS "Configuring hybrid run for ${system} @ sqrt(s) = ${energy} GeV.") - set(results_folder "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}") - file(MAKE_DIRECTORY ${results_folder}) - - set(smash_IC_files "") - set(vhlle_freezeout_hypersurfaces "") - set(sampled_particle_lists "") - set(final_particle_lists "") - set(all_analysis_outputs "") - set(all_flow_outputs "") - set(all_plots "") - set(all_conservation_plots "") - - set(num_runs "100") - foreach(i RANGE 1 ${num_runs}) - file(MAKE_DIRECTORY "${results_folder}/${i}/IC") # subdir for initial conditions - file(MAKE_DIRECTORY "${results_folder}/${i}/Hydro") # subdir for hydro run - file(MAKE_DIRECTORY "${results_folder}/${i}/Sampler") # subdir for particle sampling - file(MAKE_DIRECTORY "${results_folder}/${i}/Afterburner") # subdir for afterburner - file(MAKE_DIRECTORY "${results_folder}/${i}/Spectra") # subdir for analyzed spectra and plots - - # Set variables for files - set(smash_IC_file "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/IC/SMASH_IC.dat") - set(smash_IC_oscar "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/IC/SMASH_IC.oscar") - set(smash_IC_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/smash_initial_conditions_${system}.yaml") - set(vhlle_default_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/vhlle_hydro") - set(vhlle_output_dir "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Hydro/") - set(vhlle_updated_config "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Hydro/vhlle_config") - set(vhlle_freezeout_hypersurface "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Hydro/freezeout.dat") - set(sampler_input_hypersurface "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Sampler/freezeout.dat") - set(sampler_default_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/hadron_sampler") - set(sampler_updated_config "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Sampler/sampler_config") - set(sampler_dir "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Sampler") - set(sampled_particle_list "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Sampler/particle_lists.oscar") - set(full_particle_list "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Sampler/sampling0") - set(smash_afterburner_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/smash_afterburner.yaml") - set(final_particle_list "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/Afterburner/particles_binary.bin") - - list(APPEND smash_IC_files ${smash_IC_file}) - list(APPEND vhlle_freezeout_hypersurfaces ${vhlle_freezeout_hypersurface}) - list(APPEND sampled_particle_lists ${sampled_particle_list}) - list(APPEND final_particle_lists ${final_particle_list}) - - #----------------------------------------------------------------------------# - # Run SMASH and generate initial conditions output - #----------------------------------------------------------------------------# - add_custom_command(OUTPUT "${smash_IC_file}" "${results_folder}/${i}/config.yaml" - COMMAND "${CMAKE_BINARY_DIR}/smash" - "-o" "${results_folder}/${i}/IC" - "-i" "${smash_IC_config}" - "-c" "Modi: {Collider: {Sqrtsnn: ${energy}}}" - "-f" - "-n" - ">" "${results_folder}/${i}/IC/Terminal_Output.txt" - DEPENDS - "${smash_IC_config}" - "${CMAKE_BINARY_DIR}/smash" - COMMENT "Running SMASH for Initial Conditions of ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - #----------------------------------------------------------------------------# - # Feed SMASH initial conditions into vHLLE and run hydro evolution - #----------------------------------------------------------------------------# - # create input file with correct paths for vHLLE - add_custom_command(OUTPUT "${vhlle_updated_config}" - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_vhlle_config.py" - "--vhlle_config" "${vhlle_default_config}" - "--smash_ic" "${smash_IC_file}" - "--output_file" "${vhlle_updated_config}" - "--energy" "${energy}" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_vhlle_config.py" - "${vhlle_default_config}" - COMMENT "Creating input file for vHLLE for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - - # Run hydro - add_custom_command(OUTPUT "${vhlle_freezeout_hypersurface}" - COMMAND "${CMAKE_BINARY_DIR}/hlle_visc" - "-params" "${vhlle_updated_config}" - "-ISinput" "${smash_IC_file}" - "-outputDir" "${vhlle_output_dir}" - ">" "${vhlle_output_dir}/Terminal_Output.txt" - DEPENDS - "${vhlle_updated_config}" - "${CMAKE_BINARY_DIR}/hlle_visc" - COMMENT "Running vHLLE for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - #----------------------------------------------------------------------------# - # Run Cooper-Frye sampler for particlization of freezeout surface - #----------------------------------------------------------------------------# - # create input file with correct paths for sampler - set(N_events_afterburner "1000") - add_custom_command(OUTPUT "${sampler_updated_config}" - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_sampler_config.py" - "--sampler_config" "${sampler_default_config}" - "--vhlle_config" "${vhlle_updated_config}" - "--output_file" "${sampler_updated_config}" - "--Nevents" "${N_events_afterburner}" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_sampler_config.py" - "${sampler_default_config}" - COMMENT "Creating input file for sampler for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - # Run sampler - add_custom_command(OUTPUT ${sampled_particle_list} - COMMAND "${CMAKE_BINARY_DIR}/sampler" "events" "1" "${sampler_updated_config}" - ">" "${results_folder}/${i}/Sampler/Terminal_Output.txt" - DEPENDS - "${CMAKE_BINARY_DIR}/sampler" - "${sampler_updated_config}" - COMMENT "Sampling particles from freezeout hypersurface (${i}/${num_runs})." - ) - - #----------------------------------------------------------------------------# - # Run SMASH as afterburner - #----------------------------------------------------------------------------# - # Add spectators to particle list and rename it to be in accordance - # with SMASH list modus input format - add_custom_command(OUTPUT ${full_particle_list} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/add_spectators.py" - "--sampled_particle_list" "${sampler_dir}/particle_lists.oscar" - "--initial_particle_list" "${smash_IC_oscar}" - "--output_file" "${full_particle_list}" - "--smash_config" "${results_folder}/${i}/IC/config.yaml" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/add_spectators.py" - COMMENT "Adding spectators to sampled particle list for afterburner (${i}/${num_runs})." - ) - - # Run afterburner evolution - set(setMonash FALSE) - if (energy GREATER_EQUAL 200) - set(setMonash TRUE) - endif() - - add_custom_command(OUTPUT "${final_particle_list}" "${results_folder}/${i}/Afterburner/config.yaml" - COMMAND "${CMAKE_BINARY_DIR}/smash" - "-i" "${smash_afterburner_config}" - "-c" "Modi: { List: { File_Directory: ${sampler_dir}} }" - "-c" "General: { Nevents: ${N_events_afterburner} }" - "-c" "Collision_Term: { String_Parameters: {Use_Monash_Tune: ${setMonash} } }" - "-o" "${results_folder}/${i}/Afterburner" - "-f" - "-n" - ">" "/dev/null" - DEPENDS - "${smash_afterburner_config}" - "${CMAKE_BINARY_DIR}/smash" - "${full_particle_list}" - COMMENT "Running SMASH afterburner for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - #----------------------------------------------------------------------------# - # Analyse data - #----------------------------------------------------------------------------# - # Define filenames for particle spectra and create list of those - set(spectra_fnames "${results_folder}/${i}/Spectra/yspectra.txt" ; - "${results_folder}/${i}/Spectra/mtspectra.txt" ; - "${results_folder}/${i}/Spectra/ptspectra.txt" ; - "${results_folder}/${i}/Spectra/v2spectra.txt" ; - "${results_folder}/${i}/Spectra/meanmt0_midrapidity.txt" ; - "${results_folder}/${i}/Spectra/meanpt_midrapidity.txt" ; - "${results_folder}/${i}/Spectra/midrapidity_yield.txt" ; - "${results_folder}/${i}/Spectra/total_multiplicity.txt") - set(analysis_outputs "") - foreach(j ${spectra_fnames}) - list(APPEND analysis_outputs "${j}") - endforeach(j) - - list(APPEND all_analysis_outputs ${analysis_outputs}) - - # Perform analysis - add_custom_command(OUTPUT ${analysis_outputs} - COMMAND "python3" "${SMASH_ANALYSIS_PATH}/test/energy_scan/mult_and_spectra.py" - "--output_files" ${analysis_outputs} - "--input_files" "${final_particle_list}" - COMMENT "Analyzing spectra for ${system} @ ${energy} GeV (${i}/${num_runs})." - DEPENDS "${SMASH_ANALYSIS_PATH}/test/energy_scan/mult_and_spectra.py" - ) - - #----------------------------------------------------------------------------# - # Plot spectra - #----------------------------------------------------------------------------# - # Define plot names - set(plot_names "${results_folder}/${i}/Spectra/yspectra.pdf" ; - "${results_folder}/${i}/Spectra/mtspectra.pdf" ; - "${results_folder}/${i}/Spectra/ptspectra.pdf" ; - "${results_folder}/${i}/Spectra/v2spectra.pdf" ; - "${results_folder}/${i}/Spectra/meanmt0_midrapidity.pdf" ; - "${results_folder}/${i}/Spectra/meanpt_midrapidity.pdf" ; - "${results_folder}/${i}/Spectra/midrapidity_yield.pdf" ; - "${results_folder}/${i}/Spectra/total_multiplicity.pdf") - - set(plots "") - foreach(j ${plot_names}) - list(APPEND plots "${j}") - endforeach(j) - - list(APPEND all_plots ${plots}) - - # Perform plotting - add_custom_command(OUTPUT ${plots} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_spectra.py" - "--input_files" "${results_folder}/${i}/Spectra/*.txt" - "--energy" "${energy}" - "--system" "${system}" - "--Nevents" "${N_events_afterburner}" - COMMENT "Plotting spectra for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - - - #----------------------------------------------------------------------------# - # Check conserved quantities - #----------------------------------------------------------------------------# - set(smash_IC_binary "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/IC/SMASH_IC.bin") - set(output_dir "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/${system}_${energy}/${i}/") - set(E_cons_plot "${output_dir}/Energy_Conservation.pdf") - set(Mult_plot "${output_dir}/Pions.pdf") - set(Hydro_lifetime_list "${output_dir}/Hydro_lifetime.txt") - - list(APPEND all_conservation_plots ${E_cons_plot}) - list(APPEND all_multiplicity_plots ${Mult_plot}) - list(APPEND all_lifetime_lists ${Hydro_lifetime_list}) - add_custom_command(OUTPUT ${E_cons_plot} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/check_conservation.py" - "--SMASH_IC" "${smash_IC_oscar}" - "--Hydro_Info" "${results_folder}/${i}/Hydro/Terminal_Output.txt" - "--Sampler" "${full_particle_list}" - "--SMASH_final_state" "${final_particle_list}" - "--SMASH_ana_path" "${SMASH_ANALYSIS_PATH}" - "--output_path" "${output_dir}" - "--Nevents" "${N_events_afterburner}" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/check_conservation.py" - COMMENT "Checking conservation laws for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - add_custom_command(OUTPUT ${Mult_plot} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/check_multiplicities.py" - "--Freezeout_Surface" "${vhlle_freezeout_hypersurface}" - "--Sampler" "${sampled_particle_list}" - "--output_path" "${output_dir}" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/check_multiplicities.py" - COMMENT "Verifying multiplicities for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - add_custom_command(OUTPUT ${Hydro_lifetime_list} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/get_hydro_endtime.py" - "--Hydro_Info" "${results_folder}/${i}/Hydro/Terminal_Output.txt" - "--output_path" "${output_dir}" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/get_hydro_endtime.py" - COMMENT "Find hydro lifetime in ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - #----------------------------------------------------------------------------# - # Analyze flow - #----------------------------------------------------------------------------# - # Define filenames for particle v2 and v3 output - set(flow_fnames "${results_folder}/${i}/Spectra/EP_v2.txt" ; - "${results_folder}/${i}/Spectra/EP_v3.txt") - set(flow_outputs "") - foreach(j ${flow_fnames}) - list(APPEND flow_outputs "${j}") - endforeach(j) - - list(APPEND all_flow_outputs ${flow_outputs}) - - # Perform flow analysis (with STAR cuts) - add_custom_command(OUTPUT "${results_folder}/${i}/Spectra/EP_v2.txt" - COMMAND "python3" "${ARCHIVE_PATH}/flow/python_scripts/flow_calculation.py" - "particle_lists.oscar" "${results_folder}/${i}/Afterburner" "${results_folder}/${i}/Spectra/" - "EP_v2.txt" "2" "pT" "event_plane" "${energy}" - "211,-211,321,-321,2212,-2212,3222,-3222,3112,-3112,3312,-3312,3334,-3334" - "eta" "-0.5" "0.5" "false" "0.0" "4.0" "20" - "false" - COMMENT "Analyzing v2 for ${system} @ ${energy} GeV (${i}/${num_runs})." - DEPENDS "${ARCHIVE_PATH}/flow/python_scripts/flow_calculation.py" - - ) - - add_custom_command(OUTPUT "${results_folder}/${i}/Spectra/EP_v3.txt" - COMMAND "python3" "${ARCHIVE_PATH}/flow/python_scripts/flow_calculation.py" - "particle_lists.oscar" "${results_folder}/${i}/Afterburner" "${results_folder}/${i}/Spectra/" - "EP_v3.txt" "3" "pT" "event_plane" "${energy}" - "211,-211,321,-321,2212,-2212,3222,-3222,3112,-3112,3312,-3312,3334,-3334" - "eta" "-0.5" "0.5" "false" "0.0" "4.0" "20" - "false" - COMMENT "Analyzing v3 for ${system} @ ${energy} GeV (${i}/${num_runs})." - DEPENDS "${ARCHIVE_PATH}/flow/python_scripts/flow_calculation.py" - - ) - - endforeach(i) - - #----------------------------------------------------------------------------# - # Average previously obtained spectra - #----------------------------------------------------------------------------# - file(MAKE_DIRECTORY "${results_folder}/Averaged_Spectra") - set(averaged_spectra "${results_folder}/pT.txt" ; - "${results_folder}/mT.txt" ; - "${results_folder}/dNdy.txt" - ) - add_custom_command(OUTPUT ${averaged_spectra} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/average_spectra.py" - "--smash_ana_dir" "${SMASH_ANALYSIS_PATH}" - "--pT_files" "${results_folder}/*/Spectra/ptspectra.txt" - "--mT_files" "${results_folder}/*/Spectra/mtspectra.txt" - "--y_files" "${results_folder}/*/Spectra/yspectra.txt" - "--midyY_files" "${results_folder}/*/Spectra/midrapidity_yield.txt" - "--meanpT_files" "${results_folder}/*/Spectra/meanpt_midrapidity.txt" - "--v2_files" "${results_folder}/*/Spectra/v2spectra.txt" - "--Nevents" "${N_events_afterburner}" - "--output_dir" "${results_folder}/Averaged_Spectra/" - COMMENT "Averaging spectra for ${system} @ ${energy} GeV." - ) - - #----------------------------------------------------------------------------# - # Average previously obtained flow - #----------------------------------------------------------------------------# - set(averaged_flow_spectra "${results_folder}/v2.txt" ; - "${results_folder}/v3.txt" ; - ) - add_custom_command(OUTPUT ${averaged_flow_spectra} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/average_flow.py" - "--v2_files" "${results_folder}/*/Spectra/EP_v2.txt" - "--v3_files" "${results_folder}/*/Spectra/EP_v3.txt" - "--energy" "${energy}" - "--output_dir" "${results_folder}/Averaged_Spectra/" - COMMENT "Averaging flow for ${system} @ ${energy} GeV." - ) - - #----------------------------------------------------------------------------# - # Average previously obtained endtimes - #----------------------------------------------------------------------------# - set(averaged_endtime_files "${results_folder}/Endtime.txt") - add_custom_command(OUTPUT ${averaged_endtime_files} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/average_endtimes.py" - "--endtime_files" "${results_folder}/*/Hydro_Endtime.txt" - "--energy" "${energy}" - "--output_dir" "${results_folder}/Averaged_Spectra/" - COMMENT "Averaging flow for ${system} @ ${energy} GeV." - ) - - #----------------------------------------------------------------------------# - # Plot averaged quantities - #----------------------------------------------------------------------------# - set(averaged_plots "${results_folder}/Averaged_Spectra/yspectra.pdf" ; - "${results_folder}/Averaged_Spectra/mtspectra.pdf" ; - "${results_folder}/Averaged_Spectra/ptspectra.pdf") - - # Perform plotting of averaged quantities - add_custom_command(OUTPUT ${averaged_plots} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_spectra.py" - "--input_files" "${results_folder}/Averaged_Spectra/*.txt" - "--energy" "${energy}" - "--system" "${system}" - "--Nevents" "${N_events_afterburner}" - "--average" "True" - COMMENT "Plotting averaged spectra for ${system} @ ${energy} GeV." - ) - - - # Define subtargets to enable separated running of the hybrid submodules - - set(target ${system}_${energy}_IC) - add_custom_target(${target} ALL DEPENDS ${smash_IC_files}) - - set(target ${system}_${energy}_hydro) - add_custom_target(${target} ALL DEPENDS ${vhlle_freezeout_hypersurfaces}) - - set(target ${system}_${energy}_sampler) - add_custom_target(${target} ALL DEPENDS ${sampled_particle_lists}) - - set(target ${system}_${energy}_afterburner) - add_custom_target(${target} ALL DEPENDS ${final_particle_lists}) - - set(target ${system}_${energy}_analysis) - add_custom_target(${target} ALL DEPENDS ${all_analysis_outputs}) - - set(target ${system}_${energy}_flow) - add_custom_target(${target} ALL DEPENDS ${all_flow_outputs}) - - set(target ${system}_${energy}_plots) - add_custom_target(${target} ALL DEPENDS ${all_plots}) - - set(target ${system}_${energy}_check_conservation) - add_custom_target(${target} ALL DEPENDS ${all_conservation_plots}) - - set(target ${system}_${energy}_check_multiplicities) - add_custom_target(${target} ALL DEPENDS ${all_multiplicity_plots}) - - set(target ${system}_${energy}_hydro_endtime) - add_custom_target(${target} ALL DEPENDS ${all_lifetime_lists}) - - # Averaging - set(target ${system}_${energy}_average_spectra) - add_custom_target(${target} ALL DEPENDS ${averaged_spectra}) - - set(target ${system}_${energy}_average_flow) - add_custom_target(${target} ALL DEPENDS ${averaged_flow_spectra}) - - set(target ${system}_${energy}_average_plots) - add_custom_target(${target} ALL DEPENDS ${averaged_plots}) - - set(target ${system}_${energy}_average_endtime) - add_custom_target(${target} ALL DEPENDS ${averaged_endtime_files}) - -endfunction() - -run_one_energy("4.3;AuAu") -run_one_energy("6.4;PbPb") -run_one_energy("7.7;AuAu") -run_one_energy("8.8;PbPb") -run_one_energy("17.3;PbPb") -run_one_energy("27.0;AuAu") -run_one_energy("39.0;AuAu") -run_one_energy("62.4;AuAu") -run_one_energy("130.0;AuAu") -run_one_energy("200.0;AuAu") -run_one_energy("2760.0;PbPb") -run_one_energy("5020.0;PbPb") - - -function(Excitation_Funcs - ) - set(outputdir "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results") - set(midY_plot "${outputdir}/midy_yield_exc_func.pdf") - add_custom_command(OUTPUT ${midY_plot} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_excitation_functions.py" - "--midyY_files" "${outputdir}/*/Averaged_Spectra/midyY.txt" - "--v2_files" "${outputdir}/*/Averaged_Spectra/v2spectra.txt" - "--meanpT_files" "${outputdir}/*/Averaged_Spectra/meanpT.txt" - "--output_dir" "${outputdir}/" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_excitation_functions.py" - COMMENT "Plotting excitation functions." - ) - set(target exc_funcs) - add_custom_target(${target} ALL DEPENDS ${midY_plot}) -endfunction() - - -function(Integrated_vn - ) - set(outputdir "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results") - set(int_vn_plot "${outputdir}/int_vn.pdf") - add_custom_command(OUTPUT ${int_vn_plot} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_int_vn.py" - "--vn_files" "${outputdir}/*/Averaged_Spectra/int_vn.txt" - "--output_dir" "${outputdir}/" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_int_vn.py" - COMMENT "Plotting integrated vn excitation functions." - ) - set(target int_vn) - add_custom_target(${target} ALL DEPENDS ${int_vn_plot}) -endfunction() - - -function(Hydro_Lifetimes - ) - set(outputdir "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results") - set(lifetime_file "${outputdir}/hydro_lifetime.txt") - add_custom_command(OUTPUT ${lifetime_file} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/assemble_endtimes.py" - "--endtime_files" "${outputdir}/*/Averaged_Spectra/Endtime.txt" - "--output_dir" "${outputdir}/" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/assemble_endtimes.py" - COMMENT "Plotting integrated vn excitation functions." - ) - set(target hydro_lifetimes) - add_custom_target(${target} ALL DEPENDS ${lifetime_file}) -endfunction() - - -Excitation_Funcs() -Integrated_vn() -Hydro_Lifetimes() - - -################################################################################ -# -# Add functionality to use any custom SMASH config for the initial conditions -# -################################################################################ -function(run_custom_hybrid - energy - ) - message(STATUS "Configuring hybrid run for custom IC config @ sqrt(s) = ${energy} GeV.") - set(results_folder "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/Custom") - file(MAKE_DIRECTORY ${results_folder}) - - set(smash_IC_files "") - set(vhlle_freezeout_hypersurfaces "") - set(sampled_particle_lists "") - set(final_particle_lists "") - set(all_analysis_outputs "") - set(all_plots "") - - set(num_runs "100") - foreach(i RANGE 1 ${num_runs}) - file(MAKE_DIRECTORY "${results_folder}/${i}/IC") # subdir for initial conditions - file(MAKE_DIRECTORY "${results_folder}/${i}/Hydro") # subdir for hydro run - file(MAKE_DIRECTORY "${results_folder}/${i}/Sampler") # subdir for particle sampling - file(MAKE_DIRECTORY "${results_folder}/${i}/Afterburner") # subdir for afterburner - file(MAKE_DIRECTORY "${results_folder}/${i}/Spectra") # subdir for analyzed spectra and plots - - set(smash_IC_file "${results_folder}/${i}/IC/SMASH_IC.dat") - set(smash_IC_oscar "${results_folder}/${i}/IC/SMASH_IC.oscar") - set(smash_IC_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/smash_initial_conditions_custom.yaml") - set(vhlle_default_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/vhlle_hydro") - set(vhlle_output_dir "${results_folder}/${i}/Hydro/") - set(vhlle_updated_config "${results_folder}/${i}/Hydro/vhlle_config") - set(vhlle_freezeout_hypersurface "${results_folder}/${i}/Hydro/freezeout.dat") - set(sampler_input_hypersurface "${results_folder}/${i}/Sampler/freezeout.dat") - set(sampler_default_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/hadron_sampler") - set(sampler_updated_config "${results_folder}/${i}/Sampler/sampler_config") - set(sampler_dir "${results_folder}/${i}/Sampler") - set(sampled_particle_list "${results_folder}/${i}/Sampler/particle_lists.oscar") - set(full_particle_list "${results_folder}/${i}/Sampler/sampling0") - set(smash_afterburner_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/smash_afterburner.yaml") - set(final_particle_list "${results_folder}/${i}/Afterburner/particles_binary.bin") - - list(APPEND smash_IC_files ${smash_IC_file}) - list(APPEND vhlle_freezeout_hypersurfaces ${vhlle_freezeout_hypersurface}) - list(APPEND sampled_particle_lists ${sampled_particle_list}) - list(APPEND final_particle_lists ${final_particle_list}) - - #----------------------------------------------------------------------------# - # Run SMASH and generate initial conditions output - #----------------------------------------------------------------------------# - add_custom_command(OUTPUT "${smash_IC_file}" "${results_folder}/${i}/IC/config.yaml" - COMMAND "${CMAKE_BINARY_DIR}/smash" - "-o" "${results_folder}/${i}/IC" - "-i" "${smash_IC_config}" - "-c" "Modi: {Collider: {Sqrtsnn: ${energy}}}" - "-f" - "-n" - ">" "${results_folder}/${i}/IC/Terminal_Output.txt" - DEPENDS - "${smash_IC_config}" - "${CMAKE_BINARY_DIR}/smash" - COMMENT "Running SMASH for initial conditions of custom hybrid run." - ) - - - #----------------------------------------------------------------------------# - # Feed SMASH initial conditions into vHLLE and run hydro evolution - #----------------------------------------------------------------------------# - # create input file with correct paths for vHLLE - add_custom_command(OUTPUT "${vhlle_updated_config}" - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_vhlle_config.py" - "--vhlle_config" "${vhlle_default_config}" - "--smash_ic" "${smash_IC_file}" - "--output_file" "${vhlle_updated_config}" - "--energy" "${energy}" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_vhlle_config.py" - "${vhlle_default_config}" - COMMENT "Creating input file for vHLLE for custom hybrid run." - ) - - - # Run hydro - add_custom_command(OUTPUT "${vhlle_freezeout_hypersurface}" - COMMAND "${CMAKE_BINARY_DIR}/hlle_visc" - "-params" "${vhlle_updated_config}" - "-ISinput" "${smash_IC_file}" - "-outputDir" "${vhlle_output_dir}" - ">" "${vhlle_output_dir}/Terminal_Output.txt" - DEPENDS - "${vhlle_updated_config}" - "${CMAKE_BINARY_DIR}/hlle_visc" - COMMENT "Running vHLLE for custom hybrid run." - ) - - #----------------------------------------------------------------------------# - # Run Cooper-Frye sampler for particlization of freezeout surface - #----------------------------------------------------------------------------# - # create input file with correct paths for sampler - set(N_events_afterburner "1000") - add_custom_command(OUTPUT "${sampler_updated_config}" - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_sampler_config.py" - "--sampler_config" "${sampler_default_config}" - "--vhlle_config" "${vhlle_updated_config}" - "--output_file" "${sampler_updated_config}" - "--Nevents" "${N_events_afterburner}" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_sampler_config.py" - "${sampler_default_config}" - COMMENT "Creating input file for custom hybrid run." - ) - - # Run sampler - add_custom_command(OUTPUT ${sampled_particle_list} - COMMAND "${CMAKE_BINARY_DIR}/sampler" "events" "1" "${sampler_updated_config}" - ">" "${results_folder}/${i}/Sampler/Terminal_Output.txt" - DEPENDS - "${CMAKE_BINARY_DIR}/sampler" - "${sampler_updated_config}" - COMMENT "Sampling particles from freezeout hypersurface (${i}/${num_runs})." - ) - - - #----------------------------------------------------------------------------# - # Run SMASH as afterburner - #----------------------------------------------------------------------------# - # Add spectators to particle list and rename it to be in accordance - # with SMASH list modus input format - add_custom_command(OUTPUT ${full_particle_list} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/add_spectators.py" - "--sampled_particle_list" "${sampler_dir}/particle_lists.oscar" - "--initial_particle_list" "${smash_IC_oscar}" - "--output_file" "${full_particle_list}" - "--smash_config" "${results_folder}/${i}/IC/config.yaml" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/add_spectators.py" - COMMENT "Adding spectators to sampled particle list for afterburner (${i}/${num_runs})." - ) - - # Run afterburner evolution - set(setMonash FALSE) - if (energy GREATER_EQUAL 200) - set(setMonash TRUE) - endif() - - add_custom_command(OUTPUT "${final_particle_list}" "${results_folder}/${i}/Afterburner/config.yaml" - COMMAND "${CMAKE_BINARY_DIR}/smash" - "-i" "${smash_afterburner_config}" - "-c" "Modi: { List: { File_Directory: ${sampler_dir}} }" - "-c" "General: { Nevents: ${N_events_afterburner} }" - "-c" "Collision_Term: { String_Parameters: {Use_Monash_Tune: ${setMonash} } }" - "-o" "${results_folder}/${i}/Afterburner" - "-f" - "-n" - ">" "/dev/null" - DEPENDS - "${smash_afterburner_config}" - "${CMAKE_BINARY_DIR}/smash" - "${full_particle_list}" - COMMENT "Running SMASH afterburner for custom hybrid run." - ) - - - #----------------------------------------------------------------------------# - # Analyse data - #----------------------------------------------------------------------------# - # Define filenames for particle spectra and create list of those - set(spectra_fnames "${results_folder}/${i}/Spectra/yspectra.txt" ; - "${results_folder}/${i}/Spectra/mtspectra.txt" ; - "${results_folder}/${i}/Spectra/ptspectra.txt" ; - "${results_folder}/${i}/Spectra/v2spectra.txt" ; - "${results_folder}/${i}/Spectra/meanmt0_midrapidity.txt" ; - "${results_folder}/${i}/Spectra/meanpt_midrapidity.txt" ; - "${results_folder}/${i}/Spectra/midrapidity_yield.txt" ; - "${results_folder}/${i}/Spectra/total_multiplicity.txt") - set(analysis_outputs "") - foreach(j ${spectra_fnames}) - list(APPEND analysis_outputs "${j}") - endforeach(j) - - list(APPEND all_analysis_outputs ${analysis_outputs}) - - # Perform analysis - add_custom_command(OUTPUT ${analysis_outputs} - COMMAND "python3" "${SMASH_ANALYSIS_PATH}/test/energy_scan/mult_and_spectra.py" - "--output_files" ${analysis_outputs} - "--input_files" "${final_particle_list}" - COMMENT "Analyzing spectra for ${system} @ ${energy} GeV (${i}/${num_runs})." - DEPENDS "${SMASH_ANALYSIS_PATH}/test/energy_scan/mult_and_spectra.py" - ) - - #----------------------------------------------------------------------------# - # Plot spectra - #----------------------------------------------------------------------------# - # Define plot names - set(plot_names "${results_folder}/${i}/Spectra/yspectra.pdf" ; - "${results_folder}/${i}/Spectra/mtspectra.pdf" ; - "${results_folder}/${i}/Spectra/ptspectra.pdf" ; - "${results_folder}/${i}/Spectra/v2spectra.pdf" ; - "${results_folder}/${i}/Spectra/meanmt0_midrapidity.pdf" ; - "${results_folder}/${i}/Spectra/meanpt_midrapidity.pdf" ; - "${results_folder}/${i}/Spectra/midrapidity_yield.pdf" ; - "${results_folder}/${i}/Spectra/total_multiplicity.pdf") - - set(plots "") - foreach(j ${plot_names}) - list(APPEND plots "${j}") - endforeach(j) - - list(APPEND all_plots ${plots}) - - # Perform plotting - add_custom_command(OUTPUT ${plots} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_spectra.py" - "--input_files" "${results_folder}/${i}/Spectra/*.txt" - "--Nevents" "${N_events_afterburner}" - COMMENT "Plotting spectra for custom hybrid run." - ) - - endforeach(i) - - #----------------------------------------------------------------------------# - # Average previously obtained spectra - #----------------------------------------------------------------------------# - file(MAKE_DIRECTORY "${results_folder}/Averaged_Spectra") - set(averaged_spectra "${results_folder}/pT.txt" ; - "${results_folder}/mT.txt" ; - "${results_folder}/dNdy.txt" - ) - add_custom_command(OUTPUT ${averaged_spectra} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/average_spectra.py" - "--smash_ana_dir" "${SMASH_ANALYSIS_PATH}" - "--pT_files" "${results_folder}/*/Spectra/ptspectra.txt" - "--mT_files" "${results_folder}/*/Spectra/mtspectra.txt" - "--y_files" "${results_folder}/*/Spectra/yspectra.txt" - "--midyY_files" "${results_folder}/*/Spectra/midrapidity_yield.txt" - "--meanpT_files" "${results_folder}/*/Spectra/meanpt_midrapidity.txt" - "--v2_files" "${results_folder}/*/Spectra/v2spectra.txt" - "--Nevents" "${N_events_afterburner}" - "--output_dir" "${results_folder}/Averaged_Spectra/" - COMMENT "Averaging spectra for custom hybrid run." - ) - - #----------------------------------------------------------------------------# - # Plot averaged quantities - #----------------------------------------------------------------------------# - set(averaged_plots "${results_folder}/Averaged_Spectra/yspectra.pdf" ; - "${results_folder}/Averaged_Spectra/mtspectra.pdf" ; - "${results_folder}/Averaged_Spectra/ptspectra.pdf") - - # Perform plotting of averaged quantities - add_custom_command(OUTPUT ${averaged_plots} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_spectra.py" - "--input_files" "${results_folder}/Averaged_Spectra/*.txt" - "--Nevents" "${N_events_afterburner}" - "--average" "True" - COMMENT "Plotting averaged spectra for custom hybrid run." - ) - - - # Define subtargets to enable separated running of the hybrid submodules - set(target custom_IC) - add_custom_target(${target} ALL DEPENDS ${smash_IC_files}) - - set(target custom_hydro) - add_custom_target(${target} ALL DEPENDS ${vhlle_freezeout_hypersurfaces}) - - set(target custom_sampler) - add_custom_target(${target} ALL DEPENDS ${sampled_particle_lists}) - - set(target custom_afterburner) - add_custom_target(${target} ALL DEPENDS ${final_particle_lists}) - - set(target custom_analysis) - add_custom_target(${target} ALL DEPENDS ${all_analysis_outputs}) - - set(target custom_plots) - add_custom_target(${target} ALL DEPENDS ${all_plots}) - - # Averaging - set(target custom_average_spectra) - add_custom_target(${target} ALL DEPENDS ${averaged_spectra}) - - set(target custom_average_plots) - add_custom_target(${target} ALL DEPENDS ${averaged_plots}) - -endfunction() - -run_custom_hybrid("15.0") - - -################################################################################ -# -# Add functionality for a low statistics test case -# This test case is not suitable for production runs, as quanutum number -# conservation cannot be guaranteed owing to the lack of precision. -# -################################################################################ -function(test_hybrid - energy_and_system - ) - list(GET energy_and_system 0 energy) - list(GET energy_and_system 1 system) - message(STATUS "Configuring hybrid test run, not intended for production runs.") - set(results_folder "${CMAKE_CURRENT_BINARY_DIR}/Hybrid_Results/test") - file(MAKE_DIRECTORY ${results_folder}) - - set(smash_IC_files "") - set(vhlle_freezeout_hypersurfaces "") - set(sampled_particle_lists "") - set(final_particle_lists "") - set(all_analysis_outputs "") - set(all_plots "") - - set(num_runs "4") - foreach(i RANGE 1 ${num_runs}) - file(MAKE_DIRECTORY "${results_folder}/${i}/IC") # subdir for initial conditions - file(MAKE_DIRECTORY "${results_folder}/${i}/Hydro") # subdir for hydro run - file(MAKE_DIRECTORY "${results_folder}/${i}/Sampler") # subdir for particle sampling - file(MAKE_DIRECTORY "${results_folder}/${i}/Afterburner") # subdir for afterburner - file(MAKE_DIRECTORY "${results_folder}/${i}/Spectra") # subdir for analyzed spectra and plots - - set(smash_IC_file "${results_folder}/${i}/IC/SMASH_IC.dat") - set(smash_IC_oscar "${results_folder}/${i}/IC/SMASH_IC.oscar") - set(smash_IC_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/smash_initial_conditions_AuAu.yaml") - set(vhlle_default_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/vhlle_hydro") - set(vhlle_output_dir "${results_folder}/${i}/Hydro/") - set(vhlle_updated_config "${results_folder}/${i}/Hydro/vhlle_config") - set(vhlle_freezeout_hypersurface "${results_folder}/${i}/Hydro/freezeout.dat") - set(sampler_input_hypersurface "${results_folder}/${i}/Sampler/freezeout.dat") - set(sampler_default_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/hadron_sampler") - set(sampler_updated_config "${results_folder}/${i}/Sampler/sampler_config") - set(sampler_dir "${results_folder}/${i}/Sampler") - set(sampled_particle_list "${results_folder}/${i}/Sampler/particle_lists.oscar") - set(full_particle_list "${results_folder}/${i}/Sampler/sampling0") - set(smash_afterburner_config "${CMAKE_CURRENT_SOURCE_DIR}/configs/smash_afterburner.yaml") - set(final_particle_list "${results_folder}/${i}/Afterburner/particles_binary.bin") - - list(APPEND smash_IC_files ${smash_IC_file}) - list(APPEND vhlle_freezeout_hypersurfaces ${vhlle_freezeout_hypersurface}) - list(APPEND sampled_particle_lists ${sampled_particle_list}) - list(APPEND final_particle_lists ${final_particle_list}) - - #----------------------------------------------------------------------------# - # Run SMASH and generate initial conditions output - #----------------------------------------------------------------------------# - add_custom_command(OUTPUT "${smash_IC_file}" "${results_folder}/${i}/IC/config.yaml" - COMMAND "${CMAKE_BINARY_DIR}/smash" - "-o" "${results_folder}/${i}/IC" - "-i" "${smash_IC_config}" - "-f" - "-n" - ">" "${results_folder}/${i}/IC/Terminal_Output.txt" - DEPENDS - "${smash_IC_config}" - "${CMAKE_BINARY_DIR}/smash" - COMMENT "Running SMASH for initial conditions of hybrid test run for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - - #----------------------------------------------------------------------------# - # Feed SMASH initial conditions into vHLLE and run hydro evolution - #----------------------------------------------------------------------------# - # create input file with correct paths for vHLLE - add_custom_command(OUTPUT "${vhlle_updated_config}" - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_vhlle_config.py" - "--vhlle_config" "${vhlle_default_config}" - "--smash_ic" "${smash_IC_file}" - "--output_file" "${vhlle_updated_config}" - "--test" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_vhlle_config.py" - "${vhlle_default_config}" - COMMENT "Creating input file for vHLLE for hybrid test run for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - - # Run hydro - add_custom_command(OUTPUT "${vhlle_freezeout_hypersurface}" - COMMAND "${CMAKE_BINARY_DIR}/hlle_visc" - "-params" "${vhlle_updated_config}" - "-ISinput" "${smash_IC_file}" - "-outputDir" "${vhlle_output_dir}" - ">" "${vhlle_output_dir}/Terminal_Output.txt" - DEPENDS - "${vhlle_updated_config}" - "${CMAKE_BINARY_DIR}/hlle_visc" - COMMENT "Running vHLLE for hybrid test run for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - #----------------------------------------------------------------------------# - # Run Cooper-Frye sampler for particlization of freezeout surface - #----------------------------------------------------------------------------# - # create input file with correct paths for sampler - set(N_events_afterburner "50") - add_custom_command(OUTPUT "${sampler_updated_config}" - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_sampler_config.py" - "--sampler_config" "${sampler_default_config}" - "--vhlle_config" "${vhlle_updated_config}" - "--output_file" "${sampler_updated_config}" - "--Nevents" "${N_events_afterburner}" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/create_sampler_config.py" - "${sampler_default_config}" - COMMENT "Creating input file for hybrid test run for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - # Run sampler - add_custom_command(OUTPUT ${sampled_particle_list} - COMMAND "${CMAKE_BINARY_DIR}/sampler" "events" "1" "${sampler_updated_config}" - ">" "${results_folder}/${i}/Sampler/Terminal_Output.txt" - DEPENDS - "${CMAKE_BINARY_DIR}/sampler" - "${sampler_updated_config}" - COMMENT "Sampling particles from freezeout hypersurface (${i}/${num_runs})." - ) - - - #----------------------------------------------------------------------------# - # Run SMASH as afterburner - #----------------------------------------------------------------------------# - # Add spectators to particle list and rename it to be in accordance - # with SMASH list modus input format - add_custom_command(OUTPUT ${full_particle_list} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/add_spectators.py" - "--sampled_particle_list" "${sampler_dir}/particle_lists.oscar" - "--initial_particle_list" "${smash_IC_oscar}" - "--output_file" "${full_particle_list}" - "--smash_config" "${results_folder}/${i}/IC/config.yaml" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/add_spectators.py" - COMMENT "Adding spectators to sampled particle list for afterburner (${i}/${num_runs})." - ) - - # Run afterburner evolution - add_custom_command(OUTPUT "${final_particle_list}" "${results_folder}/${i}/Afterburner/config.yaml" - COMMAND "${CMAKE_BINARY_DIR}/smash" - "-i" "${smash_afterburner_config}" - "-c" "Modi: { List: { File_Directory: ${sampler_dir}} }" - "-c" "General: { Nevents: ${N_events_afterburner} }" - "-o" "${results_folder}/${i}/Afterburner" - "-f" - "-n" - ">" "/dev/null" - DEPENDS - "${smash_afterburner_config}" - "${CMAKE_BINARY_DIR}/smash" - "${full_particle_list}" - COMMENT "Running SMASH afterburner for hybrid test run for ${system} @ ${energy} GeV (${i}/${num_runs})." - ) - - - #----------------------------------------------------------------------------# - # Analyse data - #----------------------------------------------------------------------------# - # Define filenames for particle spectra and create list of those - set(spectra_fnames "${results_folder}/${i}/Spectra/yspectra.txt" ; - "${results_folder}/${i}/Spectra/mtspectra.txt" ; - "${results_folder}/${i}/Spectra/ptspectra.txt" ; - "${results_folder}/${i}/Spectra/v2spectra.txt" ; - "${results_folder}/${i}/Spectra/meanmt0_midrapidity.txt" ; - "${results_folder}/${i}/Spectra/meanpt_midrapidity.txt" ; - "${results_folder}/${i}/Spectra/midrapidity_yield.txt" ; - "${results_folder}/${i}/Spectra/total_multiplicity.txt") - set(analysis_outputs "") - foreach(j ${spectra_fnames}) - list(APPEND analysis_outputs "${j}") - endforeach(j) - - list(APPEND all_analysis_outputs ${analysis_outputs}) - - # Perform analysis - add_custom_command(OUTPUT ${analysis_outputs} - COMMAND "python3" "${SMASH_ANALYSIS_PATH}/test/energy_scan/mult_and_spectra.py" - "--output_files" ${analysis_outputs} - "--input_files" "${final_particle_list}" - COMMENT "Analyzing spectra for ${system} @ ${energy} GeV (${i}/${num_runs})." - DEPENDS "${SMASH_ANALYSIS_PATH}/test/energy_scan/mult_and_spectra.py" - ) - - #----------------------------------------------------------------------------# - # Plot spectra - #----------------------------------------------------------------------------# - # Define plot names - set(plot_names "${results_folder}/${i}/Spectra/yspectra.pdf" ; - "${results_folder}/${i}/Spectra/mtspectra.pdf" ; - "${results_folder}/${i}/Spectra/ptspectra.pdf" ; - "${results_folder}/${i}/Spectra/v2spectra.pdf" ; - "${results_folder}/${i}/Spectra/meanmt0_midrapidity.pdf" ; - "${results_folder}/${i}/Spectra/meanpt_midrapidity.pdf" ; - "${results_folder}/${i}/Spectra/midrapidity_yield.pdf" ; - "${results_folder}/${i}/Spectra/total_multiplicity.pdf") - - set(plots "") - foreach(j ${plot_names}) - list(APPEND plots "${j}") - endforeach(j) - - list(APPEND all_plots ${plots}) - - # Perform plotting - add_custom_command(OUTPUT ${plots} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_spectra.py" - "--input_files" "${results_folder}/${i}/Spectra/*.txt" - "--Nevents" "${N_events_afterburner}" - COMMENT "Plotting spectra for custom hybrid run." - ) - - endforeach(i) - - #----------------------------------------------------------------------------# - # Average previously obtained spectra - #----------------------------------------------------------------------------# - file(MAKE_DIRECTORY "${results_folder}/Averaged_Spectra") - set(averaged_spectra "${results_folder}/pT.txt" ; - "${results_folder}/mT.txt" ; - "${results_folder}/dNdy.txt" - ) - add_custom_command(OUTPUT ${averaged_spectra} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/average_spectra.py" - "--smash_ana_dir" "${SMASH_ANALYSIS_PATH}" - "--pT_files" "${results_folder}/*/Spectra/ptspectra.txt" - "--mT_files" "${results_folder}/*/Spectra/mtspectra.txt" - "--y_files" "${results_folder}/*/Spectra/yspectra.txt" - "--midyY_files" "${results_folder}/*/Spectra/midrapidity_yield.txt" - "--meanpT_files" "${results_folder}/*/Spectra/meanpt_midrapidity.txt" - "--v2_files" "${results_folder}/*/Spectra/v2spectra.txt" - "--Nevents" "${N_events_afterburner}" - "--output_dir" "${results_folder}/Averaged_Spectra/" - COMMENT "Averaging spectra for hybrid test run for ${system} @ ${energy} GeV." - ) - - #----------------------------------------------------------------------------# - # Plot averaged quantities - #----------------------------------------------------------------------------# - set(averaged_plots "${results_folder}/Averaged_Spectra/yspectra.pdf" ; - "${results_folder}/Averaged_Spectra/mtspectra.pdf" ; - "${results_folder}/Averaged_Spectra/ptspectra.pdf") - - # Perform plotting of averaged quantities - add_custom_command(OUTPUT ${averaged_plots} - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/python_scripts/plot_spectra.py" - "--input_files" "${results_folder}/Averaged_Spectra/*.txt" - "--Nevents" "${N_events_afterburner}" - "--average" "True" - COMMENT "Plotting averaged spectra for hybrid test run for ${system} @ ${energy} GeV." - ) - - - # Define subtargets to enable separated running of the hybrid submodules - set(target test_IC) - add_custom_target(${target} ALL DEPENDS ${smash_IC_files}) - - set(target test_hydro) - add_custom_target(${target} ALL DEPENDS ${vhlle_freezeout_hypersurfaces}) - - set(target test_sampler) - add_custom_target(${target} ALL DEPENDS ${sampled_particle_lists}) - - set(target test_afterburner) - add_custom_target(${target} ALL DEPENDS ${final_particle_lists}) - - set(target test_analysis) - add_custom_target(${target} ALL DEPENDS ${all_analysis_outputs}) - - set(target test_plots) - add_custom_target(${target} ALL DEPENDS ${all_plots}) - - # Averaging - set(target test_average_spectra) - add_custom_target(${target} ALL DEPENDS ${averaged_spectra}) - - set(target test_average_plots) - add_custom_target(${target} ALL DEPENDS ${averaged_plots}) - -endfunction() - -test_hybrid("7.7;AuAu") diff --git a/LICENSE b/LICENSE index 0cd9e98..f288702 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,4 @@ - -This document contains the two licenses currently in use for the smash-vhlle-hybrid. -GPLv3 for all python and bash scripts -BSD 3-clause for cmake scripts - - GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. @@ -677,37 +672,3 @@ may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . - - - -######################################################################## - -Cmake BSD 3-clause - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -* Neither the names of Kitware, Inc., the Insight Software Consortium, -nor the names of their contributors may be used to endorse or promote -products derived from this software without specific prior written -permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 399a51b..9ad12d7 100644 --- a/README.md +++ b/README.md @@ -20,186 +20,3 @@ If you are using the SMASH-vHLLE-hybrid, please cite [arXiv:2212.08724](https:// Before building the full hybrid model, please make sure that the submodules listed above (SMASH, vHLLE, vHLLE params, sampler) are available and already compiled. Instructions on how to compile them can be found in the corresponding READMEs. The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in CMakeLists.txt of each submodule. It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. - -## Building the hybrid model - -Once the prerequisites are met, use the following commands to build the full hybrid model in a separate `build` directory: - - mkdir build - cd build - cmake .. -DSMASH_PATH=[...]/smash/build -DVHLLE_PATH=[...]/vhlle -DVHLLE_PARAMS_PATH=[...]/vhlle_params/ -DSAMPLER_PATH=[...]/smash-hadron-sampler/build - -where `[...]` denote the paths to the `smash/build` directory, the `vhlle` directory, the `vhlle_params` directory and the `smash-hadron-sampler/build` directory. The binaries of the precompiled submodules are expected to be located therein. The `vhlle_params` directory does not contain any binary though, it only holds the equations of state necessary for the hydrodynamic evolution. - -All subtargets corresponding to the predefined collision setups have been created by `cmake`. To more easily divide the full hybrid run into smaller pieces, different targets are created for each step of the hybrid simulation. They have to be run one after the other in the order specified below. For a Pb+Pb collision at sqrt(s) = 8.8 GeV, this chain is executed via: - - make PbPb_8.8_IC - make PbPb_8.8_hydro - make PbPb_8.8_sampler - make PbPb_8.8_afterburner - -The output files of the individual submodules as well as the configuration files used can be found in the newly-created directory `[...]/build/Hybrid_Results/PbPb_8.8GeV/i`, where `i` corresponds to the i-th hybrid run in an event-by-event setup. By default, the full hybrid model is run 100 times in parallel for different initial states. To change the number of parallel runs, modify the parameter `num_runs` in `CMakeLists.txt`. - -## Building the hybrid model linked to the SMASH-analysis -To also provide the automatic analysis of the final particle lists, run the following commands to link the project to the smash-analysis: - - mkdir build - cd build - cmake .. -DSMASH_PATH=[...]/smash/build -DVHLLE_PATH=[...]/vhlle -DVHLLE_PARAMS_PATH=[...]/vhlle_params/ -DSAMPLER_PATH=[...]/smash-hadron-sampler/build -DSMASH_ANALYSIS_PATH=[...]/smash-analysis - -Once the afterburner was run, the resulting particle lists can be analysed and plotted by executing: - - make PbPb_8.8_analysis - make PbPb_8.8_plots - -The generated plots and output files are then located in `[...]/build/Hybrid_Results/PbPb_8.8GeV/i/Spectra`. The above commands analyse and plot the results of each of the 100 parallel hybrid runs. It is useful to average over the obtained results to obtain averaged final-state plots. This is done by executing - - make PbPb_8.8_average_spectra - make PbPb_8.8_average_plots - -in this specific order. The final output files, that is tables and plots, are then located in `[...]/build/Hybrid_Results/PbPb_8.8GeV/Averaged_Spectra`. - -In addition, it is possible to extract excitation functions for the mean transverse momentum and the mid-rapidity yield if the SMASH-vHLLE-hybrid was run for different collision energies. To obtain those, execute - - make exc_funcs - -after having analyzed and averaged the output at all collision energies with the above stated commands. The resulting excitation functions in terms of tables and plots containing entries at all previously run collision energies are then located in `[...]/build/Hybrid_Results`. - -## Configuring the collision setups -A number of different collision setups for the hybrid model are supported out of the box. The shear viscosities applied are taken from *Karpenko et al.: Phys.Rev.C 91 (2015)* and the longitudinal and transversal smearing parameters are adjusted to improve agreement with experimental data. The supported collision systems are: -* AuAu @ sqrt(s) = 4.3 GeV -* PbPb @ sqrt(s) = 6.4 GeV -* AuAu @ sqrt(s) = 7.7 GeV -* PbPb @ sqrt(s) = 8.8 GeV -* PbPb @ sqrt(s) = 17.3 GeV -* AuAu @ sqrt(s) = 27.0 GeV -* AuAu @ sqrt(s) = 39.0 GeV -* AuAu @ sqrt(s) = 62.4 GeV -* AuAu @ sqrt(s) = 130.0 GeV -* AuAu @ sqrt(s) = 200.0 GeV -* PbPb @ sqrt(s) = 2.76 TeV -* PbPb @ sqrt(s) = 5.02 TeV - -They can be executed in analogy to the PbPb @ sqrt(s) = 8.8 GeV example presented above. - -To run additional setups it is necessary to add the corresponding targets to the bottom of the `CMakeLists.txt` file.
-The configuration files are located in `[...]/configs/`. Four different configuration files are necessary to run the full hybrid model. These are: -1. `smash_initial_conditions_AuAu.yaml` or `smash_initial_conditions_PbPb.yaml` for the configuration of the initial setup of the collision. There are two initial conditions files corresponding to collision systems of Au+Au and Pb+Pb, respectively. If additional collision systems are desired, it is necessary to add an appropriate configuration file to the `[...]/configs/` directory. For details and further information about the configuration of SMASH, consult the [SMASH User Guide](http://theory.gsi.de/~smash/userguide/current/). -2. `vhlle_hydro` for the configuration of the hydrodynamic evolution.
-Further information about the configuration of vHLLE is provided in the `README.txt` of vHLLE. -3. `hadron_sampler` for the configuration of the sampler.
-Further information about the configuration of the sampler is provided in the `README.md` of the `hadron-sampler`. -4. `smash_afterburner.yaml` for the configuration of the SMASH afterburner evolution based on the sampled particle list. - -**Note:** In all configuration files, the paths to input files from the previous stages are adjusted automatically. Also cross-parameters that require consistency between the hydrodynamics evolution and the sampler, e.g. viscosities and critical energy density, are taken care of automatically. - -## Running a test setup - -The default setup of the SMASH-vHLLE-hybrid is meant to be executed on a computing cluster. To test the functionality and to also run it on a local computer, there is the possibility to execute a test setup of the SMASH-vHLLE-hybrid, which is a Au+Au collision at sqrt(s) = 7.7 GeV. The statistics are significantly reduced however and the grid for the hydrodynamic evolution is characterized by large cells, too large for a realistic simulation scenario.
-**This test setup is hence only meant to be used in order to test the functionality of the framework and the embedded scripts, but not for production runs.** - -To execute the different stages of the test setup, run - - make test_IC - make test_hydro - make test_sampler - make test_afterburner - -If the SMASH-vHLLE-hybrid is linked to the SMASH-analysis, one can additionally test the analysis scripts by executing - - make test_analysis - make test_average_spectra - make test_average_plots - -The results are then located in `[...]/build/Hybrid_Results/test`. - - -## Using a custom SMASH configuration file for the initial conditions - -In addition to the above described predefined Au+Au and Pb+Pb collisions, it is possible to employ a custom `SMASH` configuration file in the initial stage. This file is expected to be located in the `[...]/configs/` directory and named `smash_initial_conditions_custom.yaml`. An example configuration is shipped with the `SMASH-vHLLE-hybrid`, it can be modified as desired. Among the essential parameters the collision system and impact parameter range can be adjusted in the configuration file `smash_initial_conditions_custom.yaml`, while the collision energy needs to be specified in the file `CMakeLists.txt` as an argument of the function `run_custom_hybrid(energy)`. To run the hybrid with this custom configuration file, execute the following commands: - - make custom_IC - make custom_hydro - make custom_sampler - make custom_afterburner - -The results are stored in `[...]/build/Hybrid_Results/Custom` and the subdirectories are structured identically as for the predefined Au+Au or Pb+Pb collisions as described above. - -To further analyze and average the outcome, if the `SMASH-vHLLE-hybrid` is coupled to the `SMASH-analysis` as described above, one may further execute - - make custom_analysis - make custom_average_spectra - make custom_average_plots - -to obtain rapidity and transverse mass spectra that are stored in `[...]/build/Hybrid_Results/Custom/Averaged_Spectra`. - -**Note:** -When using the custom configuration, it is possible to specify the viscosity and smearing parameters employed in the hydrodynamical evolution in `python_scripts/hydro_parameters.py` file, by adding or editing an entry in the `hydro_params` dictionary, where the collision energy is the key. By default for a defined collision energy the values in the dictionary are used, while for all other energies parameter values of the nearest lower energy are taken. - -# Module exchanges and further modifications -It might be desired to run the SMASH-vHLLE-hybrid, but relying on a different initial state, particle sampler or similar. For this, the `CMakeLists.txt` need to be updated accordingly. Exemplary instructions for a number of use cases are provided in the following. - -#### Running vHLLE on an averaged initial state from SMASH -By default, the SMASH-vHLLE-hybrid performs event-by-event simulations. To run a single-shot hydrodynamical event with initial conditions obtained from multiple averaged SMASH events, you can proceed as follows: -1. Open the configuration file for the SMASH initial state: `configs/smash_initial_conditions_AuAu.yaml` and/or `configs/smash_initial_conditions_PbPb.yaml`. -2. Update the event counter (`Nevents`) with the number of events you wish to average over for the initial state. -3. Open the file `CMakeLists.txt` and set the `num_runs` parameter to 1. -4. **Caveat:** For the moment, the addition of spectators to the sampled particle list for the afterburner evolution is not implemented in the case of averaged initial conditions. In the current state, the totality of spectators extracted from all initial SMASH events would be added to each sampled afterburner event. This is of course wrong. For an approximation in central events it might be sufficient to neglect those spectators. This can be achieved by replacing line 97 in `python_scripts/add_spectators.py`: `write_full_particle_list(spectators)` by `write_full_particle_list([])`.
-Alternatively, one could modify SMASH in order to print participants as well as spectators to the output file that is used as input in vHLLE, such that they also contribute to the averaged initial state. For this, open the SMASH source file `smash/src/icoutput.cc` and navigate to the function `void ICOutput::at_interaction`. Therein, the statement -```cpp -if (!is_spectator) { - std::fprintf(file_.get(), "%g %g %g %g %g %g %g %g %s %i \n", - particle.position().tau(), particle.position()[1], - particle.position()[2], particle.position().eta(), m_trans, - particle.momentum()[1], particle.momentum()[2], rapidity, - particle.pdgcode().string().c_str(), particle.type().charge()); -} -``` -controls that spectators are not printed to the output. Simply remove the if statement and the corresponding brackets to print all particles to the output. Make sure to re-compile SMASH after this modification and before linking to the hybrid. Additionally, one also needs to replace line 97 in `python_scripts/add_spectators.py`: `write_full_particle_list(spectators)` by `write_full_particle_list([])` to not add the spectators twice. -4. Run `cmake ..` in the build directory. -5. Proceed as usual to run the different steps as described above. - -#### Using different kinds of initial condition that are supported by vHLLE -By default, the SMASH-vHLLE-hybrid relies on an initial state from SMASH. In vHLLE however, a number of different initial states are supported. To make use of them, proceed as follows: -1. Open the vHLLE configuration file in `configs/vhlle_hydro`. -2. Update the parameter `icModel` with the number corresponding to the initial state you wish to use. For further information about potential IC models, please consult [vHLLE](https://github.com/yukarpenko/vhlle). -3. If vHLLE requires an external file for the initialization, update line 170 in `CMakeLists.txt`: -```cmake -"-ISinput" "${smash_IC_file}" -``` -by replacing the `smash_IC_file` by whichever file you want to use. It might make sense to define a new parameter (similar to `smash_IC_file`) with the path to the respective file, as done for the `smash_IC_file` in line 112. If no external file is required, simply remove/comment line 170. -4. Run `cmake ..` in the build directory. -5. Proceed as usual to run the different steps as described above. - -#### Using a different particle sampler -By default, the SMASH-vHLLE-hybrid relies on the SMASH-hadron-sampler for the particlization process. If desired, this sampler can be exchanged by any other. It is however necessary that the created output fulfills the requirement from SMASH for the afterburner evolution. Information about those requirements is provided in the [SMASH user guide](http://theory.gsi.de/~smash/userguide/2.0.2/input_modi_list_.html). -The commands to produce the configuration file and execute the particle sampler are located in lines 183-204 of `CMakeLists.txt`. To exchange this sampler, you may proceed as follows: -1. First, the script `python_scripts/create_sampler_config.py` is executed to create the configuration file which is needed to initialize the sampler execution. If the alternative sampler also requires such a configuration file, you should update the python script accordingly. -2. Second, the sampler itself is executed. This is achieved with the following command in lines 198-204: -```cmake -add_custom_command(OUTPUT ${sampled_particle_list} -COMMAND "${CMAKE_BINARY_DIR}/sampler" "events" "1" "${sampler_updated_config}" - ">" "${results_folder}/${i}/Sampler/Terminal_Output.txt" -DEPENDS - "${CMAKE_BINARY_DIR}/sampler" - "${sampler_updated_config}" -COMMENT "Sampling particles from freezeout hypersurface (${i}/${num_runs})." -) -``` -where the lines following 'COMMAND' correspond to whatever one would type in the terminal in order to execute the sampler from there. Note though that ever piece of this command needs to be surrounded by quotation marks within the CMake script. -The 'OUTPUT' command is followed by the path to the output file that is to be created from the sampler. It usually corresponds to the sampled particle list. Whatever follows 'DEPENDS' are those files/scripts on which the sampler relies. More concretely, this means that the sampler is re-executed if any of the files/scripts mentioned therein are changed. Finally, 'COMMENT' defines a corresponding comment that is printed to the terminal upon execution of the sampler.
-To exchange the sampler by a different one, the user may exchange the lines described above by a custom command targeted at executing a different sampler. -3. The sampled particle list is further combined with the spectators, that where not plugged into the hydro evolution in the first place. This is achieved with the script `python_scripts/add_spectators.py`. If desired, update the cmake command in lines 211-219 accordingly, to rely on the sampled particle list obtained from the new sampler. -4. If a combination with spectators is not desired, the command mentioned under 3 can simply be removed. In that case however the path to the particle list that is read in from SMASH for the afterburner evolution needs to be exchanged by the path to the directory, where the list produced with the new sampler is located. This can be achieved by updating line 225 `"-c" "Modi: { List: { File_Directory: ${sampler_dir}} }"` and replacing the variable `${sampler_dir}` with the correct path. Furthermore, the name of the output file (if not identical to the default, which is `sampling0` within the SMASH-vHLLE-hybrid), needs to be updated in the SMASH afterburner config `configs/smash_afterburner.yaml`: -```yaml -Modi: - List: - File_Directory: "../build/" - File_Prefix: "sampling" - Shift_Id: 0 -``` - - Note that SMASH expects every file to terminate with a number (see [SMASH user guide](http://theory.gsi.de/~smash/userguide/current/) for further details and background), so make sure to name the file accordingly. If the filename does not end with `0`, you need to update the parameter `Shift_Id` accordingly. The prefix of the file name (that is everything except the number) is controlled by the `File_Prefix` argument and may also require being updated. The `File_Directory` is updated automatically from within the `CMakeLists.txt`. - -4. Run `cmake ..` in the build directory and make sure it configures without errors. -5. Proceed as usual to run the different steps as described above. diff --git a/cmake/FindPythonModules.cmake b/cmake/FindPythonModules.cmake deleted file mode 100644 index 6b8a6e7..0000000 --- a/cmake/FindPythonModules.cmake +++ /dev/null @@ -1,20 +0,0 @@ -function(find_python_module module) - string(TOUPPER ${module} module_upper) - if(NOT PY_${module_upper}) - if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") - set(${module}_FIND_REQUIRED TRUE) - endif() - # A module's location is usually a directory, but for binary modules - # it's a .so file. - execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))" - RESULT_VARIABLE _${module}_status - OUTPUT_VARIABLE _${module}_location - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT _${module}_status) - set(PY_${module_upper} ${_${module}_location} CACHE STRING - "Location of Python module ${module}") - endif(NOT _${module}_status) - endif(NOT PY_${module_upper}) - find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper}) -endfunction(find_python_module) From f53ffa834dc51c7047c2b3471411c4d10157b7bf Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 10 May 2023 15:33:50 +0200 Subject: [PATCH 002/549] Rename folder with python scripts --- {python_scripts => python}/add_spectators.py | 0 {python_scripts => python}/assemble_endtimes.py | 0 {python_scripts => python}/average_endtimes.py | 0 {python_scripts => python}/average_flow.py | 0 {python_scripts => python}/average_spectra.py | 0 {python_scripts => python}/check_conservation.py | 0 {python_scripts => python}/check_multiplicities.py | 0 {python_scripts => python}/create_sampler_config.py | 0 {python_scripts => python}/create_vhlle_config.py | 0 {python_scripts => python}/extract_hypersurface_contributions.py | 0 {python_scripts => python}/get_hydro_endtime.py | 0 {python_scripts => python}/hydro_parameters.py | 0 {python_scripts => python}/plot_excitation_functions.py | 0 {python_scripts => python}/plot_int_vn.py | 0 {python_scripts => python}/plot_spectra.py | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename {python_scripts => python}/add_spectators.py (100%) rename {python_scripts => python}/assemble_endtimes.py (100%) rename {python_scripts => python}/average_endtimes.py (100%) rename {python_scripts => python}/average_flow.py (100%) rename {python_scripts => python}/average_spectra.py (100%) rename {python_scripts => python}/check_conservation.py (100%) rename {python_scripts => python}/check_multiplicities.py (100%) rename {python_scripts => python}/create_sampler_config.py (100%) rename {python_scripts => python}/create_vhlle_config.py (100%) rename {python_scripts => python}/extract_hypersurface_contributions.py (100%) rename {python_scripts => python}/get_hydro_endtime.py (100%) rename {python_scripts => python}/hydro_parameters.py (100%) rename {python_scripts => python}/plot_excitation_functions.py (100%) rename {python_scripts => python}/plot_int_vn.py (100%) rename {python_scripts => python}/plot_spectra.py (100%) diff --git a/python_scripts/add_spectators.py b/python/add_spectators.py similarity index 100% rename from python_scripts/add_spectators.py rename to python/add_spectators.py diff --git a/python_scripts/assemble_endtimes.py b/python/assemble_endtimes.py similarity index 100% rename from python_scripts/assemble_endtimes.py rename to python/assemble_endtimes.py diff --git a/python_scripts/average_endtimes.py b/python/average_endtimes.py similarity index 100% rename from python_scripts/average_endtimes.py rename to python/average_endtimes.py diff --git a/python_scripts/average_flow.py b/python/average_flow.py similarity index 100% rename from python_scripts/average_flow.py rename to python/average_flow.py diff --git a/python_scripts/average_spectra.py b/python/average_spectra.py similarity index 100% rename from python_scripts/average_spectra.py rename to python/average_spectra.py diff --git a/python_scripts/check_conservation.py b/python/check_conservation.py similarity index 100% rename from python_scripts/check_conservation.py rename to python/check_conservation.py diff --git a/python_scripts/check_multiplicities.py b/python/check_multiplicities.py similarity index 100% rename from python_scripts/check_multiplicities.py rename to python/check_multiplicities.py diff --git a/python_scripts/create_sampler_config.py b/python/create_sampler_config.py similarity index 100% rename from python_scripts/create_sampler_config.py rename to python/create_sampler_config.py diff --git a/python_scripts/create_vhlle_config.py b/python/create_vhlle_config.py similarity index 100% rename from python_scripts/create_vhlle_config.py rename to python/create_vhlle_config.py diff --git a/python_scripts/extract_hypersurface_contributions.py b/python/extract_hypersurface_contributions.py similarity index 100% rename from python_scripts/extract_hypersurface_contributions.py rename to python/extract_hypersurface_contributions.py diff --git a/python_scripts/get_hydro_endtime.py b/python/get_hydro_endtime.py similarity index 100% rename from python_scripts/get_hydro_endtime.py rename to python/get_hydro_endtime.py diff --git a/python_scripts/hydro_parameters.py b/python/hydro_parameters.py similarity index 100% rename from python_scripts/hydro_parameters.py rename to python/hydro_parameters.py diff --git a/python_scripts/plot_excitation_functions.py b/python/plot_excitation_functions.py similarity index 100% rename from python_scripts/plot_excitation_functions.py rename to python/plot_excitation_functions.py diff --git a/python_scripts/plot_int_vn.py b/python/plot_int_vn.py similarity index 100% rename from python_scripts/plot_int_vn.py rename to python/plot_int_vn.py diff --git a/python_scripts/plot_spectra.py b/python/plot_spectra.py similarity index 100% rename from python_scripts/plot_spectra.py rename to python/plot_spectra.py From 1a06110e4dbcae239893ea76aaa818aa3cd2c8b0 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 10 May 2023 15:35:42 +0200 Subject: [PATCH 003/549] Add first bash codebase structure --- Hybrid-handler | 32 +++ bash/command_line_parsers/helper.bash | 9 + bash/command_line_parsers/main_parser.bash | 9 + bash/command_line_parsers/sub_parser.bash | 9 + bash/error_codes.bash | 23 ++ bash/logger.bash | 234 +++++++++++++++++++++ bash/source_codebase_files.bash | 9 + bash/system_requirements.bash | 9 + bash/utility_functions.bash | 34 +++ bash/version.bash | 9 + 10 files changed, 377 insertions(+) create mode 100755 Hybrid-handler create mode 100644 bash/command_line_parsers/helper.bash create mode 100644 bash/command_line_parsers/main_parser.bash create mode 100644 bash/command_line_parsers/sub_parser.bash create mode 100644 bash/error_codes.bash create mode 100644 bash/logger.bash create mode 100644 bash/source_codebase_files.bash create mode 100644 bash/system_requirements.bash create mode 100644 bash/utility_functions.bash create mode 100644 bash/version.bash diff --git a/Hybrid-handler b/Hybrid-handler new file mode 100755 index 0000000..e45637b --- /dev/null +++ b/Hybrid-handler @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +#----------------------------------------------------------------------------# +# __ __ __ _ __ __ __ ____ # +# / / / /_ __/ /_ _____(_)___/ / / / / /___ _____ ____/ / /__ _____ # +# / /_/ / / / / __ \/ ___/ / __ / / /_/ / __ `/ __ \/ __ / / _ \/ ___/ # +# / __ / /_/ / /_/ / / / / /_/ / / __ / /_/ / / / / /_/ / / __/ / # +# /_/ /_/\__, /_.___/_/ /_/\__,_/ /_/ /_/\__,_/_/ /_/\__,_/_/\___/_/ # +# /____/ # +# # +#----------------------------------------------------------------------------# + +source bash/logger.bash + + +Print_Trace "Trace message" +Print_Debug "Debug message" +Print_Info "Information message" +Print_Attention "Attention message" +Print_Warning "Warning message" +Print_Error "Error" +( Print_Fatal_And_Exit "Fatal error, exit!" ) +Print_Internal_And_Exit "Internal, for developer error" diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash new file mode 100644 index 0000000..8c4660d --- /dev/null +++ b/bash/command_line_parsers/helper.bash @@ -0,0 +1,9 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash new file mode 100644 index 0000000..8c4660d --- /dev/null +++ b/bash/command_line_parsers/main_parser.bash @@ -0,0 +1,9 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + diff --git a/bash/command_line_parsers/sub_parser.bash b/bash/command_line_parsers/sub_parser.bash new file mode 100644 index 0000000..8c4660d --- /dev/null +++ b/bash/command_line_parsers/sub_parser.bash @@ -0,0 +1,9 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + diff --git a/bash/error_codes.bash b/bash/error_codes.bash new file mode 100644 index 0000000..04d0d04 --- /dev/null +++ b/bash/error_codes.bash @@ -0,0 +1,23 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# Standard bash exit codes +readonly HYBRID_success_exit_code=0 +readonly HYBRID_failure_exit_code=1 + +# Variables for exit codes (between 64 and 113) +# -> http://tldp.org/LDP/abs/html/exitcodes.html +readonly HYBRID_fatal_builtin=64 +readonly HYBRID_fatal_file_not_found=65 +readonly HYBRID_fatal_wrong_config_file=66 +readonly HYBRID_fatal_value_error=71 +readonly HYBRID_fatal_logic_error=110 +readonly HYBRID_fatal_missing_feature=111 +readonly HYBRID_fatal_variable_unset=112 +readonly HYBRID_internal=113 diff --git a/bash/logger.bash b/bash/logger.bash new file mode 100644 index 0000000..1597f99 --- /dev/null +++ b/bash/logger.bash @@ -0,0 +1,234 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== +# +# This file has been taken and adapted from the BashLogger project +# and therefore its original license header is reported here below. +# +#---------------------------------------------------------------------------------------- +# +# Copyright (c) 2019 Alessandro Sciarra +# +# This file is part of BashLogger. +# +# BashLogger is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BashLogger 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BashLogger. If not, see . +# +#---------------------------------------------------------------------------------------- +# +# The logger will print output to new file descriptor 3 to be able to use the logger in +# functions that "return" printing to stdout to be called in $(). +# +# ATTENTION: It might be checked if fd 3 exists and in case open it in the Logger itself. +# However, if the first Logger call is done in a subshell, the fd 3 is not +# open globally for the script and, therefore, we should ensure to open the +# fd 3 for the script. We do then this at source time. +# +# NOTE: Nothing is done if this file is executed and not sourced! +# +if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + exec 3>&1 +fi + +function Print_Trace() +{ + __static__Logger 'TRACE' "$@" +} + +function Print_Debug() +{ + __static__Logger 'DEBUG' "$@" +} + +function Print_Info() +{ + __static__Logger 'INFO' "$@" +} + +function Print_Attention() +{ + __static__Logger 'ATTENTION' "$@" +} + +function Print_Warning() +{ + __static__Logger 'WARNING' "$@" +} + +function Print_Error() +{ + __static__Logger 'ERROR' "$@" +} + +function Print_Fatal_And_Exit() +{ + __static__Logger 'FATAL' "$@" +} + +function Print_Internal_And_Exit() +{ + __static__Logger 'INTERNAL' "$@" +} + +function __static__Logger() +{ + if [[ $# -lt 1 ]]; then + __static__Logger 'INTERNAL' "${FUNCNAME} called without label!" + fi + local label label_length label_to_be_printed color string final_endline restore_default + final_endline='\n' + restore_default='\e[0m' + label_length=10 + label="$1"; shift + label_to_be_printed=$(printf "%${label_length}s" "${label}:") + if [[ ! ${label} =~ ^(INTERNAL|FATAL|ERROR|WARNING|ATTENTION|INFO|DEBUG|TRACE)$ ]]; then + __static__Logger 'INTERNAL' "${FUNCNAME} called with unknown label '${label}'!" + fi + __static__Is_Level_On "${label}" || return 0 + exec 4>&1 # duplicate fd 1 to restore it later + case "${label}" in + ERROR|FATAL ) + color='\e[91m' + exec 1>&2 ;; # here stdout to stderr! + INTERNAL ) + color='\e[38;5;202m' ;;& # ;;& means go on in case matching following -> do *) + INFO ) + color='\e[92m' ;;& + ATTENTION ) + color='\e[38;5;200m' ;;& + WARNING ) + color='\e[93m' ;;& + DEBUG ) + color='\e[38;5;38m' ;;& + TRACE ) + color='\e[38;5;247m' ;;& + * ) + exec 1>&3 ;; # here stdout to fd 3! + esac + if __static__Is_Element_In_Array '--' "$@"; then + while [[ "$1" != '--' ]]; do + case "$1" in + -n ) + final_endline='' + shift ;; + -l ) + label_to_be_printed="$(printf "%${label_length}s" '')" + shift ;; + -d ) + restore_default='' + shift ;; + * ) + __static__Logger 'INTERNAL' "${FUNCNAME} called with unknown option \"$1\"!" ;; + esac + done + shift + fi + if [[ $# -eq 0 ]]; then + __static__Logger 'INTERNAL' "${FUNCNAME} called without message!" + fi + while [[ $1 =~ ^\\n ]]; do + printf '\n' + set -- "${1/#\\n/}" "${@:2}" + done + printf "\e[1m${color}${label_to_be_printed}\e[22m ${1//%/%%}" + shift + if [[ $# -eq 0 ]]; then + printf "${final_endline}" # If nothing more to print use 'final_endline' + else + printf '\n' + while [[ $# -gt 1 ]]; do + printf "${label_to_be_printed//?/ } ${1//%/%%}\n" + shift + done + printf "${label_to_be_printed//?/ } ${1//%/%%}${final_endline}" + fi + if [[ ${label} = 'INTERNAL' ]]; then + printf "${label_to_be_printed//?/ } Please, contact developers.\n" + fi + printf "${restore_default}" + exec 1>&4- # restore fd 1 and close fd 4 and not close fd 3 (it must stay open, see top of the file!) + if [[ ${label} =~ ^(FATAL|INTERNAL)$ ]]; then + exit "${user_fatal_exit_code:-1}" + fi +} + +function __static__Is_Level_On() +{ + local label + label="$1" + # FATAL and INTERNAL always on + if [[ ${label} =~ ^(FATAL|INTERNAL)$ ]]; then + return 0 + fi + # VERBOSE environment variable defines how verbose the output should be: + # - unset or empty -> till INFO (no DEBUG TRACE) + # - numeric -> till that level (1=ERROR, 2=WARNING, ...) + # - string -> till that level + local loggerLevels loggerLevelsOn level index + loggerLevels=( [1]='ERROR' [2]='WARNING' [3]='ATTENTION' [4]='INFO' [5]='DEBUG' [6]='TRACE' ) + loggerLevelsOn=() + if [[ ${VERBOSE} =~ ^[0-9]+$ ]]; then + loggerLevelsOn=( "${loggerLevels[@]:1:VERBOSE}" ) + elif [[ ${VERBOSE} =~ ^(ERROR|WARNING|ATTENTION|INFO|DEBUG|TRACE)$ ]]; then + for level in "${loggerLevels[@]}"; do + loggerLevelsOn+=( "${level}" ) + if [[ ${VERBOSE} = "${level}" ]]; then + break + fi + done + elif [[ ${VERBOSE} =~ ^(FATAL|INTERNAL)$ ]]; then + loggerLevelsOn=( 'FATAL' ) + else + loggerLevelsOn=( 'FATAL' 'ERROR' 'WARNING' 'ATTENTION' 'INFO' ) + fi + for level in "${loggerLevelsOn[@]}"; do + if [[ ${label} = "${level}" ]]; then + return 0 + fi + done + return 1 +} + +function __static__Is_Element_In_Array() +{ + # ATTENTION: Since this function is used in the middle of the logger, the + # logger cannot be used in this function otherwise fd 4 is closed! + local elementToBeFound arrayEntry + elementToBeFound="$1" + for arrayEntry in "${@:2}"; do + if [[ "${arrayEntry}" = "${elementToBeFound}" ]]; then + return 0 + fi + done + return 1 +} + +#-----------------------------------------------------------------# +#Set functions readonly to avoid possible redefinitions elsewhere +readonly -f \ + Print_Trace \ + Print_Debug \ + Print_Info \ + Print_Attention \ + Print_Warning \ + Print_Error \ + Print_Fatal_And_Exit \ + Print_Internal_And_Exit \ + __static__Logger \ + __static__Is_Level_On \ + __static__Is_Element_In_Array diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash new file mode 100644 index 0000000..8c4660d --- /dev/null +++ b/bash/source_codebase_files.bash @@ -0,0 +1,9 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash new file mode 100644 index 0000000..8c4660d --- /dev/null +++ b/bash/system_requirements.bash @@ -0,0 +1,9 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash new file mode 100644 index 0000000..9f485e0 --- /dev/null +++ b/bash/utility_functions.bash @@ -0,0 +1,34 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Functions_Defined_In_This_File_Readonly() +{ + # Here we assume all functions are defined with the same stile, + # including empty parenteses and the braces on new lines! I.e. + # + # function nameOfTheFunction() + # + # Accepted symbols in function name: letters, '_', ':' and '-' + # + # NOTE: The file from which this function is called is ${BASH_SOURCE[1]} + local declared_functions + declared_functions=( # Here word splitting can split names, no space allowed in function name! + $(grep -E '^[[:space:]]*function[[:space:]]+[-[:alnum:]_:]+\(\)[[:space:]]*$' "${BASH_SOURCE[1]}" |\ + sed -E 's/^[[:space:]]*function[[:space:]]+([^(]+)\(\)[[:space:]]*$/\1/') + ) + if [[ ${#declared_functions[@]} -eq 0 ]]; then + Print_Internal_And_Exit\ + "Function \"${FUNCNAME}\" called, but no function found in file\n file \"${BASH_SOURCE[1]}\"" + else + readonly -f "${declared_functions[@]}" + fi +} + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/version.bash b/bash/version.bash new file mode 100644 index 0000000..8c4660d --- /dev/null +++ b/bash/version.bash @@ -0,0 +1,9 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + From f93bfe70ecf908cc3e764f8854be094796ebbdff Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 11 May 2023 14:26:43 +0200 Subject: [PATCH 004/549] Add first draft with contribution guidelines --- CONTRIBUTING.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1edaedf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# Contributing to SMASH-vHLLE-Hybrid + +If you are reading this, you might be thinking of contributing to the codebase: Excellent decision! :upside_down_face: + +As an external contributor, go fork the repository, work on a branch dedicated to your changes and then create a pull request. Details on this workflow can be found e.g. [here](https://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project). + +In any case, before starting editing code, take few minutes to go through the following recommendations. +It will speed up the code review and avoid comments about codebase notation. + + +## Editing existing files or creating new ones + +This codebase is distributed under the terms of the GPLv3 license. +In general, you should follow the instructions therein. +Some guidelines for authors are provided in the following, in order to reach a coherent style of copyright and licensing notices. + +* If you are contributing for the first time, be sure that your git username and email are [set up correctly](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup). + To check them you can run `git config --list` and see the values of the `user.name` and `user.email` fields. + Add yourself to the AUTHORS file. +* If you create a new file, add copyright and license remarks to a comment at the top of the file (after a possible shebang). + This should read like + ```bash + #=================================================== + # + # Copyright (c) 2023 + # SMASH Hybrid Team + # + # GNU General Public License (GPLv3 or later) + # + #=================================================== + ``` + with the correct development year. + Use the `.bash` extension (**NOT** `.sh`) for files containing *bash* code. +* When editing an existing file, ensure there is the current year in the copyright line. + The years should form a comma-separated list. + If two subsequent years or more are given, the list can be merged into a range. + +### Hooks and good commits + +In order to e.g. avoid to forget to update the copyright statement, git hooks can be used to perform some checks at commit time. +You can implement them in your favorite language or look for some on the web. +We encourage you to checkout and use the [**GitHooks**](https://github.com/AxelKrypton/GitHooks) which will also enforce good habits about how to structure your commit messages, as well as sanitize white spaces in source code. +Please, refer to their README file for more information. + + +## Bash notation in the codebase + +The general advice is pretty trivial: **Be consistent with what you find**. +Here a list of some aspects worth mentioning: +* indentation is done _exclusively with spaces_ and no Tab should be used; +* braces for functions are put on separate lines; +* loops and conditional clauses are started on a single line, i.e. the `do` and `then` keywords are **NOT** put on a separate line; +* local variables are typed with all small letters and words separated by underscores, e.g. `local_variable_name`; +* global variables are prefixed by `HYBRID_` and this is meant for better readability, e.g. `HYBRID_global_variable`; +* function names are made of underscore-separated words with initials capitalized, e.g. `Function_Name_With_Words`; +* all functions declared in each separate file are marked in the end of the file as `readonly`; +* files are sourced all together by sourcing a single dedicated file. + From 22ae9e9e927a5f8bf47b5310332bc314f00e386d Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 11 May 2023 14:37:24 +0200 Subject: [PATCH 005/549] Add few more guidelines about usage of quotes --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1edaedf..f595abb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,6 +53,7 @@ Here a list of some aspects worth mentioning: * local variables are typed with all small letters and words separated by underscores, e.g. `local_variable_name`; * global variables are prefixed by `HYBRID_` and this is meant for better readability, e.g. `HYBRID_global_variable`; * function names are made of underscore-separated words with initials capitalized, e.g. `Function_Name_With_Words`; +* quotes are correctly used, i.e. everything that _might_ break if unquoted is quoted; +* single quotes are used if there is no need of using double or different quotes; * all functions declared in each separate file are marked in the end of the file as `readonly`; * files are sourced all together by sourcing a single dedicated file. - From d73dbe4f622522b327419bd8fc1353bb5301f93b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 11 May 2023 15:41:12 +0200 Subject: [PATCH 006/549] Add contributors list and mailmap file --- .mailmap | 12 ++++++++++++ AUTHORS.md | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 .mailmap create mode 100644 AUTHORS.md diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..e398695 --- /dev/null +++ b/.mailmap @@ -0,0 +1,12 @@ +Alessandro Sciarra +Anna Schäfer +Anna Schäfer <36138176+akschaefer@users.noreply.github.com> +Niklas Götz +Niklas Götz +Niklas Götz +Niklas Götz Niklas Goetz +Jan Hammelmann +Jan Hammelmann <33685583+JanHammelmann@users.noreply.github.com> +Renan Hirayama +Renan Hirayama RenHirayama <79031685+RenHirayama@users.noreply.github.com> +Zuzana Paulinyova diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..c5a7d37 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,17 @@ +## Present developers and maintainers + +| Name | E-mail | +| :---: | :----: | +| Alessandro Sciarra | asciarra@fias.uni-frankfurt.de | +| Jan Hammelmann | hammelmann@fias.uni-frankfurt.de | +| Niklas Götz | goetz@fias.uni-frankfurt.de | +| Renan Hirayama | hirayama@fias.uni-frankfurt.de | +| Zuzana Paulinyova | zuzana.paulinyova@upjs.sk | + +## Past developers + +Please, note that the e-mail address of past developers might not be active any more. + +| Name | E-mail | +| :----: | :----: | +| Anna Schäfer | aschaefer@fias.uni-frankfurt.de | From 10f11ab1333147585debf95a82b9e01f8feb8bed Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 08:52:41 +0200 Subject: [PATCH 007/549] Add statement about lenght of lines of code --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f595abb..c6a5924 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,6 +48,7 @@ Please, refer to their README file for more information. The general advice is pretty trivial: **Be consistent with what you find**. Here a list of some aspects worth mentioning: * indentation is done _exclusively with spaces_ and no Tab should be used; +* lines of code are split around 100 characters and should never be longer than 120; * braces for functions are put on separate lines; * loops and conditional clauses are started on a single line, i.e. the `do` and `then` keywords are **NOT** put on a separate line; * local variables are typed with all small letters and words separated by underscores, e.g. `local_variable_name`; From eb75f028233aa30a027338be7b2c9e7fe66c90c4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 14:48:02 +0200 Subject: [PATCH 008/549] Improve some statements about bash notation --- CONTRIBUTING.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6a5924..229d1a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ Some guidelines for authors are provided in the following, in order to reach a c * If you are contributing for the first time, be sure that your git username and email are [set up correctly](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup). To check them you can run `git config --list` and see the values of the `user.name` and `user.email` fields. - Add yourself to the AUTHORS file. + Add yourself to the [AUTHORS](AUTHORS.md) file. * If you create a new file, add copyright and license remarks to a comment at the top of the file (after a possible shebang). This should read like ```bash @@ -47,9 +47,15 @@ Please, refer to their README file for more information. The general advice is pretty trivial: **Be consistent with what you find**. Here a list of some aspects worth mentioning: -* indentation is done _exclusively with spaces_ and no Tab should be used; +* indentation is done _exclusively with spaces_ and **no** Tab should be used; * lines of code are split around 100 characters and should never be longer than 120; -* braces for functions are put on separate lines; +* bash functions use both the `function` keyword and parenthesis and the enclosing braces are put on separate lines, + ```bash + function Example_Function() + { + # Body of the function + } + ``` * loops and conditional clauses are started on a single line, i.e. the `do` and `then` keywords are **NOT** put on a separate line; * local variables are typed with all small letters and words separated by underscores, e.g. `local_variable_name`; * global variables are prefixed by `HYBRID_` and this is meant for better readability, e.g. `HYBRID_global_variable`; From dc250ced10932125d0ed8331d3a453cee213c932 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 16:37:13 +0200 Subject: [PATCH 009/549] Add further guidance about bash notation --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 229d1a9..d183f42 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,7 +58,9 @@ Here a list of some aspects worth mentioning: ``` * loops and conditional clauses are started on a single line, i.e. the `do` and `then` keywords are **NOT** put on a separate line; * local variables are typed with all small letters and words separated by underscores, e.g. `local_variable_name`; -* global variables are prefixed by `HYBRID_` and this is meant for better readability, e.g. `HYBRID_global_variable`; +* global variables in the codebase are prefixed by `HYBRID_` and this is meant for better readability, e.g. `HYBRID_global_variable`; +* analogously, global variables in tests are prefixed by `HYBRIDT_`; +* variables are always expanded using braces, i.e. you should use `${variable}` instead of `$variable`; * function names are made of underscore-separated words with initials capitalized, e.g. `Function_Name_With_Words`; * quotes are correctly used, i.e. everything that _might_ break if unquoted is quoted; * single quotes are used if there is no need of using double or different quotes; From cb7c3d2acc75d711e87548bcabb6312b1daf9979 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 17:26:17 +0200 Subject: [PATCH 010/549] Add statement about functions local to file --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d183f42..2fd7f70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,6 +62,7 @@ Here a list of some aspects worth mentioning: * analogously, global variables in tests are prefixed by `HYBRIDT_`; * variables are always expanded using braces, i.e. you should use `${variable}` instead of `$variable`; * function names are made of underscore-separated words with initials capitalized, e.g. `Function_Name_With_Words`; +* functions that are and should be used only in the file where declared are prefixed by `__static__`; * quotes are correctly used, i.e. everything that _might_ break if unquoted is quoted; * single quotes are used if there is no need of using double or different quotes; * all functions declared in each separate file are marked in the end of the file as `readonly`; From 38f273d9aea704ac99636824b31c7c114f91277c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 17:50:24 +0200 Subject: [PATCH 011/549] Use global internal exit code when using logger --- bash/logger.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/logger.bash b/bash/logger.bash index 1597f99..cec3494 100644 --- a/bash/logger.bash +++ b/bash/logger.bash @@ -77,7 +77,7 @@ function Print_Error() function Print_Fatal_And_Exit() { - __static__Logger 'FATAL' "$@" + exit_code=${HYBRID_internal:-1} __static__Logger 'FATAL' "$@" } function Print_Internal_And_Exit() @@ -163,7 +163,7 @@ function __static__Logger() printf "${restore_default}" exec 1>&4- # restore fd 1 and close fd 4 and not close fd 3 (it must stay open, see top of the file!) if [[ ${label} =~ ^(FATAL|INTERNAL)$ ]]; then - exit "${user_fatal_exit_code:-1}" + exit "${exit_code:-1}" fi } From aa79f16dd09b27a1d66b012c24113e4fbbde1d86 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 17:51:03 +0200 Subject: [PATCH 012/549] Implement utility functions to check if element is in array --- bash/utility_functions.bash | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 9f485e0..37c513b 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -7,6 +7,24 @@ # #=================================================== +function Element_In_Array_Equals_To() +{ + local element + for element in "${@:2}"; do + [[ "${element}" == "$1" ]] && return 0 + done + return 1 +} + +function Element_In_Array_Matches() +{ + local element + for element in "${@:2}"; do + [[ "${element}" =~ $1 ]] && return 0 + done + return 1 +} + function Make_Functions_Defined_In_This_File_Readonly() { # Here we assume all functions are defined with the same stile, From 528893b6c8973c88672007c264070c863fcd9dcc Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 20:25:45 +0200 Subject: [PATCH 013/549] Make error codes contiguous and add few ones --- bash/error_codes.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bash/error_codes.bash b/bash/error_codes.bash index 04d0d04..7f2ac31 100644 --- a/bash/error_codes.bash +++ b/bash/error_codes.bash @@ -16,7 +16,8 @@ readonly HYBRID_failure_exit_code=1 readonly HYBRID_fatal_builtin=64 readonly HYBRID_fatal_file_not_found=65 readonly HYBRID_fatal_wrong_config_file=66 -readonly HYBRID_fatal_value_error=71 +readonly HYBRID_fatal_command_line=67 +readonly HYBRID_fatal_value_error=68 readonly HYBRID_fatal_logic_error=110 readonly HYBRID_fatal_missing_feature=111 readonly HYBRID_fatal_variable_unset=112 From dae20882d5929a20ec2282f911a586138bd272d1 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 20:26:48 +0200 Subject: [PATCH 014/549] Add to-be-implemented function to check system requirements --- bash/system_requirements.bash | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 8c4660d..2cb9c95 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -7,3 +7,7 @@ # #=================================================== +function Check_System_Requirements() +{ + Print_Error "${FUNCNAME} not implemented yet. Skipping it." +} From 0bf8efa49ce965700ff01f8231ba6cf0c783bca5 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 15 May 2023 15:48:53 +0200 Subject: [PATCH 015/549] Add a smash initial conditions blackbox for testing --- tests/mocks/smash_IC_black-box.py | 134 ++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100755 tests/mocks/smash_IC_black-box.py diff --git a/tests/mocks/smash_IC_black-box.py b/tests/mocks/smash_IC_black-box.py new file mode 100755 index 0000000..8ce6309 --- /dev/null +++ b/tests/mocks/smash_IC_black-box.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +import time + +def check_config(valid_config): + if args.i is None: + args.i = "./config.yaml" + if not os.path.exists(args.i): + print(fatal_error+"The configuration file was expected at './config.yaml', but the file does not exist.") + sys.exit() + if not valid_config: + print(fatal_error+"Validation of SMASH input failed.") + sys.exit() + return + +def print_terminal_start(): + # generated with https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 + Terminal_Out = "###########################################################################################################\n _________ _____ _____ _________ ___ ___ \n / _____/ / \ / _ \ / _____// | \ \n \_____ \ / \ / \ / /_\ \ \_____ \/ ~ \ \n / \/ Y \/ | \/ \ Y / \n /_______ /\____|__ /\____|__ /_______ /\___|_ / \n \/ \/ \/ \/ \/ \n .__ ___. .__ .___ \n ____ ______ _ __ | |__ ___.__.\_ |_________|__| __| _/ ________________ \n / \_/ __ \ \/ \/ / ______ | | < | | | __ \_ __ \ |/ __ | ______ _/ __ \_ __ \__ \ \n | | \ ___/\ / /_____/ | Y \___ | | \_\ \ | \/ / /_/ | /_____/ \ ___/| | \// __ \_ \n |___| /\___ >\/\_/ |___| / ____| |___ /__| |__\____ | \___ >__| (____ / \n \/ \/ \/\/ \/ \/ \/ \/ \n ___ .____________ ___. .__ __ ___. ___ \n / / | \_ ___ \ \_ |__ | | _____ ____ | | _\_ |__ _______ ___ \ \ \n / / | / \ \/ ______ | __ \| | \__ \ _/ ___\| |/ /| __ \ / _ \ \/ / \ \ \n ( ( | \ \____ /_____/ | \_\ \ |__/ __ \\ \___| < | \_\ ( <_> > < ) ) \n \ \ |___|\______ / |___ /____(____ /\___ >__|_ \|___ /\____/__/\_ \ / / \n \__\ \/ \/ \/ \/ \/ \/ \/ /__/ \n ###########################################################################################################\n" + print(Terminal_Out) + print("running smash IC") + return + +def create_folders_structure(): + # if no path is given, create folder structure + if args.o is None: + args.o = "./data/" + if not os.path.exists(args.o): + os.mkdir(args.o) + n = 0 + found_folder = False + while not found_folder: + if not os.path.exists(args.o + str(n)): + found_folder = True + else: + n += 1 + args.o += str(n) + # create path if needed + if not os.path.exists(args.o): + os.makedirs(args.o) + # fix format + if args.o[-1] != "/": + args.o += "/" + return + +def check_if_empty_output(): + f_config = args.o + "config.yaml" + if os.path.exists(f_config): + print(fatal_error + "Output directory would get overwritten. Select a different output directory, clean up, or tell SMASH to ignore existing files.") + sys.exit() + else: + # create config file + f = open(f_config, "w") + f.close() + return + +def run_smash(finalize): + # create smash.lock file + f = open(args.o+file_name_is_running, "w") + f.close() + # open unfinished particle files + particles_out_oscar = open(file_particles_out_oscar+name_unfinished, "w") + particles_out_dat = open(file_particles_out_dat+name_unfinished, "w") + # run the black box + for ts in range(11): + print("running t = {} fm".format(ts)) + time.sleep(0.5) + particles_out_oscar.close() + particles_out_dat.close() + + if finalize: + finish() + else: + print(fatal_error+"crash") + return + +def finish(): + # rename output by removing the .unfinished file ending + if os.path.exists(file_particles_out_oscar+name_unfinished): + os.rename(file_particles_out_oscar+name_unfinished, file_particles_out_oscar) + else: + print("somehow the output (.oscar) file was not properly written") + sys.exit() + + if os.path.exists(file_particles_out_dat+name_unfinished): + os.rename(file_particles_out_dat+name_unfinished, file_particles_out_dat) + else: + print("somehow the output file (.dat) was not properly written") + sys.exit() + + # remove smash.lock file + os.remove(args.o+file_name_is_running) + return + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-i", required=False, + help="File to the config.yaml") + parser.add_argument("-o", required=False, + help="Path to the output folder") + parser.add_argument("-c", required=False, + help="Make changes to config.yaml (this is not tested here)") + parser.add_argument("--fail_with", required=False, + default=None, + choices=["invalid_config", "smash_crashes"], + help="Choose a place where SMASH should fail") + + args = parser.parse_args() + + config_is_valid = False if args.fail_with == "invalid_config" else True + smash_finishes = False if args.fail_with == "smash_crashes" else True + + fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " + file_name_is_running = "smash.lock" + name_unfinished = ".unfinished" + name_oscar = ".oscar" + name_dat = ".dat" + name_particles_file = "SMASH_IC" + + # initialize the system + check_config(config_is_valid) + create_folders_structure() + check_if_empty_output() + + # SMASH output participants + spectators + file_particles_out_oscar = args.o+name_particles_file+name_oscar + # special SMASH output for vHLLE. only participants + file_particles_out_dat = args.o+name_particles_file+name_dat + + # smash is now ready to run + print_terminal_start() + run_smash(smash_finishes) \ No newline at end of file From 5471d7859f51c827b627a54b94cff27c8287ea4b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 12 May 2023 20:40:45 +0200 Subject: [PATCH 016/549] Add first infrastructure for functional tests This commit contains lots of incomplete code and it is meant to fix some infrastructure about what is meant to be in the future a tests driver (for the moment about functional tests only). Few relevant comments: - The command line parser is not complete and functionality to select which test should be run or to get a list of available tests is missing. - The executable tests script is also far from being complete. There the function to print the report and the function to clean up files are missing. - The tests executable script is structured in a way such that it can easily become a tests driver which can run different categories of tests. By providing in different files (and different ways) the few functions 'Define_Available_Tests' and those invoked by 'Run_Tests', it is possible to reuse the same infrastructure for different classes of tests (e.g. functional and unit). The switch might be done by a first command line option like 'unit' or 'functional'. This will imply to rename the script to something more general like 'tests_driver' and complete the command line parser functionality (to parse the new first argument and adapt the helper message). --- tests/.gitignore | 1 + tests/command_line_parser_for_tests.bash | 107 +++++++++++++++++++ tests/functional_tests | 125 +++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 tests/.gitignore create mode 100644 tests/command_line_parser_for_tests.bash create mode 100755 tests/functional_tests diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..cab8e90 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +run_tests/ diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash new file mode 100644 index 0000000..7079fb1 --- /dev/null +++ b/tests/command_line_parser_for_tests.bash @@ -0,0 +1,107 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Parse_Command_Line_Options() +{ + while [[ $# -gt 0 ]]; do + case $1 in + -h | --help ) + __static__Print_Helper + exit ${HYBRID_success_exit_code} + shift ;; + -r | --report-level ) + if [[ $2 =~ ^[0-3]$ ]]; then + readonly HYBRIDT_report_level=$2 + else + __static__Print_Option_Specification_Error_And_Exit $1 + fi + shift 2 ;; + -t | --run-tests ) + if [[ ! $2 =~ ^- && "$2" != '' ]]; then + if [[ $2 =~ ^[1-9][0-9]*([,\-][1-9][0-9]*)*$ ]]; then + exit_code=${HYBRID_fatal_missing_feature} Print_Fatal_And_Exit "-t num option not yet implemented!" + elif [[ $2 =~ ^[[:alpha:]*] ]]; then + exit_code=${HYBRID_fatal_missing_feature} Print_Fatal_And_Exit "-t str option not yet implemented!" + else + __static__Print_Option_Specification_Error_And_Exit $2 + fi + else + __static__Print_List_Of_Tests + exit ${HYBRID_success_exit_code} + fi + shift 2 ;; + -k | --keep-tests-folder ) + HYBRIDT_clean_test_folder='FALSE' + shift ;; + * ) + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + "Invalid option \"$1\" specified! Use the \"--help\" option to get further information." + ;; + esac + done +} + +function __static__Print_Helper() +{ + local helper_color normal_color default + helper_color='\e[92m' + normal_color='\e[96m' + default_color='\e[0m' + printf "\n${helper_color} Execute tests with the following optional arguments:${default_color}\n\n" + __static__Add_Option_To_Helper "-r | --report-level"\ + "Verbosity of test report (default value ${HYBRIDT_report_level})."\ + "To be chosen among"\ + " 0 = binary, 1 = summary, 2 = short, 3 = detailed." + __static__Add_Option_To_Helper "-t | --run-tests"\ + "Specify which tests have to be run. A comma-separated"\ + "list of numbers and/or of intervals (e.g. 1,3-5) or"\ + "a string (e.g. 'help*') has to be specified. The string"\ + "is matched against test names using bash regular globbing."\ + "If no value is specified the available tests list is printed."\ + "Without this option all existing tests are run." + __static__Add_Option_To_Helper "-k | --keep-tests-folder"\ + "Leave all the created folders and files in the test folder." + Print_Warning\ + " Values from options must be separated by space and short options cannot be combined.\n" +} + +function __static__Add_Option_To_Helper() +{ + local name description length_option indentation + length_option=24 + indentation=' ' + name="$1" + description="$2" + shift 2 + printf "${normal_color}%s${default_color} -> ${helper_color}%s\n"\ + "$(printf "%s%-${length_option}s" "${indentation}" "${name}")"\ + "${description}" + while [[ $# -gt 0 ]]; do + printf "%s %s\n"\ + "$(printf "%s%${length_option}s" "${indentation}" "")"\ + "$1" + shift + done + printf "${default_color}\n" +} + +function __static__Print_List_Of_Tests() +{ + exit_code=${HYBRID_fatal_missing_feature} Print_Fatal_And_Exit "${FUNCNAME} option not yet implemented!" +} + +function __static__Print_Option_Specification_Error_And_Exit() +{ + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + "The value of the option \"$1\" was not correctly specified"\ + " (either forgotten or invalid)!" +} + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/tests/functional_tests b/tests/functional_tests new file mode 100755 index 0000000..8b2cbed --- /dev/null +++ b/tests/functional_tests @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# +# This files contains a set of system/functional tests for the main code. +# +# Basically the hybrid handler is run in some given configuration +# and with some command line options. The external software is mocked +# in a reasonable way (each workflow block will behave as the real +# software w.r.t. input and output) and the expected outcome is validated. +# + +function Main() +{ + Setup_Initial_And_Final_Output_Space + Define_Tests_Global_Variables + Source_Needed_Files + Print_Helper_And_Exit_If_Requested "$@" + Check_System_Requirements + Define_Available_Tests + Parse_Command_Line_Options "$@" + Prepare_Test_Environment + Run_Tests + Print_Tests_Report + Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So +} + +function Setup_Initial_And_Final_Output_Space() +{ + printf '\n' + trap 'printf "\n"' EXIT +} + +function Define_Tests_Global_Variables () +{ + readonly HYBRIDT_repository_top_level_path=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../" &> /dev/null && pwd) + readonly HYBRIDT_command=${HYBRIDT_repository_top_level_path}/Hybrid-handler + readonly HYBRIDT_tests_folder=${HYBRIDT_repository_top_level_path}/tests + readonly HYBRIDT_folder_to_run_tests=${HYBRIDT_tests_folder}/run_tests + readonly HYBRIDT_log_file=${HYBRIDT_folder_to_run_tests}/$(basename ${BASH_SOURCE[0]}).log + readonly listOfAuxiliaryFilesAndFolders=( "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_log_file}" ) + HYBRIDT_clean_test_folder='TRUE' + HYBRIDT_report_level=3 # Report level: 0 = binary, 1 = summary, 2 = short, 3 = detailed + HYBRIDT_tests_run=0 + HYBRIDT_tests_passed=0 + HYBRIDT_tests_failed=0 + HYBRIDT_which_tests_failed=() + HYBRIDT_available_tests=() +} + +function Source_Needed_Files() +{ + source ${HYBRIDT_repository_top_level_path}/bash/error_codes.bash || exit 1 + local -r files_to_be_sourced=( + "${HYBRIDT_repository_top_level_path}/bash/logger.bash" + "${HYBRIDT_repository_top_level_path}/bash/system_requirements.bash" + "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" + "${HYBRIDT_tests_folder}/command_line_parser_for_tests.bash" + ) + local file + for file in "${files_to_be_sourced[@]}"; do + source "${file}" || exit ${HYBRID_fatal_builtin} + done +} + +function Print_Helper_And_Exit_If_Requested() +{ + if Element_In_Array_Matches '^-(h|-help)$' "$@"; then + Parse_Command_Line_Options '--help' + fi +} + +function Define_Available_Tests() +{ + HYBRIDT_available_tests=( + 'help-'{1..3} + 'version-'{1,2} + ) +} + +function Prepare_Test_Environment() +{ + local postfix + postfix=$(date +'%Y-%m-%d_%H%M%S') + if [[ -d "${HYBRIDT_folder_to_run_tests}" ]]; then + Print_Warning "Found \"${HYBRIDT_folder_to_run_tests}\", renaming it!" + mv "${HYBRIDT_folder_to_run_tests}"\ + "${HYBRIDT_folder_to_run_tests}_${postfix}" || exit ${HYBRID_fatal_builtin} + fi +} + +function Run_Tests() +{ + if [[ ${HYBRIDT_report_level} -eq 3 ]]; then + Print_Info "\nRunning ${#HYBRIDT_available_tests[@]} test(s):\n" + fi + local test_name + for test_name in "${HYBRIDT_available_tests[@]}"; do + Print_Error "Test '${test_name}' skipped!" + continue + Make_Test_Preliminary_Operations "${test_name}" + Run_Test "${test_name}" + Clean_Tests_Environment_For_Following_Test "${test_name}" + done +} + +function Print_Tests_Report() +{ + Print_Error "${FUNCNAME} not implemented yet. Skipping it." +} + +function Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So() +{ + Print_Error "${FUNCNAME} not implemented yet. Skipping it." +} + +Main "$@" From c29322ca5e61fc95b85ea04abc6152a7383802d5 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 15 May 2023 11:56:15 +0200 Subject: [PATCH 017/549] Implement tests parser missing functionality It should now be possible to both get displayed the list of available tests as well as select some of them either by number or by string (as we are in the shell I used globbing and not regex for the moment). --- tests/command_line_parser_for_tests.bash | 74 ++++++++++++++++++++---- tests/functional_tests | 8 +-- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 7079fb1..1a406a1 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -9,6 +9,9 @@ function Parse_Command_Line_Options() { + # This function needs the array of tests not sparse => enforce it + HYBRIDT_tests_to_be_run=( "${HYBRIDT_tests_to_be_run[@]}" ) + while [[ $# -gt 0 ]]; do case $1 in -h | --help ) @@ -19,17 +22,17 @@ function Parse_Command_Line_Options() if [[ $2 =~ ^[0-3]$ ]]; then readonly HYBRIDT_report_level=$2 else - __static__Print_Option_Specification_Error_And_Exit $1 + __static__Print_Option_Specification_Error_And_Exit "$1" fi shift 2 ;; -t | --run-tests ) if [[ ! $2 =~ ^- && "$2" != '' ]]; then if [[ $2 =~ ^[1-9][0-9]*([,\-][1-9][0-9]*)*$ ]]; then - exit_code=${HYBRID_fatal_missing_feature} Print_Fatal_And_Exit "-t num option not yet implemented!" - elif [[ $2 =~ ^[[:alpha:]*] ]]; then - exit_code=${HYBRID_fatal_missing_feature} Print_Fatal_And_Exit "-t str option not yet implemented!" + __static__Set_Tests_To_Be_Run_Using_Numbers "$2" + elif [[ $2 =~ ^[[:alpha:]*?] ]]; then + __static__Set_Tests_To_Be_Run_Using_Globbing "$2" else - __static__Print_Option_Specification_Error_And_Exit $2 + __static__Print_Option_Specification_Error_And_Exit "$1" fi else __static__Print_List_Of_Tests @@ -53,7 +56,7 @@ function __static__Print_Helper() helper_color='\e[92m' normal_color='\e[96m' default_color='\e[0m' - printf "\n${helper_color} Execute tests with the following optional arguments:${default_color}\n\n" + printf "${helper_color} Execute tests with the following optional arguments:${default_color}\n\n" __static__Add_Option_To_Helper "-r | --report-level"\ "Verbosity of test report (default value ${HYBRIDT_report_level})."\ "To be chosen among"\ @@ -68,7 +71,7 @@ function __static__Print_Helper() __static__Add_Option_To_Helper "-k | --keep-tests-folder"\ "Leave all the created folders and files in the test folder." Print_Warning\ - " Values from options must be separated by space and short options cannot be combined.\n" + " Values from options must be separated by space and short options cannot be combined." } function __static__Add_Option_To_Helper() @@ -91,16 +94,67 @@ function __static__Add_Option_To_Helper() printf "${default_color}\n" } +function __static__Set_Tests_To_Be_Run_Using_Numbers() +{ + local selection_string numeric_list number selected_tests + selection_string=$1 + numeric_list=( + $(awk\ + 'BEGIN{RS=","} + /\-/{split($0, res, "-"); for(i=res[1]; i<=res[2]; i++){printf "%d\n", i}; next} + {printf "%d\n", $0}' <<< "${selection_string}") + ) + Print_Debug "Selected tests indices: ( ${numeric_list[*]} )" + selected_tests=() + for number in "${numeric_list[@]}"; do + # The user selects human-friendly numbers (1,2,...), here go back to array indices + (( number-- )) + if [[ ${number} -lt ${#HYBRIDT_tests_to_be_run[@]} ]]; then + selected_tests+=( "${HYBRIDT_tests_to_be_run[number]}" ) + else + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + "Some specified test number within \"$1\" is not valid! Use"\ + "the '-t' option without value to get a list of available tests." + fi + done + HYBRIDT_tests_to_be_run=( "${selected_tests[@]}" ) + Print_Debug "Selected tests: ( ${HYBRIDT_tests_to_be_run[*]} )" +} + +function __static__Set_Tests_To_Be_Run_Using_Globbing() +{ + local selection_string test_name selected_tests + selection_string=$1 + selected_tests=() + for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do + # In this if-clause, no quotes must be used -> globbing comparison! + if [[ ${test_name} = ${selection_string} ]]; then + selected_tests+=( "${test_name}" ) + fi + done + HYBRIDT_tests_to_be_run=( "${selected_tests[@]}" ) + if [[ ${#HYBRIDT_tests_to_be_run[@]} -eq 0 ]]; then + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + "No test name found matching \"$1\" globbing pattern! Use"\ + "the '-t' option without value to get a list of available tests." + fi + Print_Debug "Selected tests: ( ${HYBRIDT_tests_to_be_run[*]} )" +} + function __static__Print_List_Of_Tests() { - exit_code=${HYBRID_fatal_missing_feature} Print_Fatal_And_Exit "${FUNCNAME} option not yet implemented!" + local index indentation width_of_list + printf " \e[96mList of available tests:\e[0m\n\n" + width_of_list=$(( $(tput cols) * 4 / 5 )) + for ((index = 0; index < ${#HYBRIDT_tests_to_be_run[@]}; index++)); do + printf '%3d) %s\n' "$(( index+1 ))" "${HYBRIDT_tests_to_be_run[index]}" + done | column -c "${width_of_list}" } function __static__Print_Option_Specification_Error_And_Exit() { exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ - "The value of the option \"$1\" was not correctly specified"\ - " (either forgotten or invalid)!" + "The value of the option \"$1\" was not correctly specified (either forgotten or invalid)!" } diff --git a/tests/functional_tests b/tests/functional_tests index 8b2cbed..406612e 100755 --- a/tests/functional_tests +++ b/tests/functional_tests @@ -53,7 +53,7 @@ function Define_Tests_Global_Variables () HYBRIDT_tests_passed=0 HYBRIDT_tests_failed=0 HYBRIDT_which_tests_failed=() - HYBRIDT_available_tests=() + HYBRIDT_tests_to_be_run=() } function Source_Needed_Files() @@ -80,7 +80,7 @@ function Print_Helper_And_Exit_If_Requested() function Define_Available_Tests() { - HYBRIDT_available_tests=( + HYBRIDT_tests_to_be_run=( 'help-'{1..3} 'version-'{1,2} ) @@ -100,10 +100,10 @@ function Prepare_Test_Environment() function Run_Tests() { if [[ ${HYBRIDT_report_level} -eq 3 ]]; then - Print_Info "\nRunning ${#HYBRIDT_available_tests[@]} test(s):\n" + Print_Info "\nRunning ${#HYBRIDT_tests_to_be_run[@]} test(s):\n" fi local test_name - for test_name in "${HYBRIDT_available_tests[@]}"; do + for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do Print_Error "Test '${test_name}' skipped!" continue Make_Test_Preliminary_Operations "${test_name}" From 396de636ca517ec7e1cae60c60998cb3a1bea640 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 15 May 2023 15:55:54 +0200 Subject: [PATCH 018/549] Implement missing test driver functions The test driver is now ready for generalization. I implemented the report and clean-up functionality and commented out not (yet) existing functions, which will have to be provided on a per-case basis. As next step, such an interface should be implemented and this should guarantee that all functions invoked by the driver exist. It should be possible to implement a function that takes the name of the function to be invoked as first argument and possible further arguments afterwards. Using this in the tests driver it would be clear which function has to be provided by the interface. --- bash/utility_functions.bash | 42 ++++++++++++++ tests/functional_tests | 107 +++++++++++++++++++++++++++++++++--- 2 files changed, 141 insertions(+), 8 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 37c513b..a4e918a 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -25,6 +25,48 @@ function Element_In_Array_Matches() return 1 } +function Print_Line_of_Equals() +{ + local length indentation prefix postfix + length="$1" + indentation="${2-}" # Input arg. or empty string + prefix="${3-}" # Input arg. or empty string + postfix="${4-\n}" # Input arg. or endline + printf "${prefix}${indentation}" + for ((i = 0; i < ${length}; i++)); do + printf '=' + done + printf "${postfix}" +} + +function Print_Centered_Line() +{ + local input_string output_total_width indentation padding_character\ + postfix real_length padding_utility + input_string="$1" + output_total_width="${2:-$(tput cols)}" # Input arg. or full width of terminal + indentation="${3-}" # Input arg. or empty string + padding_character="${4:- }" # Input arg. or single space + postfix="${5-\n}" # Input arg. or endline + # Determine length of input at net of formatting codes (color, face) + real_length=$(printf '%s' "${input_string}" | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]//g" | wc -c) + if (( output_total_width - 2 - real_length < 0 )); then + Print_Fatal_And_Exit "Error in \"${FUNCNAME}\": specify larger total width!" + fi + # In the following we build a very long string of padding characters that + # will be later truncated when printing the output string. Very long is + # here 500 characters. The * in a string format descriptor of printf means + # that the number to be used there is passed to printf as argument. + padding_utility="$(printf '%0.1s' "${padding_character}"{1..500})" + printf "${indentation}%0.*s %s %0.*s${postfix}"\ + "$(( (output_total_width - 2 - real_length)/2 ))"\ + "${padding_utility}"\ + "${input_string}"\ + "$(( (output_total_width - 2 - real_length)/2 ))"\ + "${padding_utility}" +} + + function Make_Functions_Defined_In_This_File_Readonly() { # Here we assume all functions are defined with the same stile, diff --git a/tests/functional_tests b/tests/functional_tests index 406612e..4edfcad 100755 --- a/tests/functional_tests +++ b/tests/functional_tests @@ -46,7 +46,7 @@ function Define_Tests_Global_Variables () readonly HYBRIDT_tests_folder=${HYBRIDT_repository_top_level_path}/tests readonly HYBRIDT_folder_to_run_tests=${HYBRIDT_tests_folder}/run_tests readonly HYBRIDT_log_file=${HYBRIDT_folder_to_run_tests}/$(basename ${BASH_SOURCE[0]}).log - readonly listOfAuxiliaryFilesAndFolders=( "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_log_file}" ) + readonly HYBRIDT_auxiliary_files_and_folders=( "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_log_file}" ) HYBRIDT_clean_test_folder='TRUE' HYBRIDT_report_level=3 # Report level: 0 = binary, 1 = summary, 2 = short, 3 = detailed HYBRIDT_tests_run=0 @@ -95,6 +95,7 @@ function Prepare_Test_Environment() mv "${HYBRIDT_folder_to_run_tests}"\ "${HYBRIDT_folder_to_run_tests}_${postfix}" || exit ${HYBRID_fatal_builtin} fi + mkdir "${HYBRIDT_folder_to_run_tests}" || exit ${HYBRID_fatal_builtin} } function Run_Tests() @@ -104,22 +105,112 @@ function Run_Tests() fi local test_name for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do - Print_Error "Test '${test_name}' skipped!" - continue - Make_Test_Preliminary_Operations "${test_name}" - Run_Test "${test_name}" - Clean_Tests_Environment_For_Following_Test "${test_name}" + #Make_Test_Preliminary_Operations "${test_name}" + Announce_Running_Test "${test_name}" + #Run_Test "${test_name}" + false + Inspect_Test_Outcome $? "${test_name}" + #Clean_Tests_Environment_For_Following_Test "${test_name}" done } +function Announce_Running_Test() +{ + local test_name padded_name + test_name=$1; shift + (( HYBRIDT_tests_run++ )) + if [[ ${HYBRIDT_report_level} -eq 3 ]]; then + printf -v padded_name "%-60s" "__${test_name}$(printf '\e[94m')_" + padded_name="${padded_name// /.}" + printf ' %+2s/%-2s\e[96m%s'\ + ${HYBRIDT_tests_run} ${#HYBRIDT_tests_to_be_run[@]} "${padded_name//_/ }" + fi +} + +function Inspect_Test_Outcome() +{ + local test_exit_code test_name + test_exit_code=$1 + test_name=$2 + if [[ ${test_exit_code} -eq 0 ]]; then + (( HYBRIDT_tests_passed++ )) + if [[ ${HYBRIDT_report_level} -eq 3 ]]; then + printf " \e[92mpassed\e[0m\n" + fi + else + (( HYBRIDT_tests_failed++ )) + HYBRIDT_which_tests_failed+=( "${test_name}" ) + if [[ ${HYBRIDT_report_level} -eq 3 ]]; then + printf " \e[91mfailed\e[0m\n" + fi + fi +} + function Print_Tests_Report() { - Print_Error "${FUNCNAME} not implemented yet. Skipping it." + if [[ ${HYBRIDT_report_level} -ge 1 ]]; then + local indentation left_margin test_name_string_length\ + index separator_length passed_string failed_string + indentation=' ' + left_margin=' ' + test_name_string_length=$(printf '%s\n' "${testsToBeRun[@]}" | wc -L) + if [[ ${test_name_string_length} -lt 25 ]]; then # Minimum length + test_name_string_length=25 + fi + if (( test_name_string_length % 2 == 1 )); then # Aesthetics + (( test_name_string_length+=1 )) + fi + separator_length=$(( test_name_string_length + 3 + 2 * ${#left_margin} )) + passed_string="$(printf "Run %d test(s): %2d passed" ${HYBRIDT_tests_run} ${HYBRIDT_tests_passed})" + failed_string="$(printf " ${HYBRIDT_tests_run//?/ } %2d failed" ${HYBRIDT_tests_failed})" + Print_Line_of_Equals "${separator_length}" "${indentation}" '\n\e[96m' '\e[0m\n' + Print_Centered_Line "${passed_string}" ${separator_length} "${indentation}" + Print_Centered_Line "${failed_string}" ${separator_length} "${indentation}" + Print_Line_of_Equals "${separator_length}" "${indentation}" '\e[96m' '\e[0m\n' + fi + if [[ ${HYBRIDT_report_level} -ge 2 ]]; then + local name percentage + percentage=$(awk '{printf "%.0f%%", 100*$1/$2}' <<< "${HYBRIDT_tests_passed} ${HYBRIDT_tests_run}") + Print_Centered_Line "${percentage} of tests passed!" ${separator_length} "${indentation}" + Print_Line_of_Equals "${separator_length}" "${indentation}" '\e[96m' '\e[0m\n' + if [[ ${testsFailed} -ne 0 ]]; then + printf "${indentation}${left_margin}The following tests failed:\n" + for name in "${HYBRIDT_which_tests_failed[@]}"; do + printf "${indentation}${left_margin} ${name}\n " + done + Print_Line_of_Equals "${separator_length}" "${indentation}" '\e[96m' '\e[0m\n' + fi + fi + if [[ ${HYBRIDT_tests_failed} -ne 0 ]]; then + Print_Error "\n${HYBRIDT_tests_failed} failures were detected! Not deleting log file!" + else + Print_Info "\nAll tests passed!" + fi } function Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So() { - Print_Error "${FUNCNAME} not implemented yet. Skipping it." + if [[ ${HYBRIDT_clean_test_folder} = 'TRUE' && ${HYBRIDT_tests_failed} -eq 0 ]]; then + local global_path label + for global_path in "${HYBRIDT_auxiliary_files_and_folders[@]}"; do + if [[ -d "${global_path}" ]]; then + label='directory' + elif [[ -L "${global_path}" ]]; then + label='symlink' + elif [[ -f "${global_path}" ]]; then + label='file' + elif [[ ! -e "${global_path}" ]]; then + continue + else + Print_Internal_And_Exit "Error in \"${FUNCNAME}\"."\ + "\"${global_path}\" seems to neither be a file nor directory, leaving it!" + fi + Print_Info "Removing ${label} \"${global_path}\"" + if [[ -e "${global_path}" ]]; then # Redundant but safer + rm -r "${global_path}" + fi + done + fi } Main "$@" From a9cda2f6a29d993e119f28a301cec349d342f9ef Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 15 May 2023 17:25:05 +0200 Subject: [PATCH 019/549] Implement interface mechanism to tests driver The main file has now become a neutral driver that can run different tests type to be selected via a first command line positional option. Next, the main script will be renamed and a skeleton for functional tests will be implemented. --- tests/command_line_parser_for_tests.bash | 33 +++++++++++++++--- tests/{functional_tests => tests_runner} | 43 ++++++++++++++++++------ 2 files changed, 60 insertions(+), 16 deletions(-) rename tests/{functional_tests => tests_runner} (79%) diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 1a406a1..02aeab0 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -7,6 +7,24 @@ # #=================================================== +function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() +{ + local suite_name code_filename + suite_name="$1" + if [[ ! ${suite_name} =~ ^(functional|unit)$ ]]; then + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + "Invalid tests type \"${suite_name}\". Valid values: \"functional\", \"unit\"."\ + "Use the '--help' option to get more information." + fi + code_filename="${suite_name}_tests_specific_code.bash" + if [[ ! -f "${code_filename}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + "File \"${code_filename}\" not found." + else + source "${code_filename}" || exit ${HYBRID_fatal_builtin} + fi +} + function Parse_Command_Line_Options() { # This function needs the array of tests not sparse => enforce it @@ -52,11 +70,16 @@ function Parse_Command_Line_Options() function __static__Print_Helper() { - local helper_color normal_color default - helper_color='\e[92m' - normal_color='\e[96m' + local text_color options_color default + text_color='\e[38;5;26m' + emph_color='\e[93m' + options_color='\e[96m' default_color='\e[0m' - printf "${helper_color} Execute tests with the following optional arguments:${default_color}\n\n" + printf " USAGE: ${options_color}tests_runner [-h|--help] [...]${default_color}\n\n" + printf " ${emph_color}Name of available types of tests:${default_color}\n\n" + __static__Add_Option_To_Helper "functional" "Tests of the handler as whole script." + __static__Add_Option_To_Helper "unit" "Unit tests of the codebase." + printf " ${emph_color}Execute tests with the following optional arguments:${default_color}\n\n" __static__Add_Option_To_Helper "-r | --report-level"\ "Verbosity of test report (default value ${HYBRIDT_report_level})."\ "To be chosen among"\ @@ -82,7 +105,7 @@ function __static__Add_Option_To_Helper() name="$1" description="$2" shift 2 - printf "${normal_color}%s${default_color} -> ${helper_color}%s\n"\ + printf "${options_color}%s${default_color} -> ${text_color}%s\n"\ "$(printf "%s%-${length_option}s" "${indentation}" "${name}")"\ "${description}" while [[ $# -gt 0 ]]; do diff --git a/tests/functional_tests b/tests/tests_runner similarity index 79% rename from tests/functional_tests rename to tests/tests_runner index 4edfcad..eb0c2f1 100755 --- a/tests/functional_tests +++ b/tests/tests_runner @@ -10,12 +10,17 @@ #=================================================== # -# This files contains a set of system/functional tests for the main code. +# This files is meant to be a neutral tests driver to be used to run a given +# suite of tests. From the abstract perspective, the machinery (framework) +# can be implemented in an unaware way and this is done here. The few functions +# that should be specialized and serve as customization point are those to +# run the test, plus optional preliminary and subsequent operations to the test. # -# Basically the hybrid handler is run in some given configuration -# and with some command line options. The external software is mocked -# in a reasonable way (each workflow block will behave as the real -# software w.r.t. input and output) and the expected outcome is validated. +# The functions that shall be provided by "external" code can be easily located +# in this file, as they are called by the 'Call_through_interface' function. +# The driver has a first command line positional option that selects which suite +# is considered. The parser is then sourcing the specific code and then the code +# here is ready to be executed. # function Main() @@ -24,15 +29,32 @@ function Main() Define_Tests_Global_Variables Source_Needed_Files Print_Helper_And_Exit_If_Requested "$@" + Parse_Tests_Suite_Parameter_And_Source_Specific_Code "$1" Check_System_Requirements - Define_Available_Tests - Parse_Command_Line_Options "$@" + Call_through_interface 'Define_Available_Tests' + Parse_Command_Line_Options "${@:2}" Prepare_Test_Environment Run_Tests Print_Tests_Report Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So } +function Call_through_interface() +{ + local name_of_the_function + name_of_the_function=$1; shift + if [[ "$(type -t ${name_of_the_function})" = 'function' ]]; then + ${name_of_the_function} "$@" || return $? + # Return value propagates automatically since a function returns the last exit code. + # However, when exit on error behavior is active, the script would terminate here if + # the function returns non-zero exit code and, instead we want this propagate up! + else + exit_code=${HYBRID_fatal_missing_feature} Print_Fatal_And_Exit\ + "Function \"${name_of_the_function}\" not found!\n"\ + "Please provide an implementation following the in-code documentation." + fi +} + function Setup_Initial_And_Final_Output_Space() { printf '\n' @@ -105,12 +127,11 @@ function Run_Tests() fi local test_name for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do - #Make_Test_Preliminary_Operations "${test_name}" + Call_through_interface 'Make_Test_Preliminary_Operations' "${test_name}" Announce_Running_Test "${test_name}" - #Run_Test "${test_name}" - false + Call_through_interface 'Run_Test' "${test_name}" Inspect_Test_Outcome $? "${test_name}" - #Clean_Tests_Environment_For_Following_Test "${test_name}" + Call_through_interface 'Clean_Tests_Environment_For_Following_Test' "${test_name}" done } From 817bad09747ea33ce2a52fde36f8ca133c0ea8d8 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 15 May 2023 17:36:47 +0200 Subject: [PATCH 020/549] Add empty code structure for functional tests At the moment no real test is run and all are failed by hand. --- tests/functional_tests_specific_code.bash | 31 +++++++++++++++++++++++ tests/tests_runner | 12 ++------- 2 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 tests/functional_tests_specific_code.bash diff --git a/tests/functional_tests_specific_code.bash b/tests/functional_tests_specific_code.bash new file mode 100644 index 0000000..dd7618b --- /dev/null +++ b/tests/functional_tests_specific_code.bash @@ -0,0 +1,31 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Define_Available_Tests() +{ + HYBRIDT_tests_to_be_run=( + 'help-'{1..3} + 'version-'{1,2} + ) +} + +function Make_Test_Preliminary_Operations() +{ + : # No-op for the moment +} + +function Run_Test() +{ + false # Fail by definition for the moment +} + +function Clean_Tests_Environment_For_Following_Test() +{ + : # No-op for the moment +} diff --git a/tests/tests_runner b/tests/tests_runner index eb0c2f1..b94632d 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -49,8 +49,8 @@ function Call_through_interface() # However, when exit on error behavior is active, the script would terminate here if # the function returns non-zero exit code and, instead we want this propagate up! else - exit_code=${HYBRID_fatal_missing_feature} Print_Fatal_And_Exit\ - "Function \"${name_of_the_function}\" not found!\n"\ + exit_code=${HYBRID_fatal_missing_feature} Print_Internal_And_Exit\ + "\nFunction \"${name_of_the_function}\" not found!"\ "Please provide an implementation following the in-code documentation." fi } @@ -100,14 +100,6 @@ function Print_Helper_And_Exit_If_Requested() fi } -function Define_Available_Tests() -{ - HYBRIDT_tests_to_be_run=( - 'help-'{1..3} - 'version-'{1,2} - ) -} - function Prepare_Test_Environment() { local postfix From 25379a498ed97f7615097ae75b19826f04edb7c4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 15 May 2023 17:40:59 +0200 Subject: [PATCH 021/549] Ignore all auxiliary tests file possibly from previous runs --- tests/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/.gitignore b/tests/.gitignore index cab8e90..44045ff 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1 @@ -run_tests/ +run_tests*/ From f81089b0dce416ca65657cb5a9a5d12cc6289ef9 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Tue, 16 May 2023 16:25:12 +0200 Subject: [PATCH 022/549] Add Afterburner blackbox --- tests/mocks/smash_afterburner_black-box.py | 197 +++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100755 tests/mocks/smash_afterburner_black-box.py diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py new file mode 100755 index 0000000..005341d --- /dev/null +++ b/tests/mocks/smash_afterburner_black-box.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +import re +import time + +def check_config(valid_config): + if args.i is None: + args.i = "./config.yaml" + if not os.path.exists(args.i): + print(fatal_error+"The configuration file was expected at './config.yaml', but the file does not exist.") + sys.exit() + if not valid_config: + print(fatal_error+"Validation of SMASH input failed.") + sys.exit() + return + +def print_terminal_start(): + # generated with https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 + Terminal_Out="""###########################################################################################################\n +_________ _____ _____ _________ ___ ___\n + / _____/ / \ / _ \ / _____// | \ \n + \_____ \ / \ / \ / /_\ \ \_____ \/ ~ \\n + / \/ Y \/ | \/ \ Y /\n + /_______ /\____|__ /\____|__ /_______ /\___|_ /\n + \/ \/ \/ \/ \/\n + \n ___ ___ ___. .__ .___ _____________________ _____ + ____ ______ _ __ / | \ ___.__.\_ |_________|__| __| _/ \_ _____/\______ \ / _ \\n + / \_/ __ \ \/ \/ / ______ / ~ < | | | __ \_ __ \ |/ __ | ______ | __)_ | _/ / /_\ \\n + | | \ ___/\ / /_____/ \ Y /\___ | | \_\ \ | \/ / /_/ | /_____/ | \ | | \/ | \\n + |___| /\___ >\/\_/ \___|_ / / ____| |___ /__| |__\____ | /_______ / |____|_ /\____|__ /\n + \/ \/ \/ \/ \/ \/ \/ \/ \/\n + \n _____ _____________________________________________________ ____ _____________ _______ _____________________ __________.____ _____ _________ ____ __. __________\n ________ ____ ___\n + / _ \ \_ _____/\__ ___/\_ _____/\______ \______ \ | \______ \ \ \ \_ _____/\______ \ \______ \ | / _ \ \_ ___ \| |/ _| \______ \\n\_____ \ \ \/ / + / /_\ \ | __) | | | __)_ | _/| | _/ | /| _/ / | \ | __)_ | _/ | | _/ | / /_\ \/ \ \/| < ______ | | _/ /\n | \ \ / + / | \| \ | | | \ | | \| | \ | / | | \/ | \| \ | | \ | | \ |___/ | \ \___| | \ /_____/ | | \/\n | \/ \ + \____|__ /\___ / |____| /_______ / |____|_ /|______ /______/ |____|_ /\____|__ /_______ / |____|_ / |______ /_______ \____|__ /\______ /____|__ \ |______ /\n\_______ /___/\ \ + \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/\n \/ \_/\n###########################################################################################################\n""" + + + + + + print(Terminal_Out) + print("running SMASH Afterburner") + return + +def create_folders_structure(): + # if no path is given, create folder structure + if args.o is None: + args.o = "./data/" + if not os.path.exists(args.o): + os.mkdir(args.o) + n = 0 + found_folder = False + while not found_folder: + if not os.path.exists(args.o + str(n)): + found_folder = True + else: + n += 1 + args.o += str(n) + # create path if needed + if not os.path.exists(args.o): + os.makedirs(args.o) + # fix format + if args.o[-1] != "/": + args.o += "/" + return + +def check_if_empty_output(): + f_config = args.o + "config.yaml" + if os.path.exists(f_config): + print(fatal_error + "Output directory would get overwritten. Select a different output directory, clean up, or tell SMASH to ignore existing files.") + sys.exit() + else: + # create config file + f = open(f_config, "w") + f.close() + return + +def run_smash(finalize,file_particles_in): + # create smash.lock file + f = open(args.o+file_name_is_running, "w") + extension_pattern = r"\d+" # Matches one or more digits at the end of the filename + regex=re.compile(file_particles_in+extension_pattern) + # Get a list of files in the current directory + files = os.listdir() + # Filter files that match the base name and have only integer extensions + matching_files = [file_in for file_in in files if regex.match(os.getcwd()+"/"+file_in)] + if(len(matching_files)>0): + try: + f_in=open(matching_files[0],"r") + f_in.close() + except: + print(fatal_error+"Sampled particle list could not be opened") + exit() + else: + print(fatal_error+"Sampled particle list could not be found") + exit() + f.close() + # open unfinished particle files + particles_out_oscar = open(file_particles_out_oscar+name_unfinished, "w") + particles_out_bin = open(file_particles_out_bin+name_unfinished, "w") + # run the black box + for ts in range(11): + print("running t = {} fm".format(ts)) + time.sleep(0.5) + particles_out_oscar.close() + particles_out_bin.close() + + if finalize: + finish() + else: + print(fatal_error+"crash") + return + +def finish(): + # rename output by removing the .unfinished file ending + if os.path.exists(file_particles_out_oscar+name_unfinished): + os.rename(file_particles_out_oscar+name_unfinished, file_particles_out_oscar) + else: + print("somehow the output (.oscar) file was not properly written") + sys.exit() + + if os.path.exists(file_particles_out_bin+name_unfinished): + os.rename(file_particles_out_bin+name_unfinished, file_particles_out_bin) + else: + print("somehow the output file (.bin) was not properly written") + sys.exit() + + # remove smash.lock file + os.remove(args.o+file_name_is_running) + return + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-i", required=False, + help="File to the config.yaml") + parser.add_argument("-o", required=False, + help="Path to the output folder") + parser.add_argument("-c", required=False,action='append',nargs='+', + help="Make changes to config.yaml (this is mocked here)") + parser.add_argument("--fail_with", required=False, + default=None, + choices=["invalid_config", "smash_crashes"], + help="Choose a place where SMASH should fail") + + args = parser.parse_args() + + + smash_finishes = False if args.fail_with == "smash_crashes" else True + + fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " + file_name_is_running = "smash.lock" + name_unfinished = ".unfinished" + name_oscar = ".oscar" + name_bin = ".bin" + name_particles_file = "particle_lists" + + dir_config=False + n_events_config=False + if(args.c == None): + config_is_valid = False + else: + for option in args.c: + #print(option) + if "Modi:" in option[0].split(): + sampler_dir=option[0].split()[5].split("}")[0] + print(sampler_dir) + dir_config=True + elif "Nevents:" in option[0].split(): + try: + n_events=int(option[0].split()[3].strip()) + n_events_config=True + except: + print("Nevents could not be parsed") + + + config_is_valid = False if args.fail_with == "invalid_config" or not dir_config or not n_events_config else True + + # initialize the system + check_config(config_is_valid) + create_folders_structure() + check_if_empty_output() + + #SMASH input sampled particles + spectators + file_particles_in=sampler_dir+"sampling" + # SMASH output participants + spectators + file_particles_out_oscar = args.o+name_particles_file+name_oscar + # special SMASH output for vHLLE. only participants + file_particles_out_bin = args.o+name_particles_file+name_bin + + # smash is now ready to run + print_terminal_start() + run_smash(smash_finishes,file_particles_in) From 0ae90b83a74c17b4d22723d11a3d4bb3a6f444a2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 09:05:17 +0200 Subject: [PATCH 023/549] Add empty skeleton for unit tests plus small improvements --- tests/command_line_parser_for_tests.bash | 1 + tests/tests_runner | 5 +++- tests/unit_tests_specific_code.bash | 30 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests_specific_code.bash diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 02aeab0..728b1c7 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -89,6 +89,7 @@ function __static__Print_Helper() "list of numbers and/or of intervals (e.g. 1,3-5) or"\ "a string (e.g. 'help*') has to be specified. The string"\ "is matched against test names using bash regular globbing."\ + "Remember to quote the argument to avoid shell expansion."\ "If no value is specified the available tests list is printed."\ "Without this option all existing tests are run." __static__Add_Option_To_Helper "-k | --keep-tests-folder"\ diff --git a/tests/tests_runner b/tests/tests_runner index b94632d..4ff16ed 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -183,7 +183,10 @@ function Print_Tests_Report() fi if [[ ${HYBRIDT_report_level} -ge 2 ]]; then local name percentage - percentage=$(awk '{printf "%.0f%%", 100*$1/$2}' <<< "${HYBRIDT_tests_passed} ${HYBRIDT_tests_run}") + percentage=$(awk\ + '{ + if($2!=0) {printf "%.0f%%", 100*$1/$2} else {printf "-- %"} + }' <<< "${HYBRIDT_tests_passed} ${HYBRIDT_tests_run}") Print_Centered_Line "${percentage} of tests passed!" ${separator_length} "${indentation}" Print_Line_of_Equals "${separator_length}" "${indentation}" '\e[96m' '\e[0m\n' if [[ ${testsFailed} -ne 0 ]]; then diff --git a/tests/unit_tests_specific_code.bash b/tests/unit_tests_specific_code.bash new file mode 100644 index 0000000..9725048 --- /dev/null +++ b/tests/unit_tests_specific_code.bash @@ -0,0 +1,30 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Define_Available_Tests() +{ + HYBRIDT_tests_to_be_run=( + # No tests existing yet + ) +} + +function Make_Test_Preliminary_Operations() +{ + : # No-op for the moment +} + +function Run_Test() +{ + false # Fail by definition for the moment +} + +function Clean_Tests_Environment_For_Following_Test() +{ + : # No-op for the moment +} From 2e4f531b488dd0b79a553220cdf9b65197504172 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 17 May 2023 16:33:49 +0200 Subject: [PATCH 024/549] Minor fixes --- tests/mocks/smash_IC_black-box.py | 41 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/tests/mocks/smash_IC_black-box.py b/tests/mocks/smash_IC_black-box.py index 8ce6309..aee4270 100755 --- a/tests/mocks/smash_IC_black-box.py +++ b/tests/mocks/smash_IC_black-box.py @@ -10,10 +10,10 @@ def check_config(valid_config): args.i = "./config.yaml" if not os.path.exists(args.i): print(fatal_error+"The configuration file was expected at './config.yaml', but the file does not exist.") - sys.exit() + sys.exit(1) if not valid_config: print(fatal_error+"Validation of SMASH input failed.") - sys.exit() + sys.exit(1) return def print_terminal_start(): @@ -45,11 +45,11 @@ def create_folders_structure(): args.o += "/" return -def check_if_empty_output(): +def validate_output_folder(): f_config = args.o + "config.yaml" if os.path.exists(f_config): print(fatal_error + "Output directory would get overwritten. Select a different output directory, clean up, or tell SMASH to ignore existing files.") - sys.exit() + sys.exit(1) else: # create config file f = open(f_config, "w") @@ -61,12 +61,12 @@ def run_smash(finalize): f = open(args.o+file_name_is_running, "w") f.close() # open unfinished particle files - particles_out_oscar = open(file_particles_out_oscar+name_unfinished, "w") - particles_out_dat = open(file_particles_out_dat+name_unfinished, "w") + particles_out_oscar = open(SMASH_output_file_with_participants_and_spectators+name_unfinished, "w") + particles_out_dat = open(SMASH_special_output_file_for_vHLLE_with_participants_only+name_unfinished, "w") # run the black box - for ts in range(11): + for ts in range(1,11): print("running t = {} fm".format(ts)) - time.sleep(0.5) + time.sleep(0.1) particles_out_oscar.close() particles_out_dat.close() @@ -74,21 +74,22 @@ def run_smash(finalize): finish() else: print(fatal_error+"crash") + sys.exit(1) return def finish(): # rename output by removing the .unfinished file ending - if os.path.exists(file_particles_out_oscar+name_unfinished): - os.rename(file_particles_out_oscar+name_unfinished, file_particles_out_oscar) + if os.path.exists(SMASH_output_file_with_participants_and_spectators+name_unfinished): + os.rename(SMASH_output_file_with_participants_and_spectators+name_unfinished, SMASH_output_file_with_participants_and_spectators) else: print("somehow the output (.oscar) file was not properly written") - sys.exit() + sys.exit(1) - if os.path.exists(file_particles_out_dat+name_unfinished): - os.rename(file_particles_out_dat+name_unfinished, file_particles_out_dat) + if os.path.exists(SMASH_special_output_file_for_vHLLE_with_participants_only+name_unfinished): + os.rename(SMASH_special_output_file_for_vHLLE_with_participants_only+name_unfinished, SMASH_special_output_file_for_vHLLE_with_participants_only) else: print("somehow the output file (.dat) was not properly written") - sys.exit() + sys.exit(1) # remove smash.lock file os.remove(args.o+file_name_is_running) @@ -109,8 +110,8 @@ def finish(): args = parser.parse_args() - config_is_valid = False if args.fail_with == "invalid_config" else True - smash_finishes = False if args.fail_with == "smash_crashes" else True + config_is_valid = args.fail_with != "invalid_config" + smash_finishes = args.fail_with != "smash_crashes" fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " file_name_is_running = "smash.lock" @@ -122,12 +123,10 @@ def finish(): # initialize the system check_config(config_is_valid) create_folders_structure() - check_if_empty_output() + validate_output_folder() - # SMASH output participants + spectators - file_particles_out_oscar = args.o+name_particles_file+name_oscar - # special SMASH output for vHLLE. only participants - file_particles_out_dat = args.o+name_particles_file+name_dat + SMASH_output_file_with_participants_and_spectators = args.o+name_particles_file+name_oscar + SMASH_special_output_file_for_vHLLE_with_participants_only = args.o+name_particles_file+name_dat # smash is now ready to run print_terminal_start() From 75b66f264fbfed3580e52c5481d07bb7140a445b Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Wed, 17 May 2023 17:11:39 +0200 Subject: [PATCH 025/549] Improve structure and failure handling --- tests/mocks/smash_afterburner_black-box.py | 103 +++++++++++---------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 005341d..08c4b3f 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -11,10 +11,10 @@ def check_config(valid_config): args.i = "./config.yaml" if not os.path.exists(args.i): print(fatal_error+"The configuration file was expected at './config.yaml', but the file does not exist.") - sys.exit() + sys.exit(1) if not valid_config: print(fatal_error+"Validation of SMASH input failed.") - sys.exit() + sys.exit(1) return def print_terminal_start(): @@ -69,42 +69,44 @@ def create_folders_structure(): args.o += "/" return -def check_if_empty_output(): +def ensure_no_output_is_overwritten(): f_config = args.o + "config.yaml" if os.path.exists(f_config): print(fatal_error + "Output directory would get overwritten. Select a different output directory, clean up, or tell SMASH to ignore existing files.") - sys.exit() + sys.exit(1) else: # create config file f = open(f_config, "w") f.close() return -def run_smash(finalize,file_particles_in): +def run_smash(finalize,file_particles_in,sampler_dir): # create smash.lock file f = open(args.o+file_name_is_running, "w") extension_pattern = r"\d+" # Matches one or more digits at the end of the filename regex=re.compile(file_particles_in+extension_pattern) # Get a list of files in the current directory - files = os.listdir() + files = os.listdir(sampler_dir) # Filter files that match the base name and have only integer extensions matching_files = [file_in for file_in in files if regex.match(os.getcwd()+"/"+file_in)] if(len(matching_files)>0): try: - f_in=open(matching_files[0],"r") - f_in.close() + for match in matching_files: + f_in=open(match,"r") + f_in.close() + print("File read") except: print(fatal_error+"Sampled particle list could not be opened") - exit() + sys.exit(1) else: print(fatal_error+"Sampled particle list could not be found") - exit() + sys.exit(1) f.close() # open unfinished particle files particles_out_oscar = open(file_particles_out_oscar+name_unfinished, "w") particles_out_bin = open(file_particles_out_bin+name_unfinished, "w") # run the black box - for ts in range(11): + for ts in range(1): print("running t = {} fm".format(ts)) time.sleep(0.5) particles_out_oscar.close() @@ -114,6 +116,7 @@ def run_smash(finalize,file_particles_in): finish() else: print(fatal_error+"crash") + sys.exit(1) return def finish(): @@ -122,18 +125,46 @@ def finish(): os.rename(file_particles_out_oscar+name_unfinished, file_particles_out_oscar) else: print("somehow the output (.oscar) file was not properly written") - sys.exit() + sys.exit(1) if os.path.exists(file_particles_out_bin+name_unfinished): os.rename(file_particles_out_bin+name_unfinished, file_particles_out_bin) else: print("somehow the output file (.bin) was not properly written") - sys.exit() + sys.exit(1) # remove smash.lock file os.remove(args.o+file_name_is_running) return +def parse_command_line_config_options(args): + + sampler_dir="" + dir_config=False + n_events_config=False + if(args.c == None): + print("No command line options given") + sys.exit(1) + else: + for option in args.c: + if "Modi:" in option[0].split(): + sampler_dir=option[0].split()[5].split("}")[0] + dir_config=True + elif "Nevents:" in option[0].split(): + try: + n_events=int(option[0].split()[3].strip()) + n_events_config=True + except: + print("Nevents could not be parsed") + sys.exit(1) + + if not (dir_config and n_events_config): + print("Necessary command line options not found") + sys.exit(1) + + return sampler_dir + + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-i", required=False, @@ -148,9 +179,15 @@ def finish(): help="Choose a place where SMASH should fail") args = parser.parse_args() - - - smash_finishes = False if args.fail_with == "smash_crashes" else True + + # initialize the system + check_config(args.fail_with != "invalid_config") + create_folders_structure() + ensure_no_output_is_overwritten() + + sampler_dir=parse_command_line_config_options(args) + smash_finishes = args.fail_with != "smash_crashes" + fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " file_name_is_running = "smash.lock" @@ -158,40 +195,12 @@ def finish(): name_oscar = ".oscar" name_bin = ".bin" name_particles_file = "particle_lists" - - dir_config=False - n_events_config=False - if(args.c == None): - config_is_valid = False - else: - for option in args.c: - #print(option) - if "Modi:" in option[0].split(): - sampler_dir=option[0].split()[5].split("}")[0] - print(sampler_dir) - dir_config=True - elif "Nevents:" in option[0].split(): - try: - n_events=int(option[0].split()[3].strip()) - n_events_config=True - except: - print("Nevents could not be parsed") - - - config_is_valid = False if args.fail_with == "invalid_config" or not dir_config or not n_events_config else True - - # initialize the system - check_config(config_is_valid) - create_folders_structure() - check_if_empty_output() - - #SMASH input sampled particles + spectators file_particles_in=sampler_dir+"sampling" - # SMASH output participants + spectators file_particles_out_oscar = args.o+name_particles_file+name_oscar - # special SMASH output for vHLLE. only participants file_particles_out_bin = args.o+name_particles_file+name_bin + + # smash is now ready to run print_terminal_start() - run_smash(smash_finishes,file_particles_in) + run_smash(smash_finishes,file_particles_in,sampler_dir) From d7efce302f46eab09ed86960d2d47192347255a8 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 18:05:50 +0200 Subject: [PATCH 026/549] Refine few error messages and sleep for 1s --- tests/mocks/smash_afterburner_black-box.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 08c4b3f..38db1f9 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -106,7 +106,7 @@ def run_smash(finalize,file_particles_in,sampler_dir): particles_out_oscar = open(file_particles_out_oscar+name_unfinished, "w") particles_out_bin = open(file_particles_out_bin+name_unfinished, "w") # run the black box - for ts in range(1): + for ts in range(2): print("running t = {} fm".format(ts)) time.sleep(0.5) particles_out_oscar.close() @@ -143,7 +143,7 @@ def parse_command_line_config_options(args): dir_config=False n_events_config=False if(args.c == None): - print("No command line options given") + print("No -c command line option was given") sys.exit(1) else: for option in args.c: @@ -159,7 +159,9 @@ def parse_command_line_config_options(args): sys.exit(1) if not (dir_config and n_events_config): - print("Necessary command line options not found") + print("Necessary command line options not found\n" + " -c 'Modi: { List: { File_Directory: } }'\n" + " -c 'General: { Nevents: }'") sys.exit(1) return sampler_dir From 9d3d3713e91fafda7f2c19abe5df5324827613b7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 18:06:40 +0200 Subject: [PATCH 027/549] Ensure that passed directory exists --- tests/mocks/smash_afterburner_black-box.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 38db1f9..4230e79 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -163,6 +163,10 @@ def parse_command_line_config_options(args): " -c 'Modi: { List: { File_Directory: } }'\n" " -c 'General: { Nevents: }'") sys.exit(1) + + if not os.path.isdir(sampler_dir): + print("Directory '{0}' not found".format(sampler_dir)) + sys.exit(1) return sampler_dir From 3dbf903bd79ab0082beb76a1d1310f0613528711 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 18:07:22 +0200 Subject: [PATCH 028/549] Enforce that passed directory has trailing slash --- tests/mocks/smash_afterburner_black-box.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 4230e79..bc5483c 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -168,6 +168,8 @@ def parse_command_line_config_options(args): print("Directory '{0}' not found".format(sampler_dir)) sys.exit(1) + if sampler_dir != "/": + sampler_dir += "/" return sampler_dir From 9f76c46fd9972e360035688ac1213221a12bdd77 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 18:07:51 +0200 Subject: [PATCH 029/549] Fix bug in path where to look for input files --- tests/mocks/smash_afterburner_black-box.py | 58 +++++++++++----------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index bc5483c..1048d8f 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -20,29 +20,29 @@ def check_config(valid_config): def print_terminal_start(): # generated with https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 Terminal_Out="""###########################################################################################################\n -_________ _____ _____ _________ ___ ___\n - / _____/ / \ / _ \ / _____// | \ \n - \_____ \ / \ / \ / /_\ \ \_____ \/ ~ \\n - / \/ Y \/ | \/ \ Y /\n - /_______ /\____|__ /\____|__ /_______ /\___|_ /\n - \/ \/ \/ \/ \/\n - \n ___ ___ ___. .__ .___ _____________________ _____ - ____ ______ _ __ / | \ ___.__.\_ |_________|__| __| _/ \_ _____/\______ \ / _ \\n - / \_/ __ \ \/ \/ / ______ / ~ < | | | __ \_ __ \ |/ __ | ______ | __)_ | _/ / /_\ \\n - | | \ ___/\ / /_____/ \ Y /\___ | | \_\ \ | \/ / /_/ | /_____/ | \ | | \/ | \\n - |___| /\___ >\/\_/ \___|_ / / ____| |___ /__| |__\____ | /_______ / |____|_ /\____|__ /\n - \/ \/ \/ \/ \/ \/ \/ \/ \/\n +_________ _____ _____ _________ ___ ___\n + / _____/ / \ / _ \ / _____// | \ \n + \_____ \ / \ / \ / /_\ \ \_____ \/ ~ \\n + / \/ Y \/ | \/ \ Y /\n + /_______ /\____|__ /\____|__ /_______ /\___|_ /\n + \/ \/ \/ \/ \/\n + \n ___ ___ ___. .__ .___ _____________________ _____ + ____ ______ _ __ / | \ ___.__.\_ |_________|__| __| _/ \_ _____/\______ \ / _ \\n + / \_/ __ \ \/ \/ / ______ / ~ < | | | __ \_ __ \ |/ __ | ______ | __)_ | _/ / /_\ \\n + | | \ ___/\ / /_____/ \ Y /\___ | | \_\ \ | \/ / /_/ | /_____/ | \ | | \/ | \\n + |___| /\___ >\/\_/ \___|_ / / ____| |___ /__| |__\____ | /_______ / |____|_ /\____|__ /\n + \/ \/ \/ \/ \/ \/ \/ \/ \/\n \n _____ _____________________________________________________ ____ _____________ _______ _____________________ __________.____ _____ _________ ____ __. __________\n ________ ____ ___\n / _ \ \_ _____/\__ ___/\_ _____/\______ \______ \ | \______ \ \ \ \_ _____/\______ \ \______ \ | / _ \ \_ ___ \| |/ _| \______ \\n\_____ \ \ \/ / - / /_\ \ | __) | | | __)_ | _/| | _/ | /| _/ / | \ | __)_ | _/ | | _/ | / /_\ \/ \ \/| < ______ | | _/ /\n | \ \ / - / | \| \ | | | \ | | \| | \ | / | | \/ | \| \ | | \ | | \ |___/ | \ \___| | \ /_____/ | | \/\n | \/ \ + / /_\ \ | __) | | | __)_ | _/| | _/ | /| _/ / | \ | __)_ | _/ | | _/ | / /_\ \/ \ \/| < ______ | | _/ /\n | \ \ / + / | \| \ | | | \ | | \| | \ | / | | \/ | \| \ | | \ | | \ |___/ | \ \___| | \ /_____/ | | \/\n | \/ \ \____|__ /\___ / |____| /_______ / |____|_ /|______ /______/ |____|_ /\____|__ /_______ / |____|_ / |______ /_______ \____|__ /\______ /____|__ \ |______ /\n\_______ /___/\ \ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/\n \/ \_/\n###########################################################################################################\n""" - - - - - + + + + + print(Terminal_Out) print("running SMASH Afterburner") return @@ -88,11 +88,11 @@ def run_smash(finalize,file_particles_in,sampler_dir): # Get a list of files in the current directory files = os.listdir(sampler_dir) # Filter files that match the base name and have only integer extensions - matching_files = [file_in for file_in in files if regex.match(os.getcwd()+"/"+file_in)] + matching_files = [file_in for file_in in files if regex.match(sampler_dir+file_in)] if(len(matching_files)>0): try: for match in matching_files: - f_in=open(match,"r") + f_in=open(sampler_dir+match,"r") f_in.close() print("File read") except: @@ -138,7 +138,7 @@ def finish(): return def parse_command_line_config_options(args): - + sampler_dir="" dir_config=False n_events_config=False @@ -157,7 +157,7 @@ def parse_command_line_config_options(args): except: print("Nevents could not be parsed") sys.exit(1) - + if not (dir_config and n_events_config): print("Necessary command line options not found\n" " -c 'Modi: { List: { File_Directory: } }'\n" @@ -167,11 +167,11 @@ def parse_command_line_config_options(args): if not os.path.isdir(sampler_dir): print("Directory '{0}' not found".format(sampler_dir)) sys.exit(1) - + if sampler_dir != "/": sampler_dir += "/" return sampler_dir - + if __name__ == '__main__': parser = argparse.ArgumentParser() @@ -187,15 +187,15 @@ def parse_command_line_config_options(args): help="Choose a place where SMASH should fail") args = parser.parse_args() - + # initialize the system check_config(args.fail_with != "invalid_config") create_folders_structure() ensure_no_output_is_overwritten() - + sampler_dir=parse_command_line_config_options(args) smash_finishes = args.fail_with != "smash_crashes" - + fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " file_name_is_running = "smash.lock" @@ -207,7 +207,7 @@ def parse_command_line_config_options(args): file_particles_out_oscar = args.o+name_particles_file+name_oscar file_particles_out_bin = args.o+name_particles_file+name_bin - + # smash is now ready to run print_terminal_start() From 2263b42a76d89d8835e484aec4a834ed043845df Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 18:19:21 +0200 Subject: [PATCH 030/549] Fix declaration of variables --- tests/mocks/smash_afterburner_black-box.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 1048d8f..8469b0c 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -137,8 +137,7 @@ def finish(): os.remove(args.o+file_name_is_running) return -def parse_command_line_config_options(args): - +def parse_command_line_config_options(): sampler_dir="" dir_config=False n_events_config=False @@ -188,26 +187,23 @@ def parse_command_line_config_options(args): args = parser.parse_args() - # initialize the system - check_config(args.fail_with != "invalid_config") - create_folders_structure() - ensure_no_output_is_overwritten() - - sampler_dir=parse_command_line_config_options(args) smash_finishes = args.fail_with != "smash_crashes" - - fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " file_name_is_running = "smash.lock" name_unfinished = ".unfinished" name_oscar = ".oscar" name_bin = ".bin" name_particles_file = "particle_lists" + sampler_dir=parse_command_line_config_options() file_particles_in=sampler_dir+"sampling" - file_particles_out_oscar = args.o+name_particles_file+name_oscar - file_particles_out_bin = args.o+name_particles_file+name_bin + # initialize the system + check_config(args.fail_with != "invalid_config") + create_folders_structure() + ensure_no_output_is_overwritten() + file_particles_out_oscar = args.o+name_particles_file+name_oscar + file_particles_out_bin = args.o+name_particles_file+name_bin # smash is now ready to run print_terminal_start() From 110c74517ad00f4961742c15070f8c46deaebc8b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 18:19:41 +0200 Subject: [PATCH 031/549] Rename few variables with more expressive names --- tests/mocks/smash_afterburner_black-box.py | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 8469b0c..7c5086b 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -80,11 +80,11 @@ def ensure_no_output_is_overwritten(): f.close() return -def run_smash(finalize,file_particles_in,sampler_dir): +def run_smash(finalize,SMASH_input_file_with_participants_and_spectators,sampler_dir): # create smash.lock file f = open(args.o+file_name_is_running, "w") extension_pattern = r"\d+" # Matches one or more digits at the end of the filename - regex=re.compile(file_particles_in+extension_pattern) + regex=re.compile(SMASH_input_file_with_participants_and_spectators+extension_pattern) # Get a list of files in the current directory files = os.listdir(sampler_dir) # Filter files that match the base name and have only integer extensions @@ -103,8 +103,8 @@ def run_smash(finalize,file_particles_in,sampler_dir): sys.exit(1) f.close() # open unfinished particle files - particles_out_oscar = open(file_particles_out_oscar+name_unfinished, "w") - particles_out_bin = open(file_particles_out_bin+name_unfinished, "w") + particles_out_oscar = open(SMASH_output_file_with_participants_and_spectators+name_unfinished, "w") + particles_out_bin = open(SMASH_special_output_file_for_vHLLE_with_participants_only+name_unfinished, "w") # run the black box for ts in range(2): print("running t = {} fm".format(ts)) @@ -121,14 +121,14 @@ def run_smash(finalize,file_particles_in,sampler_dir): def finish(): # rename output by removing the .unfinished file ending - if os.path.exists(file_particles_out_oscar+name_unfinished): - os.rename(file_particles_out_oscar+name_unfinished, file_particles_out_oscar) + if os.path.exists(SMASH_output_file_with_participants_and_spectators+name_unfinished): + os.rename(SMASH_output_file_with_participants_and_spectators+name_unfinished, SMASH_output_file_with_participants_and_spectators) else: print("somehow the output (.oscar) file was not properly written") sys.exit(1) - if os.path.exists(file_particles_out_bin+name_unfinished): - os.rename(file_particles_out_bin+name_unfinished, file_particles_out_bin) + if os.path.exists(SMASH_special_output_file_for_vHLLE_with_participants_only+name_unfinished): + os.rename(SMASH_special_output_file_for_vHLLE_with_participants_only+name_unfinished, SMASH_special_output_file_for_vHLLE_with_participants_only) else: print("somehow the output file (.bin) was not properly written") sys.exit(1) @@ -195,16 +195,16 @@ def parse_command_line_config_options(): name_bin = ".bin" name_particles_file = "particle_lists" sampler_dir=parse_command_line_config_options() - file_particles_in=sampler_dir+"sampling" # initialize the system check_config(args.fail_with != "invalid_config") create_folders_structure() ensure_no_output_is_overwritten() - file_particles_out_oscar = args.o+name_particles_file+name_oscar - file_particles_out_bin = args.o+name_particles_file+name_bin + SMASH_input_file_with_participants_and_spectators = sampler_dir+"sampling" + SMASH_output_file_with_participants_and_spectators = args.o+name_particles_file+name_oscar + SMASH_special_output_file_for_vHLLE_with_participants_only = args.o+name_particles_file+name_bin # smash is now ready to run print_terminal_start() - run_smash(smash_finishes,file_particles_in,sampler_dir) + run_smash(smash_finishes,SMASH_input_file_with_participants_and_spectators,sampler_dir) From f2990eee23d064396ab02435624a58bf1a27a20a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 18:24:48 +0200 Subject: [PATCH 032/549] Add needed -c options to the helper to make usage clearer --- tests/mocks/smash_afterburner_black-box.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 7c5086b..2bbe48e 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -179,7 +179,10 @@ def parse_command_line_config_options(): parser.add_argument("-o", required=False, help="Path to the output folder") parser.add_argument("-c", required=False,action='append',nargs='+', - help="Make changes to config.yaml (this is mocked here)") + help="Use:" + " -c 'Modi: { List: { File_Directory: } }'" + " -c 'General: { Nevents: }'") + parser.add_argument("--fail_with", required=False, default=None, choices=["invalid_config", "smash_crashes"], From 54a81060f9c60f949a51e390e2dbcbd1064f0a70 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 14:26:16 +0200 Subject: [PATCH 033/549] Make the tests runner fail if any test failed This is important in order to be able to automatically understand if there were failing tests. We use the number of failing tests as exit code and use 255 (maximum exit code possible) if that amount of tests or more failed (one day, maybe...). --- tests/tests_runner | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/tests_runner b/tests/tests_runner index 4ff16ed..7fd3136 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -37,6 +37,7 @@ function Main() Run_Tests Print_Tests_Report Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So + Exit_With_Tests_Outcome_Dependent_Exit_Code } function Call_through_interface() @@ -229,4 +230,16 @@ function Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So() fi } +function Exit_With_Tests_Outcome_Dependent_Exit_Code() +{ + # Make exit code be the number of failed tests, but + # consider that we cannot go beyond 255 (exit code maximum) + if [[ ${HYBRIDT_tests_failed} -gt 255 ]]; then + Print_Warning "More than 255 tests failed. The exit code cannot match the number of failed tests." + exit 255 + else + exit ${HYBRIDT_tests_failed} + fi +} + Main "$@" From f8ef8b40431203282e25d1045a31ebde19135acf Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 11 May 2023 18:51:22 +0200 Subject: [PATCH 034/549] Enable desired shell behaviour in main script --- Hybrid-handler | 4 ++++ bash/logger.bash | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index e45637b..b9abd09 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -19,6 +19,10 @@ # # #----------------------------------------------------------------------------# +# Set stricter bash mode (see CONTRIBUTING for more information) +set -o pipefail -o nounset -o errexit +shopt -s extglob inherit_errexit + source bash/logger.bash diff --git a/bash/logger.bash b/bash/logger.bash index cec3494..7e68cab 100644 --- a/bash/logger.bash +++ b/bash/logger.bash @@ -182,16 +182,16 @@ function __static__Is_Level_On() local loggerLevels loggerLevelsOn level index loggerLevels=( [1]='ERROR' [2]='WARNING' [3]='ATTENTION' [4]='INFO' [5]='DEBUG' [6]='TRACE' ) loggerLevelsOn=() - if [[ ${VERBOSE} =~ ^[0-9]+$ ]]; then + if [[ ${VERBOSE-} =~ ^[0-9]+$ ]]; then loggerLevelsOn=( "${loggerLevels[@]:1:VERBOSE}" ) - elif [[ ${VERBOSE} =~ ^(ERROR|WARNING|ATTENTION|INFO|DEBUG|TRACE)$ ]]; then + elif [[ ${VERBOSE-} =~ ^(ERROR|WARNING|ATTENTION|INFO|DEBUG|TRACE)$ ]]; then for level in "${loggerLevels[@]}"; do loggerLevelsOn+=( "${level}" ) - if [[ ${VERBOSE} = "${level}" ]]; then + if [[ ${VERBOSE-} = "${level}" ]]; then break fi done - elif [[ ${VERBOSE} =~ ^(FATAL|INTERNAL)$ ]]; then + elif [[ ${VERBOSE-} =~ ^(FATAL|INTERNAL)$ ]]; then loggerLevelsOn=( 'FATAL' ) else loggerLevelsOn=( 'FATAL' 'ERROR' 'WARNING' 'ATTENTION' 'INFO' ) From f2d6754232b9af003a2562b64337ffefa3a0bf09 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 May 2023 14:09:27 +0200 Subject: [PATCH 035/549] Add explanation about chosen shell behavior --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fd7f70..403d0ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,24 @@ We encourage you to checkout and use the [**GitHooks**](https://github.com/AxelK Please, refer to their README file for more information. +## Used bash behavior + +After long consideration, it has been decided to use some stricter bash mode. +In particular, the harmless `pipefail`, `nounset` and `extglob` options are enabled, together with the more controversial `errexit` one (and its sibling `inherit_errexit`). +This implies for the developer to be aware of what is going on, but we believe it is worth so. +We are aware that the `errexit` option leads to many corner cases and that there are controversial opinions around. +Our personal approach is to go ahead and use `set -e`, but beware of possible gotchas. +It has useful semantics and, more importantly, can protect from dangerous situations (`cd NotExistingFolder; rm -r *`). +Of course, a proper error handling would be even better. +However, considering that even more inexperienced developers might contribute to the project and miss some error handling (and, by the way, oversight are always possible), we still decided to let the shell abort on error, accepting to deal with all possible downsides this feature has. + +Finally, a short remark about `extglob` option. To motivate why we decided to enable it globally, it is best to quote [Greg's wiki](http://mywiki.wooledge.org/glob): +> **`extglob` changes the way certain characters are parsed. It is necessary to have a newline (not just a semicolon) between `shopt -s extglob` and any subsequent commands to use it.** +> You cannot enable extended globs inside a group command that uses them, because the entire block is parsed before the `shopt` is _evaluated_. +> Note that the typical function body is a _group command_. +> An unpleasant workaround could be to use a _subshell command_ list as the function body. + + ## Bash notation in the codebase The general advice is pretty trivial: **Be consistent with what you find**. From 69f1c257ded800617b527b74c27774c83f0989f2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 May 2023 10:38:29 +0200 Subject: [PATCH 036/549] Rephrase errexit comment to make it less repetitive --- CONTRIBUTING.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 403d0ea..6bd0da3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,12 +47,11 @@ Please, refer to their README file for more information. After long consideration, it has been decided to use some stricter bash mode. In particular, the harmless `pipefail`, `nounset` and `extglob` options are enabled, together with the more controversial `errexit` one (and its sibling `inherit_errexit`). -This implies for the developer to be aware of what is going on, but we believe it is worth so. We are aware that the `errexit` option leads to many corner cases and that there are controversial opinions around. -Our personal approach is to go ahead and use `set -e`, but beware of possible gotchas. -It has useful semantics and, more importantly, can protect from dangerous situations (`cd NotExistingFolder; rm -r *`). +The advantage is its useful semantics and, more importantly, it can protect from dangerous situations (`cd NotExistingFolder; rm -r *`). +But beware of possible gotchas. Of course, a proper error handling would be even better. -However, considering that even more inexperienced developers might contribute to the project and miss some error handling (and, by the way, oversight are always possible), we still decided to let the shell abort on error, accepting to deal with all possible downsides this feature has. +Considering that even more inexperienced developers might contribute to the project and miss some error handling (and, by the way, oversight is always possible), we still decided to let the shell abort on error, accepting to deal with all possible downsides this feature has. Finally, a short remark about `extglob` option. To motivate why we decided to enable it globally, it is best to quote [Greg's wiki](http://mywiki.wooledge.org/glob): > **`extglob` changes the way certain characters are parsed. It is necessary to have a newline (not just a semicolon) between `shopt -s extglob` and any subsequent commands to use it.** From 0578bba16796c444801d00d80c41ad1e91efeea1 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 24 May 2023 15:34:36 +0200 Subject: [PATCH 037/549] Use sub-shell to run each test case --- tests/tests_runner | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/tests_runner b/tests/tests_runner index 7fd3136..28951f9 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -120,11 +120,17 @@ function Run_Tests() fi local test_name for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do - Call_through_interface 'Make_Test_Preliminary_Operations' "${test_name}" - Announce_Running_Test "${test_name}" - Call_through_interface 'Run_Test' "${test_name}" + (( HYBRIDT_tests_run++ )) + # Run in sub-shell to have the same starting environment + ( + Call_through_interface 'Make_Test_Preliminary_Operations' "${test_name}" + Announce_Running_Test "${test_name}" + Call_through_interface 'Run_Test' "${test_name}" + local test_outcome=$? + Call_through_interface 'Clean_Tests_Environment_For_Following_Test' "${test_name}" + exit ${test_outcome} + ) Inspect_Test_Outcome $? "${test_name}" - Call_through_interface 'Clean_Tests_Environment_For_Following_Test' "${test_name}" done } @@ -132,7 +138,6 @@ function Announce_Running_Test() { local test_name padded_name test_name=$1; shift - (( HYBRIDT_tests_run++ )) if [[ ${HYBRIDT_report_level} -eq 3 ]]; then printf -v padded_name "%-60s" "__${test_name}$(printf '\e[94m')_" padded_name="${padded_name// /.}" From 6345841c3a4252a1124bfaa369eb6c17e7532437 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 24 May 2023 15:36:10 +0200 Subject: [PATCH 038/549] Add first unit test and rename tests files --- tests/command_line_parser_for_tests.bash | 2 +- ...ecific_code.bash => functional_tests.bash} | 1 + tests/unit_tests.bash | 49 +++++++++++++++++++ tests/unit_tests_specific_code.bash | 30 ------------ 4 files changed, 51 insertions(+), 31 deletions(-) rename tests/{functional_tests_specific_code.bash => functional_tests.bash} (96%) create mode 100644 tests/unit_tests.bash delete mode 100644 tests/unit_tests_specific_code.bash diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 728b1c7..1fed88d 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -16,7 +16,7 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() "Invalid tests type \"${suite_name}\". Valid values: \"functional\", \"unit\"."\ "Use the '--help' option to get more information." fi - code_filename="${suite_name}_tests_specific_code.bash" + code_filename="${suite_name}_tests.bash" if [[ ! -f "${code_filename}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ "File \"${code_filename}\" not found." diff --git a/tests/functional_tests_specific_code.bash b/tests/functional_tests.bash similarity index 96% rename from tests/functional_tests_specific_code.bash rename to tests/functional_tests.bash index dd7618b..52cde62 100644 --- a/tests/functional_tests_specific_code.bash +++ b/tests/functional_tests.bash @@ -22,6 +22,7 @@ function Make_Test_Preliminary_Operations() function Run_Test() { + local test_name=$1 false # Fail by definition for the moment } diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash new file mode 100644 index 0000000..4d1fb54 --- /dev/null +++ b/tests/unit_tests.bash @@ -0,0 +1,49 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Unit_Test__define-global-variables() +{ + Define_Further_Global_Variables +} + +#======================================================================================================================= + +function Define_Available_Tests() +{ + HYBRIDT_tests_to_be_run=( + 'define-global-variables' + ) +} + +function Make_Test_Preliminary_Operations() +{ + case "$1" in + define-global-variables ) + source "${HYBRIDT_repository_top_level_path}"/bash/global_variables.bash || exit "${HYBRID_fatal_builtin}" + ;; + * ) + ;; + esac +} + +function Run_Test() +{ + local test_name=$1 + { + printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" + Unit_Test__${test_name} + } &>> "${HYBRIDT_log_file}" +} + +function Clean_Tests_Environment_For_Following_Test() +{ + : # No-op for the moment +} + +#======================================================================================================================= diff --git a/tests/unit_tests_specific_code.bash b/tests/unit_tests_specific_code.bash deleted file mode 100644 index 9725048..0000000 --- a/tests/unit_tests_specific_code.bash +++ /dev/null @@ -1,30 +0,0 @@ -#=================================================== -# -# Copyright (c) 2023 -# SMASH Hybrid Team -# -# GNU General Public License (GPLv3 or later) -# -#=================================================== - -function Define_Available_Tests() -{ - HYBRIDT_tests_to_be_run=( - # No tests existing yet - ) -} - -function Make_Test_Preliminary_Operations() -{ - : # No-op for the moment -} - -function Run_Test() -{ - false # Fail by definition for the moment -} - -function Clean_Tests_Environment_For_Following_Test() -{ - : # No-op for the moment -} From 8348fcd24a0bc05c12920229b96b9b65ca821f77 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 24 May 2023 16:53:08 +0200 Subject: [PATCH 039/549] Rename function to parse tests command line options --- tests/command_line_parser_for_tests.bash | 2 +- tests/tests_runner | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 1fed88d..72f6cc2 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -25,7 +25,7 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() fi } -function Parse_Command_Line_Options() +function Parse_Tests_Command_Line_Options() { # This function needs the array of tests not sparse => enforce it HYBRIDT_tests_to_be_run=( "${HYBRIDT_tests_to_be_run[@]}" ) diff --git a/tests/tests_runner b/tests/tests_runner index 28951f9..e163c88 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -32,7 +32,7 @@ function Main() Parse_Tests_Suite_Parameter_And_Source_Specific_Code "$1" Check_System_Requirements Call_through_interface 'Define_Available_Tests' - Parse_Command_Line_Options "${@:2}" + Parse_Tests_Command_Line_Options "${@:2}" Prepare_Test_Environment Run_Tests Print_Tests_Report From 6240997950e7209b14e6ee660441936ee2d24061 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 24 May 2023 16:01:15 +0200 Subject: [PATCH 040/549] Implement main script skeleton Most functions are still to be implemented and they print an error at the moment. --- Hybrid-handler | 63 ++++++++++++++++++---- bash/command_line_parsers/helper.bash | 4 ++ bash/command_line_parsers/main_parser.bash | 9 ++++ bash/global_variables.bash | 19 +++++++ bash/source_codebase_files.bash | 26 +++++++++ bash/system_requirements.bash | 2 +- bash/utility_functions.bash | 5 ++ bash/version.bash | 4 ++ tests/unit_tests.bash | 34 ++++++++++++ 9 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 bash/global_variables.bash diff --git a/Hybrid-handler b/Hybrid-handler index b9abd09..1d1c270 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -23,14 +23,59 @@ set -o pipefail -o nounset -o errexit shopt -s extglob inherit_errexit -source bash/logger.bash +function Main() +{ + Setup_Initial_And_Final_Output_Space + Define_Repository_Global_Path + Store_Command_Line_Options_Into_Global_Variable "$@" + Source_Codebase_Files + Define_Further_Global_Variables + Parse_Execution_Mode + Act_And_Exit_If_User_Ran_Auxiliary_Modes + Check_System_Requirements + Parse_Command_Line_Options + Make_Needed_Operations_Depending_On_Execution_Mode +} +function Setup_Initial_And_Final_Output_Space() +{ + printf '\n' + trap 'printf "\n"' EXIT +} -Print_Trace "Trace message" -Print_Debug "Debug message" -Print_Info "Information message" -Print_Attention "Attention message" -Print_Warning "Warning message" -Print_Error "Error" -( Print_Fatal_And_Exit "Fatal error, exit!" ) -Print_Internal_And_Exit "Internal, for developer error" +function Define_Repository_Global_Path() +{ + readonly HYBRID_repository_global_path=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) +} + +function Store_Command_Line_Options_Into_Global_Variable() +{ + HYBRID_specified_command_line_options=( "$@" ) +} + +function Source_Codebase_Files() +{ + source "${HYBRID_repository_global_path}/bash/source_codebase_files.bash" || exit 1 +} + +function Act_And_Exit_If_User_Ran_Auxiliary_Modes() +{ + case "${HYBRID_execution_mode}" in + help ) + Give_Required_Help + ;;& + version ) + Print_Software_Version + ;;& + help | version ) + exit ${HYBRID_success_exit_code} + ;; + esac +} + +function Make_Needed_Operations_Depending_On_Execution_Mode() +{ + Print_Not_Implemented_Function_Error +} + +Main "$@" diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index 8c4660d..58f3dec 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -7,3 +7,7 @@ # #=================================================== +function Give_Required_Help() +{ + Print_Not_Implemented_Function_Error +} diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 8c4660d..32ad068 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -7,3 +7,12 @@ # #=================================================== +function Parse_Execution_Mode() +{ + Print_Not_Implemented_Function_Error +} + +function Parse_Command_Line_Options() +{ + Print_Not_Implemented_Function_Error +} diff --git a/bash/global_variables.bash b/bash/global_variables.bash new file mode 100644 index 0000000..df13ec1 --- /dev/null +++ b/bash/global_variables.bash @@ -0,0 +1,19 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Define_Further_Global_Variables() +{ + readonly HYBRID_valid_blocks_labels=( + 'IC' + 'Hydro' + 'Sampler' + 'Afterburner' + ) + HYBRID_execution_mode='help' +} diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 8c4660d..6ca9b7e 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -7,3 +7,29 @@ # #=================================================== +function __static__Source_Codebase_Files() +{ + local list_of_files file_to_be_sourced + # Source error codes and fail with hard-coded generic error + source "${HYBRID_repository_global_path}/bash/error_codes.bash" || exit 1 + list_of_files=( + 'command_line_parsers/helper.bash' + 'command_line_parsers/main_parser.bash' + 'command_line_parsers/sub_parser.bash' + 'global_variables.bash' + 'logger.bash' + 'system_requirements.bash' + 'utility_functions.bash' + 'version.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRID_repository_global_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done +} + +# Call the function above and source the codebase files when this script is sourced +if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + __static__Source_Codebase_Files +fi + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 2cb9c95..0f73cd2 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -9,5 +9,5 @@ function Check_System_Requirements() { - Print_Error "${FUNCNAME} not implemented yet. Skipping it." + Print_Not_Implemented_Function_Error } diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index a4e918a..24325d1 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -66,6 +66,11 @@ function Print_Centered_Line() "${padding_utility}" } +function Print_Not_Implemented_Function_Error() +{ + Print_Error "Function \"${FUNCNAME[1]}\" not implemented yet, skipping it." + return 1 +} function Make_Functions_Defined_In_This_File_Readonly() { diff --git a/bash/version.bash b/bash/version.bash index 8c4660d..b14be93 100644 --- a/bash/version.bash +++ b/bash/version.bash @@ -7,3 +7,7 @@ # #=================================================== +function Print_Software_Version() +{ + Print_Not_Implemented_Function_Error +} diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 4d1fb54..f55fd7a 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -12,6 +12,31 @@ function Unit_Test__define-global-variables() Define_Further_Global_Variables } +function Unit_Test__parse-execution-mode() +{ + Parse_Execution_Mode +} + +function Unit_Test__system-requirements() +{ + Check_System_Requirements +} + +function Unit_Test__version() +{ + Print_Software_Version +} + +function Unit_Test__handler-help() +{ + Give_Required_Help +} + +function Unit_Test__parse-command-line-options() +{ + Parse_Command_Line_Options +} + #======================================================================================================================= function Define_Available_Tests() @@ -27,6 +52,15 @@ function Make_Test_Preliminary_Operations() define-global-variables ) source "${HYBRIDT_repository_top_level_path}"/bash/global_variables.bash || exit "${HYBRID_fatal_builtin}" ;; + parse-* ) + source "${HYBRIDT_repository_top_level_path}"/bash/command_line_parsers/main_parser.bash || exit "${HYBRID_fatal_builtin}" + ;; + handler-help ) + source "${HYBRIDT_repository_top_level_path}"/bash/command_line_parsers/helper.bash || exit "${HYBRID_fatal_builtin}" + ;; + version ) + source "${HYBRIDT_repository_top_level_path}"/bash/version.bash || exit "${HYBRID_fatal_builtin}" + ;; * ) ;; esac From bec55b9d877e282383c297b6de391a6d4438c2e7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 24 May 2023 16:52:15 +0200 Subject: [PATCH 041/549] Declare available unit tests based on function names --- tests/unit_tests.bash | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index f55fd7a..e88be26 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -41,8 +41,11 @@ function Unit_Test__parse-command-line-options() function Define_Available_Tests() { + # Available tests are based on functions in this file whose names begins with "Unit_Test__" HYBRIDT_tests_to_be_run=( - 'define-global-variables' + # Here word splitting can split names, no space allowed in function name! + $(grep -E '^function[[:space:]]+Unit_Test__[-[:alnum:]_:]+\(\)[[:space:]]*$' "${BASH_SOURCE[0]}" |\ + sed -E 's/^function[[:space:]]+Unit_Test__([^(]+)\(\)[[:space:]]*$/\1/') ) } From e10519828e7b3e2fcf2fe768b41d0f85355a1913 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 May 2023 09:48:38 +0200 Subject: [PATCH 042/549] Remove unit tests of print-only functions --- tests/.gitignore | 1 + tests/unit_tests.bash | 12 +----------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/.gitignore b/tests/.gitignore index 44045ff..0b10d49 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1,2 @@ run_tests*/ +mocks/config.yaml diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index e88be26..2a5e723 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -7,11 +7,6 @@ # #=================================================== -function Unit_Test__define-global-variables() -{ - Define_Further_Global_Variables -} - function Unit_Test__parse-execution-mode() { Parse_Execution_Mode @@ -24,12 +19,7 @@ function Unit_Test__system-requirements() function Unit_Test__version() { - Print_Software_Version -} - -function Unit_Test__handler-help() -{ - Give_Required_Help + false } function Unit_Test__parse-command-line-options() From cbec4ae0f62684074fd9dae4040111455bb4f06c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 May 2023 09:49:35 +0200 Subject: [PATCH 043/549] Add skeleton code about the main handler functionality All functions are still missing, this serves only as proof of concept for upcoming discussion in the team. --- Hybrid-handler | 15 ++++++++++++++- bash/global_variables.bash | 19 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index 1d1c270..a3fb0d8 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -75,7 +75,20 @@ function Act_And_Exit_If_User_Ran_Auxiliary_Modes() function Make_Needed_Operations_Depending_On_Execution_Mode() { - Print_Not_Implemented_Function_Error + case "${HYBRID_execution_mode}" in + do ) + local software_section + Validate_And_Parse_Configuration_File + for software_section in "${HYBRID_given_software_sections[@]}"; do + Prepare_Software_Input_File "${software_section}" + Ensure_All_Needed_Input_Exists "${software_section}" + Run_Software "${software_section}" + done + ;; + * ) + Print_Internal_And_Exit "Unexpected execution mode at top-level." + ;; + esac } Main "$@" diff --git a/bash/global_variables.bash b/bash/global_variables.bash index df13ec1..d937242 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -9,11 +9,28 @@ function Define_Further_Global_Variables() { - readonly HYBRID_valid_blocks_labels=( + readonly HYBRID_valid_configuration_sections=( + 'Hybrid-handler' 'IC' 'Hydro' 'Sampler' 'Afterburner' ) + readonly HYBRID_default_configurations_folder="${HYBRID_repository_global_path}/configs" + # Variables to be set from command line HYBRID_execution_mode='help' + HYBRID_configuration_file='./config.yaml' + HYBRID_output_directory='.' + # Variables to be set from configuration/setup + HYBRID_ic_software_executable='' + HYBRID_hydro_software_executable='' + HYBRID_sampler_software_executable='' + HYBRID_Afterburner_software_executable='' + HYBRID_given_software_sections=() + declare -gA HYBRID_software_input=( + ['IC']='' + ['Hydro']='' + ['Sampler']='' + ['Afterburner']='' + ) } From eb60bd96050fad2678a4b2423767513d692392e0 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 May 2023 09:56:19 +0200 Subject: [PATCH 044/549] Add dispatching mechanism to call functions if existing --- bash/dispatch_functions.bash | 23 +++++++++++++++++++++++ bash/source_codebase_files.bash | 1 + bash/utility_functions.bash | 16 ++++++++++++++++ tests/tests_runner | 13 +------------ 4 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 bash/dispatch_functions.bash diff --git a/bash/dispatch_functions.bash b/bash/dispatch_functions.bash new file mode 100644 index 0000000..6283ad3 --- /dev/null +++ b/bash/dispatch_functions.bash @@ -0,0 +1,23 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Prepare_Software_Input_File() +{ + Call_Function_If_Existing ${FUNCNAME}_$1 "${@:2}" +} + +function Ensure_All_Needed_Input_Exists() +{ + Call_Function_If_Existing ${FUNCNAME}_$1 "${@:2}" +} + +function Run_Software() +{ + Call_Function_If_Existing ${FUNCNAME}_$1 "${@:2}" +} diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 6ca9b7e..e4dec77 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -16,6 +16,7 @@ function __static__Source_Codebase_Files() 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' 'command_line_parsers/sub_parser.bash' + 'dispatch_functions.bash' 'global_variables.bash' 'logger.bash' 'system_requirements.bash' diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 24325d1..cdc69dc 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -72,6 +72,22 @@ function Print_Not_Implemented_Function_Error() return 1 } +function Call_Function_If_Existing() +{ + local name_of_the_function=$1 + shift + if [[ "$(type -t ${name_of_the_function})" = 'function' ]]; then + # Return value propagates automatically since a function returns the last exit code. + # However, when exit on error behavior is active, the script would terminate here if + # the function returns non-zero exit code and, instead we want this propagate up! + ${name_of_the_function} "$@" || return $? + else + exit_code=${HYBRID_fatal_missing_feature} Print_Internal_And_Exit\ + "\nFunction \"${name_of_the_function}\" not found!"\ + "Please provide an implementation following the in-code documentation." + fi +} + function Make_Functions_Defined_In_This_File_Readonly() { # Here we assume all functions are defined with the same stile, diff --git a/tests/tests_runner b/tests/tests_runner index e163c88..b5a2fbd 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -42,18 +42,7 @@ function Main() function Call_through_interface() { - local name_of_the_function - name_of_the_function=$1; shift - if [[ "$(type -t ${name_of_the_function})" = 'function' ]]; then - ${name_of_the_function} "$@" || return $? - # Return value propagates automatically since a function returns the last exit code. - # However, when exit on error behavior is active, the script would terminate here if - # the function returns non-zero exit code and, instead we want this propagate up! - else - exit_code=${HYBRID_fatal_missing_feature} Print_Internal_And_Exit\ - "\nFunction \"${name_of_the_function}\" not found!"\ - "Please provide an implementation following the in-code documentation." - fi + Call_Function_If_Existing $1 "${@:2}" } function Setup_Initial_And_Final_Output_Space() From 132977bffdf187f89fee2cfa8e53b0b73eb91019 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 May 2023 09:56:52 +0200 Subject: [PATCH 045/549] Add further functionality skeleton as empty functions Each software of the workflow will have dedicated functionality which will be implemented in separate files. the structure has been prepared. The entry point for the parsing of the handler configuration has also been sketched in words in a comment. --- bash/Afterburner_functionality.bash | 23 +++++++++++++++++++++++ bash/Hydro_functionality.bash | 23 +++++++++++++++++++++++ bash/IC_functionality.bash | 23 +++++++++++++++++++++++ bash/Sampler_functionality.bash | 23 +++++++++++++++++++++++ bash/configuration_parser.bash | 23 +++++++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 bash/Afterburner_functionality.bash create mode 100644 bash/Hydro_functionality.bash create mode 100644 bash/IC_functionality.bash create mode 100644 bash/Sampler_functionality.bash create mode 100644 bash/configuration_parser.bash diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash new file mode 100644 index 0000000..7910ab8 --- /dev/null +++ b/bash/Afterburner_functionality.bash @@ -0,0 +1,23 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Prepare_Software_Input_File_Afterburner() +{ + Print_Not_Implemented_Function_Error +} + +function Ensure_All_Needed_Input_Exists_Afterburner() +{ + Print_Not_Implemented_Function_Error +} + +function Run_Software_Afterburner() +{ + Print_Not_Implemented_Function_Error +} diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash new file mode 100644 index 0000000..102cd7f --- /dev/null +++ b/bash/Hydro_functionality.bash @@ -0,0 +1,23 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Prepare_Software_Input_File_Hydro() +{ + Print_Not_Implemented_Function_Error +} + +function Ensure_All_Needed_Input_Exists_Hydro() +{ + Print_Not_Implemented_Function_Error +} + +function Run_Software_Hydro() +{ + Print_Not_Implemented_Function_Error +} diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash new file mode 100644 index 0000000..49d1ef6 --- /dev/null +++ b/bash/IC_functionality.bash @@ -0,0 +1,23 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Prepare_Software_Input_File_IC() +{ + Print_Not_Implemented_Function_Error +} + +function Ensure_All_Needed_Input_Exists_IC() +{ + Print_Not_Implemented_Function_Error +} + +function Run_Software_IC() +{ + Print_Not_Implemented_Function_Error +} diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash new file mode 100644 index 0000000..3a2c40d --- /dev/null +++ b/bash/Sampler_functionality.bash @@ -0,0 +1,23 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Prepare_Software_Input_File_Sampler() +{ + Print_Not_Implemented_Function_Error +} + +function Ensure_All_Needed_Input_Exists_Sampler() +{ + Print_Not_Implemented_Function_Error +} + +function Run_Software_Sampler() +{ + Print_Not_Implemented_Function_Error +} diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash new file mode 100644 index 0000000..e743b78 --- /dev/null +++ b/bash/configuration_parser.bash @@ -0,0 +1,23 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Validate_And_Parse_Configuration_File() +{ + Print_Not_Implemented_Function_Error + # Needed steps: + # 1. Check existence 'HYBRID_configuration_file' + # 2. Validate YAML using yq (remove all comments first) + # 3. Validate top-level sections against 'HYBRID_valid_configuration_sections' + # -> here also the ordering should be validated + # 4. Extract and parse 'Hybrid-handler' section + # 5. Parse software sections setting all needed variables + # -> see global_variables.bash + # -> the software input keys must not be validated but simply put into 'HYBRID_software_input' + # 6. Validate software to be later run for the given software sections (?) +} From bf7cbfbd31ff6b2a5fd66d51a4c75537b88b636c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 May 2023 10:16:42 +0200 Subject: [PATCH 046/549] Fix bug in tests runner and add few more empty unit tests --- bash/utility_functions.bash | 1 - tests/command_line_parser_for_tests.bash | 2 +- tests/unit_tests.bash | 24 +++++++++++++++++------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index cdc69dc..7e9b348 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -69,7 +69,6 @@ function Print_Centered_Line() function Print_Not_Implemented_Function_Error() { Print_Error "Function \"${FUNCNAME[1]}\" not implemented yet, skipping it." - return 1 } function Call_Function_If_Existing() diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 72f6cc2..5459c52 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -16,7 +16,7 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() "Invalid tests type \"${suite_name}\". Valid values: \"functional\", \"unit\"."\ "Use the '--help' option to get more information." fi - code_filename="${suite_name}_tests.bash" + code_filename="${HYBRIDT_tests_folder}/${suite_name}_tests.bash" if [[ ! -f "${code_filename}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ "File \"${code_filename}\" not found." diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 2a5e723..af5fc4d 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -7,24 +7,34 @@ # #=================================================== -function Unit_Test__parse-execution-mode() +function Unit_Test__configuration-validate-1() { - Parse_Execution_Mode + false } -function Unit_Test__system-requirements() +function Unit_Test__configuration-validate-2() { - Check_System_Requirements + false } -function Unit_Test__version() +function Unit_Test__parse-command-line-options() { false } -function Unit_Test__parse-command-line-options() +function Unit_Test__parse-execution-mode() { - Parse_Command_Line_Options + false +} + +function Unit_Test__system-requirements() +{ + false +} + +function Unit_Test__version() +{ + false } #======================================================================================================================= From 0057acd28d07bafdce5d0538045e5f99ebaf1406 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 May 2023 09:26:16 +0200 Subject: [PATCH 047/549] Make reading out of top-level global path deal with symlinks --- Hybrid-handler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hybrid-handler b/Hybrid-handler index a3fb0d8..4eb13ec 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -45,7 +45,7 @@ function Setup_Initial_And_Final_Output_Space() function Define_Repository_Global_Path() { - readonly HYBRID_repository_global_path=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) + readonly HYBRID_repository_global_path=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")") } function Store_Command_Line_Options_Into_Global_Variable() From c586830add29cd6b30ac1bed5b3111fe5dcbeec4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 May 2023 10:51:17 +0200 Subject: [PATCH 048/549] Refactor enabling shell behaviour into function --- Hybrid-handler | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index 4eb13ec..d6dc878 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -19,12 +19,9 @@ # # #----------------------------------------------------------------------------# -# Set stricter bash mode (see CONTRIBUTING for more information) -set -o pipefail -o nounset -o errexit -shopt -s extglob inherit_errexit - function Main() { + Enable_Desired_Shell_Behavior Setup_Initial_And_Final_Output_Space Define_Repository_Global_Path Store_Command_Line_Options_Into_Global_Variable "$@" @@ -37,6 +34,13 @@ function Main() Make_Needed_Operations_Depending_On_Execution_Mode } +function Enable_Desired_Shell_Behavior() +{ + # Set stricter bash mode (see CONTRIBUTING for more information) + set -o pipefail -o nounset -o errexit + shopt -s extglob inherit_errexit +} + function Setup_Initial_And_Final_Output_Space() { printf '\n' From 7d1776c6f5dc2464b65c04cc068ed5125bf6c895 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 May 2023 14:08:14 +0200 Subject: [PATCH 049/549] Fix wrong name of function in tests runner --- tests/tests_runner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests_runner b/tests/tests_runner index b5a2fbd..ccfbd46 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -86,7 +86,7 @@ function Source_Needed_Files() function Print_Helper_And_Exit_If_Requested() { if Element_In_Array_Matches '^-(h|-help)$' "$@"; then - Parse_Command_Line_Options '--help' + Parse_Tests_Command_Line_Options '--help' fi } From c6dab31a5fd743736fc9f24fef600c88203fa480 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 May 2023 17:39:17 +0200 Subject: [PATCH 050/549] Distribute unit tests across files with own set-up/tear-down --- CONTRIBUTING.md | 5 +- bash/dispatch_functions.bash | 6 +-- bash/utility_functions.bash | 12 ++++- tests/tests_runner | 2 +- tests/unit_tests.bash | 62 +++++------------------ tests/unit_tests_command_line_parser.bash | 18 +++++++ tests/unit_tests_configuration.bash | 18 +++++++ tests/unit_tests_system_requirements.bash | 13 +++++ tests/unit_tests_version.bash | 13 +++++ 9 files changed, 93 insertions(+), 56 deletions(-) create mode 100644 tests/unit_tests_command_line_parser.bash create mode 100644 tests/unit_tests_configuration.bash create mode 100644 tests/unit_tests_system_requirements.bash create mode 100644 tests/unit_tests_version.bash diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6bd0da3..d09a9b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,4 +83,7 @@ Here a list of some aspects worth mentioning: * quotes are correctly used, i.e. everything that _might_ break if unquoted is quoted; * single quotes are used if there is no need of using double or different quotes; * all functions declared in each separate file are marked in the end of the file as `readonly`; -* files are sourced all together by sourcing a single dedicated file. +* files are sourced all together by sourcing a single dedicated file; +* unit tests must be put in files whose names begin with `unit_tests_` and have the `.bash` extension (this convention allows the runner to source them all automatically); +* unit tests are automatically recognized by the tests runner as functions having the `Unit_Test__` prefix (and the remaining part of the function will be the unit test name); +* operations to be done before or after a unit test can be put in the `Make_Test_Preliminary_Operations__[test-name]` and `Clean_Tests_Environment_For_Following_Test__[test-name]` functions, respectively (here `[test-name]` must match the string used in the unit test function name). diff --git a/bash/dispatch_functions.bash b/bash/dispatch_functions.bash index 6283ad3..1884dfa 100644 --- a/bash/dispatch_functions.bash +++ b/bash/dispatch_functions.bash @@ -9,15 +9,15 @@ function Prepare_Software_Input_File() { - Call_Function_If_Existing ${FUNCNAME}_$1 "${@:2}" + Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } function Ensure_All_Needed_Input_Exists() { - Call_Function_If_Existing ${FUNCNAME}_$1 "${@:2}" + Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } function Run_Software() { - Call_Function_If_Existing ${FUNCNAME}_$1 "${@:2}" + Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 7e9b348..a4e5dd4 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -71,7 +71,7 @@ function Print_Not_Implemented_Function_Error() Print_Error "Function \"${FUNCNAME[1]}\" not implemented yet, skipping it." } -function Call_Function_If_Existing() +function Call_Function_If_Existing_Or_Exit() { local name_of_the_function=$1 shift @@ -87,6 +87,16 @@ function Call_Function_If_Existing() fi } +function Call_Function_If_Existing_Or_No_Op() +{ + local name_of_the_function=$1 + shift + if [[ "$(type -t ${name_of_the_function})" = 'function' ]]; then + # See 'Call_Function_If_Existing_Or_Exit' for more information about 'return $?' + ${name_of_the_function} "$@" || return $? + fi +} + function Make_Functions_Defined_In_This_File_Readonly() { # Here we assume all functions are defined with the same stile, diff --git a/tests/tests_runner b/tests/tests_runner index ccfbd46..907af25 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -42,7 +42,7 @@ function Main() function Call_through_interface() { - Call_Function_If_Existing $1 "${@:2}" + Call_Function_If_Existing_Or_Exit $1 "${@:2}" } function Setup_Initial_And_Final_Output_Space() diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index af5fc4d..abe3501 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -7,66 +7,28 @@ # #=================================================== -function Unit_Test__configuration-validate-1() -{ - false -} - -function Unit_Test__configuration-validate-2() -{ - false -} - -function Unit_Test__parse-command-line-options() -{ - false -} - -function Unit_Test__parse-execution-mode() -{ - false -} - -function Unit_Test__system-requirements() -{ - false -} - -function Unit_Test__version() -{ - false -} - -#======================================================================================================================= - function Define_Available_Tests() { + # Source all unit tests files to also deduce existing tests + local file_to_be_sourced files_to_be_sourced + files_to_be_sourced=( + "${HYBRIDT_tests_folder}/"unit_tests_*.bash + ) + for file_to_be_sourced in "${files_to_be_sourced[@]}"; do + Print_Debug "Sourcing ${file_to_be_sourced}" + source "${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done # Available tests are based on functions in this file whose names begins with "Unit_Test__" HYBRIDT_tests_to_be_run=( # Here word splitting can split names, no space allowed in function name! - $(grep -E '^function[[:space:]]+Unit_Test__[-[:alnum:]_:]+\(\)[[:space:]]*$' "${BASH_SOURCE[0]}" |\ + $(grep -hE '^function[[:space:]]+Unit_Test__[-[:alnum:]_:]+\(\)[[:space:]]*$' "${files_to_be_sourced[@]}" |\ sed -E 's/^function[[:space:]]+Unit_Test__([^(]+)\(\)[[:space:]]*$/\1/') ) } function Make_Test_Preliminary_Operations() { - case "$1" in - define-global-variables ) - source "${HYBRIDT_repository_top_level_path}"/bash/global_variables.bash || exit "${HYBRID_fatal_builtin}" - ;; - parse-* ) - source "${HYBRIDT_repository_top_level_path}"/bash/command_line_parsers/main_parser.bash || exit "${HYBRID_fatal_builtin}" - ;; - handler-help ) - source "${HYBRIDT_repository_top_level_path}"/bash/command_line_parsers/helper.bash || exit "${HYBRID_fatal_builtin}" - ;; - version ) - source "${HYBRIDT_repository_top_level_path}"/bash/version.bash || exit "${HYBRID_fatal_builtin}" - ;; - * ) - ;; - esac + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 } function Run_Test() @@ -80,7 +42,7 @@ function Run_Test() function Clean_Tests_Environment_For_Following_Test() { - : # No-op for the moment + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 } #======================================================================================================================= diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash new file mode 100644 index 0000000..cae6ab4 --- /dev/null +++ b/tests/unit_tests_command_line_parser.bash @@ -0,0 +1,18 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Unit_Test__parse-command-line-options() +{ + false +} + +function Unit_Test__parse-execution-mode() +{ + false +} diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash new file mode 100644 index 0000000..82777bf --- /dev/null +++ b/tests/unit_tests_configuration.bash @@ -0,0 +1,18 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Unit_Test__configuration-validate-1() +{ + false +} + +function Unit_Test__configuration-validate-2() +{ + false +} diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash new file mode 100644 index 0000000..e9ef6e5 --- /dev/null +++ b/tests/unit_tests_system_requirements.bash @@ -0,0 +1,13 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Unit_Test__system-requirements() +{ + false +} diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash new file mode 100644 index 0000000..4fba111 --- /dev/null +++ b/tests/unit_tests_version.bash @@ -0,0 +1,13 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Unit_Test__version() +{ + false +} From db93ca01d596c6dce744a976d640ba1c08a13cc5 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 May 2023 19:06:16 +0200 Subject: [PATCH 051/549] Implement and test function to remove comments from file --- bash/utility_functions.bash | 20 +++++++++ tests/unit_tests_utility_functions.bash | 60 +++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 tests/unit_tests_utility_functions.bash diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index a4e5dd4..23888cd 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -71,6 +71,26 @@ function Print_Not_Implemented_Function_Error() Print_Error "Function \"${FUNCNAME[1]}\" not implemented yet, skipping it." } +function Remove_Comments_In_Existing_File() +{ + # NOTE: This function consider as comments anything coming after ANY occurrence of + # the specified comment character and you should not use it if there might + # be occurrences of that character that do not start a comment! + # 1) Entire lines starting with a comment (possibly with leading spaces) are removed + # 2) Inline comments with any space before them are removed + local filename comment_character + filename=$1 + comment_character=${2:-#} + if [[ ! -f ${filename} ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + "File \"${filename}\" not found." + elif [[ ${#comment_character} -ne 1 ]]; then + Print_Internal_And_Exit "Comment character \"${comment_character}\" invalid!" + else + sed -i '/^[[:blank:]]*'"${comment_character}"'/d;s/[[:blank:]]*'"${comment_character}"'.*//' "${filename}" + fi +} + function Call_Function_If_Existing_Or_Exit() { local name_of_the_function=$1 diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash new file mode 100644 index 0000000..3df6e64 --- /dev/null +++ b/tests/unit_tests_utility_functions.bash @@ -0,0 +1,60 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# NOTE: No need to source utility functions code, as the test runner uses them! + +function Unit_Test__remove-comments-in-existing-file() +{ + local number_of_lines + cd "${HYBRIDT_folder_to_run_tests}" + # Test case 0 + ( Remove_Comments_In_Existing_File 'not_existing_file.txt' &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Remove comments on not existent file did not fail!" + return 1 + fi + # Test case 1 + local -r file_containing_one_commented_line_only=${FUNCNAME}_1.txt + printf ' # Comment\n' > "${file_containing_one_commented_line_only}" + Remove_Comments_In_Existing_File "${file_containing_one_commented_line_only}" + if [[ -s "${file_containing_one_commented_line_only}" ]]; then + Print_Error "File \"${file_containing_one_commented_line_only}\" not empty!" + return 1 + fi + rm "${file_containing_one_commented_line_only}" + # Test case 2 + local -r file_containing_no_comments=${FUNCNAME}_2.txt + printf $'No comment\nin any\nline\n' > "${file_containing_no_comments}" + number_of_lines=$(wc -l < "${file_containing_no_comments}") + Remove_Comments_In_Existing_File "${file_containing_no_comments}" + if [[ $(wc -l < "${file_containing_no_comments}") -ne ${number_of_lines} ]]; then + Print_Error "Removing comments in \"${file_containing_no_comments}\" file failed!" + return 1 + fi + rm "${file_containing_no_comments}" + # Test case 3 + local -r file_containing_three_commented_lines=${FUNCNAME}_3.txt + printf $'Some\n #comment\ntext\n#comment\namong\n#comment\ncomments\n' > "${file_containing_three_commented_lines}" + number_of_lines=$(wc -l < "${file_containing_three_commented_lines}") + Remove_Comments_In_Existing_File "${file_containing_three_commented_lines}" + if (( $(wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3 )); then + Print_Error "Removing comments in \"${file_containing_three_commented_lines}\" file failed!" + return 1 + fi + rm "${file_containing_three_commented_lines}" + # Test case 4 + local -r file_containing_one_line_with_an_inline_comment=${FUNCNAME}_4.txt + printf 'Hello %% Comment\n' > "${file_containing_one_line_with_an_inline_comment}" + Remove_Comments_In_Existing_File "${file_containing_one_line_with_an_inline_comment}" '%' + if [[ $(cat "${file_containing_one_line_with_an_inline_comment}") != 'Hello' ]]; then + Print_Error "Removing comments in \"${file_containing_one_line_with_an_inline_comment}\" file failed!" + return 1 + fi + rm "${file_containing_one_line_with_an_inline_comment}" +} From ebda868583c15d87d264de9abbcf6bf559556611 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 May 2023 19:08:08 +0200 Subject: [PATCH 052/549] Add functionality to search and replace existing keys This has been implemented and tested so far for YAML files only, basically delegating to yq library. Search and replace in text files has still to be implemented. --- bash/software_input_functionality.bash | 61 ++++++++++++++ ...it_tests_software_input_functionality.bash | 82 +++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 bash/software_input_functionality.bash create mode 100644 tests/unit_tests_software_input_functionality.bash diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash new file mode 100644 index 0000000..acac97b --- /dev/null +++ b/bash/software_input_functionality.bash @@ -0,0 +1,61 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# NOTE: It is assumed that '#' is the character to begins comments in input files +function Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File() +{ + local style base_input_file keys_to_be_replaced + style=$1 + base_input_file=$2 + keys_to_be_replaced=$3 + Remove_Comments_In_Existing_File "${base_input_file}" + case "${style}" in + YAML ) + __static__Replace_Keys_Into_YAML_File + ;; + TXT ) + __static__Replace_Keys_Into_Txt_File + ;; + * ) + Print_Internal_And_Exit "Wrong first argument passed to \"${FUNCNAME}\"." + ;; + esac +} + +# NOTE: The following functions use the local variables of the calling function +function __static__Replace_Keys_Into_YAML_File() +{ + # Use yq -P to bring all YAML to same format (crucial for later check on number of lines) + if ! yq -P --inplace "${base_input_file}" 2> /dev/null; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + "File \"${base_input_file}\" does not seem to contain valid YAML syntax." + fi + if ! keys_to_be_replaced=$(yq -P <(printf "${keys_to_be_replaced}\n") 2> /dev/null); then + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + 'Keys to be replaced do not seem to contain valid YAML syntax.' + fi + local initial_number_of_lines + initial_number_of_lines=$(wc -l < "${base_input_file}") + # Use yq to merge the two "files" into the first one + yq --inplace eval-all '. as $item ireduce ({}; . * $item)'\ + "${base_input_file}"\ + <(printf "${keys_to_be_replaced}\n") + # The merge must not have changed the number of lines of input file. If it did, + # it means that some key was not present and has been appended => Error! + if [[ $(wc -l < "${base_input_file}") -ne ${initial_number_of_lines} ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'One or more provided software input keys were not found to be replaced'\ + "in the \"${base_input_file}\" file." "Please, check your configuration file." + fi +} + +function __static__Replace_Keys_Into_Txt_File() +{ + Print_Not_Implemented_Function_Error +} diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash new file mode 100644 index 0000000..b5345eb --- /dev/null +++ b/tests/unit_tests_software_input_functionality.bash @@ -0,0 +1,82 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Test_Preliminary_Operations__replace-in-software-input-YAML() +{ + source "${HYBRIDT_repository_top_level_path}"/bash/software_input_functionality.bash\ + || exit "${HYBRID_fatal_builtin}" +} + +function Unit_Test__replace-in-software-input-YAML() +{ + local base_input_file keys_to_be_replaced expected_result + base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + # Test case 1: + printf 'Scalar\nKey: Value\n' > "${base_input_file}" + ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'YAML replacement in invalid file succeeded' + return 1 + fi + # Test case 2: + printf 'Key: Value\n' > "${base_input_file}" + keys_to_be_replaced=$'Invalid\nyaml: syntax' + ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Invalid YAML replacement in valid file succeeded' + return 1 + fi + # Test case 3: + printf 'Key: Value\n' > "${base_input_file}" + keys_to_be_replaced='New_key: value' + ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Valid YAML replacement but with new key in valid file succeeded' + return 1 + fi + # Test case 4: + printf\ + ' + Array: + - 1 + - 2 + - 3 + + Map: + Key_1: Hi + Key_2: Bye + Foo: Bar + ' > "${base_input_file}" + keys_to_be_replaced=' + Array: [5,6,7] + Foo: BarBar + ' + expected_result=' +Array: + - 5 + - 6 + - 7 +Map: + Key_1: Hi + Key_2: Bye +Foo: BarBar' + __static__Replace_Keys_Into_YAML_File + if [[ "$(cat "${base_input_file}")" != "${expected_result}" ]]; then + Print_Error "YAML replacement failed!"\ + '---- OBTAINED: ----' "$(cat "${base_input_file}")"\ + '---- EXPECTED: ----' "${expected_result}"\ + '-------------------' + return 1 + fi +} + +function Unit_Test__replace-in-software-input-TXT() +{ + false +} From f0cfd1ea89245666277fd62e97e042545857150e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 May 2023 19:51:15 +0200 Subject: [PATCH 053/549] Implement search and replace for input keys in text files --- bash/software_input_functionality.bash | 18 ++++++-- ...it_tests_software_input_functionality.bash | 44 ++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index acac97b..69bfeb8 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -35,8 +35,7 @@ function __static__Replace_Keys_Into_YAML_File() if ! yq -P --inplace "${base_input_file}" 2> /dev/null; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ "File \"${base_input_file}\" does not seem to contain valid YAML syntax." - fi - if ! keys_to_be_replaced=$(yq -P <(printf "${keys_to_be_replaced}\n") 2> /dev/null); then + elif ! keys_to_be_replaced=$(yq -P <(printf "${keys_to_be_replaced}\n") 2> /dev/null); then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ 'Keys to be replaced do not seem to contain valid YAML syntax.' fi @@ -57,5 +56,18 @@ function __static__Replace_Keys_Into_YAML_File() function __static__Replace_Keys_Into_Txt_File() { - Print_Not_Implemented_Function_Error + # Impose that both file and new keys have two entries per line + if ! awk 'NF != 2 { exit 1 }' "${base_input_file}"; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + "File \"${base_input_file}\" does not seem to contain two columns per line only!" + elif ! awk 'NF != 2 { exit 1 }' <(printf "${keys_to_be_replaced}\n"); then + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + 'Keys to be replaced do not seem to contain valid key-value syntax.' + fi + # NOTE: Since the YAML implementation is very general, here we can take advantage of it + # on constraint of inserting and then removing a ':' after the "key". + awk -i inplace 'BEGIN{OFS=": "}{print $1, $2}' "${base_input_file}" + keys_to_be_replaced=$(awk 'BEGIN{OFS=": "}{print $1, $2}' <<< "${keys_to_be_replaced}") + __static__Replace_Keys_Into_YAML_File + awk -i inplace 'BEGIN{FS=": "}{printf "%-20s%s\n", $1, $2}' "${base_input_file}" } diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index b5345eb..3161c11 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -76,7 +76,49 @@ Foo: BarBar' fi } +function Make_Test_Preliminary_Operations__replace-in-software-input-TXT() +{ + Make_Test_Preliminary_Operations__replace-in-software-input-YAML +} + function Unit_Test__replace-in-software-input-TXT() { - false + local base_input_file keys_to_be_replaced expected_result + base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + # Test case 1: + printf 'Key Value Extra-field\n' > "${base_input_file}" + ( __static__Replace_Keys_Into_Txt_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'TXT replacement in invalid file succeeded' + return 1 + fi + # Test case 2: + printf 'Key: Value\n' > "${base_input_file}" + keys_to_be_replaced='Invalid txt syntax' + ( __static__Replace_Keys_Into_Txt_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Invalid TXT replacement in valid file succeeded' + return 1 + fi + # Test case 3: + printf 'Key Value\n' > "${base_input_file}" + keys_to_be_replaced='New_key value' + ( __static__Replace_Keys_Into_Txt_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Valid YAML replacement but with new key in valid file succeeded' + return 1 + fi + # Test case 4: + printf 'a 0.123\nb 0.456\nc 0.789\n' > "${base_input_file}" + keys_to_be_replaced=$'a 42\nc 77' + printf -v expected_result "%-20s%s\n" 'a' '42' 'b' '0.456' 'c' '77' + expected_result=${expected_result%?} # Get rid of trailing endline + __static__Replace_Keys_Into_Txt_File + if [[ "$(cat "${base_input_file}")" != "${expected_result}" ]]; then + Print_Error "YAML replacement failed!"\ + "---- OBTAINED: ----\n$(cat "${base_input_file}")"\ + "---- EXPECTED: ----\n${expected_result}"\ + '-------------------' + return 1 + fi } From 292995068e0444957d9c982107f71126992dc9e6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 30 May 2023 11:27:22 +0200 Subject: [PATCH 054/549] Add some more explicit information about used conventions --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d09a9b3..2a3d703 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,7 +83,7 @@ Here a list of some aspects worth mentioning: * quotes are correctly used, i.e. everything that _might_ break if unquoted is quoted; * single quotes are used if there is no need of using double or different quotes; * all functions declared in each separate file are marked in the end of the file as `readonly`; -* files are sourced all together by sourcing a single dedicated file; -* unit tests must be put in files whose names begin with `unit_tests_` and have the `.bash` extension (this convention allows the runner to source them all automatically); +* files are sourced all together by sourcing a single dedicated file (cf. *bash/source_codebase_files.bash* file); +* unit tests must be put in files whose names begin with `unit_tests_` and have the `.bash` extension (this convention allows the runner to source them all automatically) in the ***tests*** folder; * unit tests are automatically recognized by the tests runner as functions having the `Unit_Test__` prefix (and the remaining part of the function will be the unit test name); * operations to be done before or after a unit test can be put in the `Make_Test_Preliminary_Operations__[test-name]` and `Clean_Tests_Environment_For_Following_Test__[test-name]` functions, respectively (here `[test-name]` must match the string used in the unit test function name). From b1e371dd9ec87c123ea2db1e07a926dcaf6b2596 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 30 May 2023 12:05:35 +0200 Subject: [PATCH 055/549] Add more descriptive error message when validating YAML file --- bash/software_input_functionality.bash | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 69bfeb8..2b3cbc0 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -34,7 +34,9 @@ function __static__Replace_Keys_Into_YAML_File() # Use yq -P to bring all YAML to same format (crucial for later check on number of lines) if ! yq -P --inplace "${base_input_file}" 2> /dev/null; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ - "File \"${base_input_file}\" does not seem to contain valid YAML syntax." + "File \"${base_input_file}\" does not seem to contain valid YAML syntax. Run"\ + " yq -P --inplace \"${base_input_file}\""\ + "to have more information about the problem." elif ! keys_to_be_replaced=$(yq -P <(printf "${keys_to_be_replaced}\n") 2> /dev/null); then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ 'Keys to be replaced do not seem to contain valid YAML syntax.' From a30f850b99a2f0a43d4a7a26f298db31e9764289 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 30 May 2023 12:12:45 +0200 Subject: [PATCH 056/549] Fix typo in comment --- bash/utility_functions.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 23888cd..78d4e6c 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -73,7 +73,7 @@ function Print_Not_Implemented_Function_Error() function Remove_Comments_In_Existing_File() { - # NOTE: This function consider as comments anything coming after ANY occurrence of + # NOTE: This function considers as comments anything coming after ANY occurrence of # the specified comment character and you should not use it if there might # be occurrences of that character that do not start a comment! # 1) Entire lines starting with a comment (possibly with leading spaces) are removed From 412b51cfd695bb463617d08815ca46179861cbb3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 30 May 2023 12:53:42 +0200 Subject: [PATCH 057/549] Remove auxiliary files in unit tests when test passes --- tests/unit_tests_software_input_functionality.bash | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 3161c11..26bc36a 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -15,6 +15,8 @@ function Make_Test_Preliminary_Operations__replace-in-software-input-YAML() function Unit_Test__replace-in-software-input-YAML() { + # NOTE: The following variables must be named exactly so as the are used + # by __static__Replace_Keys_Into_YAML_File function local base_input_file keys_to_be_replaced expected_result base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml # Test case 1: @@ -74,6 +76,7 @@ Foo: BarBar' '-------------------' return 1 fi + rm "${base_input_file}" } function Make_Test_Preliminary_Operations__replace-in-software-input-TXT() @@ -83,6 +86,8 @@ function Make_Test_Preliminary_Operations__replace-in-software-input-TXT() function Unit_Test__replace-in-software-input-TXT() { + # NOTE: The following variables must be named exactly so as the are used + # by __static__Replace_Keys_Into_Txt_File function local base_input_file keys_to_be_replaced expected_result base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml # Test case 1: @@ -121,4 +126,5 @@ function Unit_Test__replace-in-software-input-TXT() '-------------------' return 1 fi + rm "${base_input_file}" } From a45a41b63d810c41a9d84c88ad1dd36decb0b666 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 30 May 2023 13:54:38 +0200 Subject: [PATCH 058/549] Rename utility function name --- bash/software_input_functionality.bash | 2 +- bash/utility_functions.bash | 2 +- tests/unit_tests_software_input_functionality.bash | 6 +++--- tests/unit_tests_utility_functions.bash | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 2b3cbc0..2a9bdc1 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -14,7 +14,7 @@ function Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File() style=$1 base_input_file=$2 keys_to_be_replaced=$3 - Remove_Comments_In_Existing_File "${base_input_file}" + Remove_Comments_In_File "${base_input_file}" # this checks for existence, too case "${style}" in YAML ) __static__Replace_Keys_Into_YAML_File diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 78d4e6c..0d41be2 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -71,7 +71,7 @@ function Print_Not_Implemented_Function_Error() Print_Error "Function \"${FUNCNAME[1]}\" not implemented yet, skipping it." } -function Remove_Comments_In_Existing_File() +function Remove_Comments_In_File() { # NOTE: This function considers as comments anything coming after ANY occurrence of # the specified comment character and you should not use it if there might diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 26bc36a..edbff0b 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -23,7 +23,7 @@ function Unit_Test__replace-in-software-input-YAML() printf 'Scalar\nKey: Value\n' > "${base_input_file}" ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error 'YAML replacement in invalid file succeeded' + Print_Error 'YAML replacement in invalid file succeeded.' return 1 fi # Test case 2: @@ -31,7 +31,7 @@ function Unit_Test__replace-in-software-input-YAML() keys_to_be_replaced=$'Invalid\nyaml: syntax' ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error 'Invalid YAML replacement in valid file succeeded' + Print_Error 'Invalid YAML replacement in valid file succeeded.' return 1 fi # Test case 3: @@ -39,7 +39,7 @@ function Unit_Test__replace-in-software-input-YAML() keys_to_be_replaced='New_key: value' ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error 'Valid YAML replacement but with new key in valid file succeeded' + Print_Error 'Valid YAML replacement but with new key in valid file succeeded.' return 1 fi # Test case 4: diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 3df6e64..7b4a212 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -22,7 +22,7 @@ function Unit_Test__remove-comments-in-existing-file() # Test case 1 local -r file_containing_one_commented_line_only=${FUNCNAME}_1.txt printf ' # Comment\n' > "${file_containing_one_commented_line_only}" - Remove_Comments_In_Existing_File "${file_containing_one_commented_line_only}" + Remove_Comments_In_File "${file_containing_one_commented_line_only}" if [[ -s "${file_containing_one_commented_line_only}" ]]; then Print_Error "File \"${file_containing_one_commented_line_only}\" not empty!" return 1 @@ -32,7 +32,7 @@ function Unit_Test__remove-comments-in-existing-file() local -r file_containing_no_comments=${FUNCNAME}_2.txt printf $'No comment\nin any\nline\n' > "${file_containing_no_comments}" number_of_lines=$(wc -l < "${file_containing_no_comments}") - Remove_Comments_In_Existing_File "${file_containing_no_comments}" + Remove_Comments_In_File "${file_containing_no_comments}" if [[ $(wc -l < "${file_containing_no_comments}") -ne ${number_of_lines} ]]; then Print_Error "Removing comments in \"${file_containing_no_comments}\" file failed!" return 1 @@ -42,7 +42,7 @@ function Unit_Test__remove-comments-in-existing-file() local -r file_containing_three_commented_lines=${FUNCNAME}_3.txt printf $'Some\n #comment\ntext\n#comment\namong\n#comment\ncomments\n' > "${file_containing_three_commented_lines}" number_of_lines=$(wc -l < "${file_containing_three_commented_lines}") - Remove_Comments_In_Existing_File "${file_containing_three_commented_lines}" + Remove_Comments_In_File "${file_containing_three_commented_lines}" if (( $(wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3 )); then Print_Error "Removing comments in \"${file_containing_three_commented_lines}\" file failed!" return 1 @@ -51,7 +51,7 @@ function Unit_Test__remove-comments-in-existing-file() # Test case 4 local -r file_containing_one_line_with_an_inline_comment=${FUNCNAME}_4.txt printf 'Hello %% Comment\n' > "${file_containing_one_line_with_an_inline_comment}" - Remove_Comments_In_Existing_File "${file_containing_one_line_with_an_inline_comment}" '%' + Remove_Comments_In_File "${file_containing_one_line_with_an_inline_comment}" '%' if [[ $(cat "${file_containing_one_line_with_an_inline_comment}") != 'Hello' ]]; then Print_Error "Removing comments in \"${file_containing_one_line_with_an_inline_comment}\" file failed!" return 1 From e63ddc4b4898331aade58417acc8a23299561281 Mon Sep 17 00:00:00 2001 From: ren Date: Tue, 30 May 2023 15:21:57 +0200 Subject: [PATCH 059/549] Create check for system requirements and add to unit test --- bash/system_requirements.bash | 88 ++++++++++++++++++++++- tests/unit_tests_system_requirements.bash | 45 +++++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 0f73cd2..e0c692b 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -7,7 +7,93 @@ # #=================================================== + +function __static__Declare_System_Requirements() +{ + declare -grA HYBRID_systemRequirements=( + ['bash']='4.4.0' + ['awk']='4.1.0' + ['sed']='4.2.1' + ['tput']='5.9' # the last number is a date + ) +} + function Check_System_Requirements() { - Print_Not_Implemented_Function_Error + local program requirements_present + requirements_present=0 + declare -A system_found_versions + __static__Declare_System_Requirements + for program in "${!HYBRID_systemRequirements[@]}"; do + if ! __static__Try_Find_Requirement "${program}"; then + echo "${program}" 'not found! Minimum version' "${HYBRID_systemRequirements[${program}]}" 'is required.' + requirements_present=1 + continue + fi + if ! __static__Try_Find_Version "${program}"; then + echo 'Unable to find version of ' "${program}" ', skipping version check! Please ensure that '\ + 'current version is at least ' "${HYBRID_systemRequirements[${program}]}" + continue + fi + if ! __static__Check_Version_Suffices "${program}"; then + echo "${program}" 'version' "${system_found_versions[${program}]}" 'found, but version'\ + "${HYBRID_systemRequirements[${program}]}" 'is required.' + requirements_present=1 + fi + done + if [[ ${requirements_present} -ne 0 ]]; then + Print_Fatal_And_Exit 'Please install (maybe locally) the required versions of the above programs.' + else + return ${requirements_present} + fi + +} + +function __static__Try_Find_Requirement() +{ + if hash $1 2>/dev/null; then + return 0 + else + return 1 + fi +} + +function __static__Try_Find_Version() +{ + local found_version + case "$1" in + bash ) + found_version="$(sed 's/ /./g' <<< "${BASH_VERSINFO[@]:0:3}")" + ;; + awk ) + found_version=$(awk --version | head -n1 | grep -o "[0-9.]\+" | head -n1) + ;; + sed ) + found_version=$(sed --version | head -n1 | grep -o "[0-9.]\+" | head -n1) + ;; + tput ) + found_version=$(tput -V | grep -o "[0-9.]\+" | cut -d'.' -f1,2) + ;; + *) + return 1 + esac + if [[ ${found_version} =~ ^[0-9]([.0-9])*$ ]]; then + system_found_versions["$1"]="${found_version}" + else + return 1 + fi +} + +function __static__Check_Version_Suffices() +{ + # Here we assume that the programs follow the semantic versioning standard + local required found versioning + required=($(echo "${HYBRID_systemRequirements[$1]}" | tr '.' ' ')) + found=($(echo "${system_found_versions[$1]}" | tr '.' ' ')) + for versioning in ${!required[@]}; do + if [[ "${required[${versioning}]}" -lt "${found[${versioning}]}" ]]; then + return 0 + fi + done + return 1 } diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index c0033f1..bb15c22 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -7,7 +7,50 @@ # #=================================================== +function __static__Fake_Command_Version() +{ + if [[ "$3" = "$1" ]]; then + echo $2 + else + Print_Fatal_And_Exit "wrong usage of ${FUNCNAME[1]}" + fi +} + +function __static__Inhibit_Commands_Version() +{ + function awk() + { + __static__Fake_Command_Version '--version' "GNU Awk ${version}, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)" "$@" + } + + function sed() + { + __static__Fake_Command_Version '--version' "sed (GNU sed) ${version} Packaged by Debian" "$@" + } + + function tput() + { + __static__Fake_Command_Version '-V' "ncurses ${version}" "$@" + } +} + function Unit_Test__system-requirements() { - return 0 + __static__Inhibit_Commands_Version + local good_version='99.0.0' + local bad_version='1.0' + + local version=${good_version} + ( Check_System_Requirements &> /dev/null ) + if [[ $? -ne 0 ]]; then + return 1 + fi + + + version=${bad_version} + ( Check_System_Requirements &> /dev/null ) + echo $? + if [[ $? -ne 1 ]]; then + return 1 + fi } From 64c2e9b7421575bc7ea48ac071017d35033557e9 Mon Sep 17 00:00:00 2001 From: gabriele-inghirami Date: Tue, 30 May 2023 15:42:18 +0200 Subject: [PATCH 060/549] Add Github hosted actions --- .../github-actions-on-github-servers.yml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/github-actions-on-github-servers.yml diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml new file mode 100644 index 0000000..2f336f1 --- /dev/null +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -0,0 +1,65 @@ +name: actions_on_github_servers + +on: + # trigger the workflow upon request + workflow_dispatch: + # trigger the workflow on push or pull request, + # but only for the main branch + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + +jobs: + check_pull: + # we use jobs in a matrix + # the OS to be used for the test is taken from the current matrix element + runs-on: ${{ matrix.os }} + strategy: + # we want to avoid to stop all the tests the first time that one of them gets an error + fail-fast: false + matrix: + include: + #here we set up the various matrix elements + #the entries in each matrix element are just variables, not keywords, with (hopefully) self-explaining names + # 1st matrix element, current LTS Ubuntu distribution, default bash version + - os: ubuntu-22.04 + bash_version: "default" + # 2st matrix element, current LTS Ubuntu distribution, bash 4.4 + - os: ubuntu-22.04 + bash_version: "4.4" + steps: + # this is an action provided by GitHub to checkout the repository + - uses: actions/checkout@v3 + # we set the name of the step, collecting all the tests here except those about formatting + - name: code_check + # we set some environment variables which depend on the specific matrix element + env: + OS_NAME: ${{ matrix.os }} + BASH_TESTED_VERSION: ${{matrix.bash_version}} + # we run the step. We recall that in YAML the pipe symbol "|" means that the following lines, including newlines, are interpreted literally + run: | + # we save in a variable the path of the starting directory + export main_dir=$PWD + # if we are not using the default bash version, we download and compile the desired one + if [ $BASH_TESTED_VERSION != "default" ]; then + wget https://ftp.gnu.org/gnu/bash/bash-$BASH_TESTED_VERSION.tar.gz + tar xf bash-$BASH_TESTED_VERSION.tar.gz + cd bash-$BASH_TESTED_VERSION + ./configure + make -j$(nproc) + BASH_EXE=$(pwd)/bash + else + BASH_EXE=$(which bash) + fi + # we print the bash version to check that we used the desired one + echo -e "\n\n"; $BASH_EXE --version; echo -e "\n\n" + # now we enter into the tests directory and execute the tests, + # with maximum detail (-r 3) and keeping all the files (-k) + cd $main_dir/tests + $BASH_EXE tests_runner unit -k -r 3 + $BASH_EXE tests_runner functional -k -r 3 From 348a39c13aa9aeee33f5cf8a15d9a1eed0a6af2f Mon Sep 17 00:00:00 2001 From: gabriele-inghirami Date: Wed, 31 May 2023 00:22:10 +0200 Subject: [PATCH 061/549] Improve test label and style in actions --- .../github-actions-on-github-servers.yml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index 2f336f1..51ad77b 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -31,7 +31,7 @@ jobs: bash_version: "default" # 2st matrix element, current LTS Ubuntu distribution, bash 4.4 - os: ubuntu-22.04 - bash_version: "4.4" + bash_version: "bash-4.4" steps: # this is an action provided by GitHub to checkout the repository - uses: actions/checkout@v3 @@ -41,15 +41,16 @@ jobs: env: OS_NAME: ${{ matrix.os }} BASH_TESTED_VERSION: ${{matrix.bash_version}} - # we run the step. We recall that in YAML the pipe symbol "|" means that the following lines, including newlines, are interpreted literally + # we run the step + # we recall that in YAML the pipe symbol "|" means that the following lines, including newlines, are interpreted literally run: | # we save in a variable the path of the starting directory - export main_dir=$PWD + export main_dir=${PWD} # if we are not using the default bash version, we download and compile the desired one - if [ $BASH_TESTED_VERSION != "default" ]; then - wget https://ftp.gnu.org/gnu/bash/bash-$BASH_TESTED_VERSION.tar.gz - tar xf bash-$BASH_TESTED_VERSION.tar.gz - cd bash-$BASH_TESTED_VERSION + if [ ${BASH_TESTED_VERSION} != "default" ]; then + wget https://ftp.gnu.org/gnu/bash/${BASH_TESTED_VERSION}.tar.gz + tar xf ${BASH_TESTED_VERSION}.tar.gz + cd ${BASH_TESTED_VERSION} ./configure make -j$(nproc) BASH_EXE=$(pwd)/bash @@ -57,9 +58,8 @@ jobs: BASH_EXE=$(which bash) fi # we print the bash version to check that we used the desired one - echo -e "\n\n"; $BASH_EXE --version; echo -e "\n\n" - # now we enter into the tests directory and execute the tests, - # with maximum detail (-r 3) and keeping all the files (-k) + printf "\n$(${BASH_EXE} --version)\n" + # now we enter into the tests directory and execute the tests with maximum detail (-r 3) cd $main_dir/tests - $BASH_EXE tests_runner unit -k -r 3 - $BASH_EXE tests_runner functional -k -r 3 + ${BASH_EXE} tests_runner unit -r 3 + ${BASH_EXE} tests_runner functional -r 3 From 3b834f8c8d160e4676366f3857c656d2d6b0e797 Mon Sep 17 00:00:00 2001 From: gabriele-inghirami Date: Wed, 31 May 2023 01:10:33 +0200 Subject: [PATCH 062/549] Make skeleton tests pass by default --- tests/functional_tests.bash | 2 +- tests/unit_tests_command_line_parser.bash | 4 ++-- tests/unit_tests_configuration.bash | 4 ++-- tests/unit_tests_system_requirements.bash | 2 +- tests/unit_tests_version.bash | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index 52cde62..a1f9477 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -23,7 +23,7 @@ function Make_Test_Preliminary_Operations() function Run_Test() { local test_name=$1 - false # Fail by definition for the moment + return 0 # Success by definition for the moment } function Clean_Tests_Environment_For_Following_Test() diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index cae6ab4..c95367b 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -9,10 +9,10 @@ function Unit_Test__parse-command-line-options() { - false + return 0 } function Unit_Test__parse-execution-mode() { - false + return 0 } diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 82777bf..6e4c0fd 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -9,10 +9,10 @@ function Unit_Test__configuration-validate-1() { - false + return 0 } function Unit_Test__configuration-validate-2() { - false + return 0 } diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index e9ef6e5..c0033f1 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -9,5 +9,5 @@ function Unit_Test__system-requirements() { - false + return 0 } diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash index 4fba111..b44e8cf 100644 --- a/tests/unit_tests_version.bash +++ b/tests/unit_tests_version.bash @@ -9,5 +9,5 @@ function Unit_Test__version() { - false + return 0 } From c486653dc0b8be151e448fa996054b39d833c070 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 31 May 2023 15:15:08 +0200 Subject: [PATCH 063/549] Rename action job name to make more descriptive --- .github/workflows/github-actions-on-github-servers.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index 51ad77b..1bb85f8 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -15,7 +15,7 @@ on: - develop jobs: - check_pull: + run_tests: # we use jobs in a matrix # the OS to be used for the test is taken from the current matrix element runs-on: ${{ matrix.os }} @@ -29,7 +29,7 @@ jobs: # 1st matrix element, current LTS Ubuntu distribution, default bash version - os: ubuntu-22.04 bash_version: "default" - # 2st matrix element, current LTS Ubuntu distribution, bash 4.4 + # 2st matrix element, current LTS Ubuntu distribution, bash 4.4 - os: ubuntu-22.04 bash_version: "bash-4.4" steps: From e627acdce4d8580a655b3382539a540a96abdba3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 31 May 2023 16:48:28 +0200 Subject: [PATCH 064/549] Fix bug in setting exit code in logger I somehow had put the default setting of the internal exit code for internal error messages in the wrong function. --- bash/logger.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/logger.bash b/bash/logger.bash index 7e68cab..1f361a3 100644 --- a/bash/logger.bash +++ b/bash/logger.bash @@ -77,12 +77,12 @@ function Print_Error() function Print_Fatal_And_Exit() { - exit_code=${HYBRID_internal:-1} __static__Logger 'FATAL' "$@" + __static__Logger 'FATAL' "$@" } function Print_Internal_And_Exit() { - __static__Logger 'INTERNAL' "$@" + exit_code=${HYBRID_internal:-1} __static__Logger 'INTERNAL' "$@" } function __static__Logger() From 5d29a4509cd39feb251f831dbe46ca2695eea2d3 Mon Sep 17 00:00:00 2001 From: ren Date: Wed, 31 May 2023 17:02:18 +0200 Subject: [PATCH 065/549] Create unit test that fakes commands --- bash/system_requirements.bash | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index e0c692b..b0feaa3 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -10,7 +10,7 @@ function __static__Declare_System_Requirements() { - declare -grA HYBRID_systemRequirements=( + declare -gA HYBRID_systemRequirements=( ['bash']='4.4.0' ['awk']='4.1.0' ['sed']='4.2.1' @@ -46,7 +46,6 @@ function Check_System_Requirements() else return ${requirements_present} fi - } function __static__Try_Find_Requirement() From 17fc1afe7cd3364c7a4957cbd9873aec56e62ff0 Mon Sep 17 00:00:00 2001 From: ren Date: Wed, 31 May 2023 19:12:38 +0200 Subject: [PATCH 066/549] Change echo to logging messages and add amplification comment --- bash/system_requirements.bash | 17 ++++++++++------- tests/unit_tests_system_requirements.bash | 16 +++++++++++----- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index b0feaa3..277222b 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -14,7 +14,8 @@ function __static__Declare_System_Requirements() ['bash']='4.4.0' ['awk']='4.1.0' ['sed']='4.2.1' - ['tput']='5.9' # the last number is a date + ['tput']='5.9.0' # the last number is a date + ['yq']='4.0' ) } @@ -26,18 +27,16 @@ function Check_System_Requirements() __static__Declare_System_Requirements for program in "${!HYBRID_systemRequirements[@]}"; do if ! __static__Try_Find_Requirement "${program}"; then - echo "${program}" 'not found! Minimum version' "${HYBRID_systemRequirements[${program}]}" 'is required.' + Print_Error "${program}" 'not found! Minimum version' "${HYBRID_systemRequirements[${program}]}" 'is required.' requirements_present=1 continue fi if ! __static__Try_Find_Version "${program}"; then - echo 'Unable to find version of ' "${program}" ', skipping version check! Please ensure that '\ - 'current version is at least ' "${HYBRID_systemRequirements[${program}]}" + Print_Warning "Unable to find version of ${program}, skipping version check! Please ensure that current version is at least ${HYBRID_systemRequirements[${program}]}." continue fi if ! __static__Check_Version_Suffices "${program}"; then - echo "${program}" 'version' "${system_found_versions[${program}]}" 'found, but version'\ - "${HYBRID_systemRequirements[${program}]}" 'is required.' + Print_Error "${program} version ${system_found_versions[${program}]} found, but version ${HYBRID_systemRequirements[${program}]} is required." requirements_present=1 fi done @@ -62,7 +61,8 @@ function __static__Try_Find_Version() local found_version case "$1" in bash ) - found_version="$(sed 's/ /./g' <<< "${BASH_VERSINFO[@]:0:3}")" + found_version="${BASH_VERSINFO[@]:0:3}" + found_version="${found_version// /.}" ;; awk ) found_version=$(awk --version | head -n1 | grep -o "[0-9.]\+" | head -n1) @@ -73,6 +73,9 @@ function __static__Try_Find_Version() tput ) found_version=$(tput -V | grep -o "[0-9.]\+" | cut -d'.' -f1,2) ;; + yq ) + found_version=$(yq --version | grep -o "v[0-9.]\+" | cut -d'v' -f2) + ;; *) return 1 esac diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index bb15c22..46a55de 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -22,18 +22,25 @@ function __static__Inhibit_Commands_Version() { __static__Fake_Command_Version '--version' "GNU Awk ${version}, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)" "$@" } - function sed() { __static__Fake_Command_Version '--version' "sed (GNU sed) ${version} Packaged by Debian" "$@" } - function tput() { __static__Fake_Command_Version '-V' "ncurses ${version}" "$@" } + function yq() + { + __static__Fake_Command_Version '--version' "yq (https://github.com/mikefarah/yq/) version v${version}" "$@" + } } +# This tests asks Check_System_Requirements first for success, in case of a 'good' input; +# and for failure, in case of a 'bad' one. Here 99.0.0 and 1.0 respectively should suffice +# for most commands. Since the check only uses the commands to look for their version, they +# are faked here to only accept the specific version flag, and fail otherwise. + function Unit_Test__system-requirements() { __static__Inhibit_Commands_Version @@ -43,14 +50,13 @@ function Unit_Test__system-requirements() local version=${good_version} ( Check_System_Requirements &> /dev/null ) if [[ $? -ne 0 ]]; then + Print_Error "${good_version} is lower than some requirements" return 1 fi - - version=${bad_version} ( Check_System_Requirements &> /dev/null ) - echo $? if [[ $? -ne 1 ]]; then + Print_Error "${bad_version} is higher than some requirements" return 1 fi } From 47c08afd39e4e9b06b81dae4511cee6575452a67 Mon Sep 17 00:00:00 2001 From: ren Date: Wed, 31 May 2023 19:45:13 +0200 Subject: [PATCH 067/549] Accept equal to minimum version --- bash/system_requirements.bash | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 277222b..a4b7fcd 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -92,6 +92,9 @@ function __static__Check_Version_Suffices() local required found versioning required=($(echo "${HYBRID_systemRequirements[$1]}" | tr '.' ' ')) found=($(echo "${system_found_versions[$1]}" | tr '.' ' ')) + if [[ "${required[${versioning}]}" -eq "${found[${versioning}]}" ]]; then + return 0 + fi for versioning in ${!required[@]}; do if [[ "${required[${versioning}]}" -lt "${found[${versioning}]}" ]]; then return 0 From a4d3b80081b876317bcafe749081dfe15f17f28c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Jun 2023 15:41:42 +0200 Subject: [PATCH 068/549] Implement a function to test if YAML contain given key --- bash/utility_functions.bash | 18 +++++++++++ tests/unit_tests_utility_functions.bash | 42 +++++++++++++++++++++---- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 0d41be2..d04f138 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -25,6 +25,24 @@ function Element_In_Array_Matches() return 1 } +# NOTE: This function needs to be called with the YAML string as first argument +# and the section keys as remaining arguments. If YAML is invalid, return false. +function Has_YAML_String_Given_Key() +{ + local yaml_string section key + if [[ $# -lt 2 ]]; then + Print_Internal_And_Exit "Function '${FUNCNAME}' called with less than 2 arguments." + fi + yaml_string=$1; shift + section="$(printf '.%s' "${@:1:$#-1}")" # All arguments but last + key=${@: -1} # Last argument + if [[ $(yq "${section}"' | has("'"${key}"'")' <<< "${yaml_string}") = 'true' ]]; then + return 0 + else + return 1 + fi +} + function Print_Line_of_Equals() { local length indentation prefix postfix diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 7b4a212..d50358f 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -9,14 +9,44 @@ # NOTE: No need to source utility functions code, as the test runner uses them! -function Unit_Test__remove-comments-in-existing-file() +function Unit_Test__utility-has-YAML-string-given-key() +{ + # Test case: Wrong call + ( Has_YAML_String_Given_Key &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Wrong call to function succeeded." + return 1 + fi + ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c' &> /dev/null ) + if [[ $? -ne 0 ]] ; then + Print_Error "Existing key '{a: {b: {c:}}}' not found." + return 1 + fi + ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' &> /dev/null ) + if [[ $? -ne 0 ]] ; then + Print_Error "Existing key '{a: {b:}}' not found." + return 1 + fi + ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' &> /dev/null ) + if [[ $? -ne 0 ]] ; then + Print_Error "Existing key '{a:}' not found." + return 1 + fi + ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'nope' &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Not existing key found." + return 1 + fi +} + +function Unit_Test__utility-remove-comments-in-existing-file() { local number_of_lines cd "${HYBRIDT_folder_to_run_tests}" # Test case 0 ( Remove_Comments_In_Existing_File 'not_existing_file.txt' &> /dev/null ) if [[ $? -eq 0 ]] ; then - Print_Error "Remove comments on not existent file did not fail!" + Print_Error "Remove comments on not existent file did not fail." return 1 fi # Test case 1 @@ -24,7 +54,7 @@ function Unit_Test__remove-comments-in-existing-file() printf ' # Comment\n' > "${file_containing_one_commented_line_only}" Remove_Comments_In_File "${file_containing_one_commented_line_only}" if [[ -s "${file_containing_one_commented_line_only}" ]]; then - Print_Error "File \"${file_containing_one_commented_line_only}\" not empty!" + Print_Error "File \"${file_containing_one_commented_line_only}\" not empty." return 1 fi rm "${file_containing_one_commented_line_only}" @@ -34,7 +64,7 @@ function Unit_Test__remove-comments-in-existing-file() number_of_lines=$(wc -l < "${file_containing_no_comments}") Remove_Comments_In_File "${file_containing_no_comments}" if [[ $(wc -l < "${file_containing_no_comments}") -ne ${number_of_lines} ]]; then - Print_Error "Removing comments in \"${file_containing_no_comments}\" file failed!" + Print_Error "Removing comments in \"${file_containing_no_comments}\" file failed." return 1 fi rm "${file_containing_no_comments}" @@ -44,7 +74,7 @@ function Unit_Test__remove-comments-in-existing-file() number_of_lines=$(wc -l < "${file_containing_three_commented_lines}") Remove_Comments_In_File "${file_containing_three_commented_lines}" if (( $(wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3 )); then - Print_Error "Removing comments in \"${file_containing_three_commented_lines}\" file failed!" + Print_Error "Removing comments in \"${file_containing_three_commented_lines}\" file failed." return 1 fi rm "${file_containing_three_commented_lines}" @@ -53,7 +83,7 @@ function Unit_Test__remove-comments-in-existing-file() printf 'Hello %% Comment\n' > "${file_containing_one_line_with_an_inline_comment}" Remove_Comments_In_File "${file_containing_one_line_with_an_inline_comment}" '%' if [[ $(cat "${file_containing_one_line_with_an_inline_comment}") != 'Hello' ]]; then - Print_Error "Removing comments in \"${file_containing_one_line_with_an_inline_comment}\" file failed!" + Print_Error "Removing comments in \"${file_containing_one_line_with_an_inline_comment}\" file failed." return 1 fi rm "${file_containing_one_line_with_an_inline_comment}" From cd52bfce825b84ba97d4fc0f6d76d0cc5bb1447b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Jun 2023 15:42:23 +0200 Subject: [PATCH 069/549] Redirect internal error messages to standard error --- bash/logger.bash | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bash/logger.bash b/bash/logger.bash index 1f361a3..8cad999 100644 --- a/bash/logger.bash +++ b/bash/logger.bash @@ -106,9 +106,11 @@ function __static__Logger() color='\e[91m' exec 1>&2 ;; # here stdout to stderr! INTERNAL ) - color='\e[38;5;202m' ;;& # ;;& means go on in case matching following -> do *) + color='\e[38;5;202m' + exec 1>&2 ;; # here stdout to stderr! INFO ) - color='\e[92m' ;;& + color='\e[92m' # ;;& means go on in case matching following -> do *) + ;;& ATTENTION ) color='\e[38;5;200m' ;;& WARNING ) From 40ffc9435ec24d6798d776f1eb77965251ffeb64 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Jun 2023 16:12:08 +0200 Subject: [PATCH 070/549] Implement a function to read a given key from a YAML string --- bash/utility_functions.bash | 17 +++++++++++++++- tests/unit_tests_utility_functions.bash | 26 ++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index d04f138..35ad981 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -26,7 +26,7 @@ function Element_In_Array_Matches() } # NOTE: This function needs to be called with the YAML string as first argument -# and the section keys as remaining arguments. If YAML is invalid, return false. +# and the section key(s) as remaining argument(s). If YAML is invalid, return false. function Has_YAML_String_Given_Key() { local yaml_string section key @@ -43,6 +43,21 @@ function Has_YAML_String_Given_Key() fi } +# NOTE: This function needs to be called with the YAML string as first argument +# and the section key(s) as remaining argument(s). If YAML is invalid, return false. +function Read_From_YAML_String_Given_Key() +{ + local yaml_string section + if [[ $# -lt 2 ]]; then + Print_Internal_And_Exit "Function '${FUNCNAME}' called with less than 2 arguments." + elif ! Has_YAML_String_Given_Key "$@"; then + Print_Internal_And_Exit "Function '${FUNCNAME}' called with YAML string not containing given key." + fi + yaml_string=$1; shift + section="$(printf '.%s' "$@")" + yq "${section}" <<< "${yaml_string}" +} + function Print_Line_of_Equals() { local length indentation prefix postfix diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index d50358f..d41dc10 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -11,7 +11,6 @@ function Unit_Test__utility-has-YAML-string-given-key() { - # Test case: Wrong call ( Has_YAML_String_Given_Key &> /dev/null ) if [[ $? -eq 0 ]] ; then Print_Error "Wrong call to function succeeded." @@ -39,6 +38,31 @@ function Unit_Test__utility-has-YAML-string-given-key() fi } +function Unit_Test__utility-read-from-YAML-string-given-key() +{ + ( Read_From_YAML_String_Given_Key &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Wrong call to function succeeded." + return 1 + fi + ( Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'nope' &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Not existing key successfully read." + return 1 + fi + local result + result=$(Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c') + if [[ ${result} -ne 42 ]] ; then + Print_Error "Reading scalar key failed." + return 1 + fi + result=$(Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b') + if [[ "${result}" != 'c: 42' ]] ; then + Print_Error "Reading map key failed." + return 1 + fi +} + function Unit_Test__utility-remove-comments-in-existing-file() { local number_of_lines From 142a935795b927418c0fdcdba65088cb0daadad6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Jun 2023 18:13:12 +0200 Subject: [PATCH 071/549] Add function to remove YAML key from string and improve code The YAML utility functions have been now changed to validate the YAML string in input and tests adjusted accordingly. --- bash/utility_functions.bash | 31 ++++++++++++++--- tests/unit_tests_utility_functions.bash | 45 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 35ad981..8a703db 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -26,7 +26,8 @@ function Element_In_Array_Matches() } # NOTE: This function needs to be called with the YAML string as first argument -# and the section key(s) as remaining argument(s). If YAML is invalid, return false. +# and the section key(s) as remaining argument(s). If YAML is invalid, +# an error is printed and the function exits. function Has_YAML_String_Given_Key() { local yaml_string section key @@ -34,6 +35,9 @@ function Has_YAML_String_Given_Key() Print_Internal_And_Exit "Function '${FUNCNAME}' called with less than 2 arguments." fi yaml_string=$1; shift + if ! yq <<< "${yaml_string}" &> /dev/null; then + Print_Internal_And_Exit "Function '${FUNCNAME}' called with invalid YAML string." + fi section="$(printf '.%s' "${@:1:$#-1}")" # All arguments but last key=${@: -1} # Last argument if [[ $(yq "${section}"' | has("'"${key}"'")' <<< "${yaml_string}") = 'true' ]]; then @@ -44,18 +48,35 @@ function Has_YAML_String_Given_Key() } # NOTE: This function needs to be called with the YAML string as first argument -# and the section key(s) as remaining argument(s). If YAML is invalid, return false. +# and the section key(s) as remaining argument(s). If YAML does not contain +# the key (or it is invalid) the function exits with an error. function Read_From_YAML_String_Given_Key() { - local yaml_string section + local yaml_string key + if [[ $# -lt 2 ]]; then + Print_Internal_And_Exit "Function '${FUNCNAME}' called with less than 2 arguments." + elif ! Has_YAML_String_Given_Key "$@"; then + Print_Internal_And_Exit "Function '${FUNCNAME}' called with YAML string not containing given key." + fi + yaml_string=$1; shift + key="$(printf '.%s' "$@")" + yq "${key}" <<< "${yaml_string}" +} + +# NOTE: This function needs to be called with the YAML string as first argument +# and the section key(s) as remaining argument(s). If YAML does not contain +# the key (or it is invalid) the function exits with an error. +function Print_YAML_String_Without_Given_Key() +{ + local yaml_string key if [[ $# -lt 2 ]]; then Print_Internal_And_Exit "Function '${FUNCNAME}' called with less than 2 arguments." elif ! Has_YAML_String_Given_Key "$@"; then Print_Internal_And_Exit "Function '${FUNCNAME}' called with YAML string not containing given key." fi yaml_string=$1; shift - section="$(printf '.%s' "$@")" - yq "${section}" <<< "${yaml_string}" + key="$(printf '.%s' "$@")" + yq 'del('"${key}"')' <<< "${yaml_string}" } function Print_Line_of_Equals() diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index d41dc10..fcf5530 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -16,6 +16,11 @@ function Unit_Test__utility-has-YAML-string-given-key() Print_Error "Wrong call to function succeeded." return 1 fi + ( Has_YAML_String_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Function called on invalid YAML succeeded." + return 1 + fi ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c' &> /dev/null ) if [[ $? -ne 0 ]] ; then Print_Error "Existing key '{a: {b: {c:}}}' not found." @@ -45,6 +50,11 @@ function Unit_Test__utility-read-from-YAML-string-given-key() Print_Error "Wrong call to function succeeded." return 1 fi + ( Read_From_YAML_String_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Function called on invalid YAML succeeded." + return 1 + fi ( Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'nope' &> /dev/null ) if [[ $? -eq 0 ]] ; then Print_Error "Not existing key successfully read." @@ -63,6 +73,41 @@ function Unit_Test__utility-read-from-YAML-string-given-key() fi } +function Unit_Test__utility-print-YAML-string-without-given-key() +{ + ( Print_YAML_String_Without_Given_Key &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Wrong call to function succeeded." + return 1 + fi + ( Print_YAML_String_Without_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Function called on invalid YAML succeeded." + return 1 + fi + ( Print_YAML_String_Without_Given_Key $'a:\n b: 42\n' 'nope' &> /dev/null ) + if [[ $? -eq 0 ]] ; then + Print_Error "Not existing key successfully deleted." + return 1 + fi + local result + result=$(Print_YAML_String_Without_Given_Key $'a: 42\nb: 17\n' 'b') + if [[ "${result}" != 'a: 42' ]] ; then + Print_Error "Deleting scalar key failed." + return 1 + fi + result=$(Print_YAML_String_Without_Given_Key $'a:\n b:\n c: 17\n' 'a' 'b') + if [[ "${result}" != 'a: {}' ]] ; then + Print_Error "Deleting map key failed." + return 1 + fi + result=$(Print_YAML_String_Without_Given_Key $'a: 42\n' 'a') + if [[ "${result}" != '{}' ]] ; then + Print_Error "Deleting only existing key failed." + return 1 + fi +} + function Unit_Test__utility-remove-comments-in-existing-file() { local number_of_lines From 0158e32f126396767ceddea5342cc06a62a097b2 Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Tue, 30 May 2023 15:57:26 +0200 Subject: [PATCH 072/549] Add vHLLE blackbox --- tests/mocks/vhlle_black-box.py | 204 +++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100755 tests/mocks/vhlle_black-box.py diff --git a/tests/mocks/vhlle_black-box.py b/tests/mocks/vhlle_black-box.py new file mode 100755 index 0000000..17f47eb --- /dev/null +++ b/tests/mocks/vhlle_black-box.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +import random +import time +import datetime + +def print_terminal_start(): + # generated with https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 + Terminal_Out = """###########################################################################################################\n + _________ _____ _____ _________ ___ ___ + / _____/ / \ / _ \ / _____// | \ \n + \_____ \ / \ / \ / /_\ \ \_____ \/ ~ \ \n + / \/ Y \/ | \/ \ Y / \n + /_______ /\____|__ /\____|__ /_______ /\___|_ / \n + \/ \/ \/ \/ \/ \n + .__ ___. .__ .___ \n + ____ ______ _ __ | |__ ___.__.\_ |_________|__| __| _/ ________________ \n + / \_/ __ \ \/ \/ / ______ | | < | | | __ \_ __ \ |/ __ | ______ _/ __ \_ __ \__ \ \n + | | \ ___/\ / /_____/ | Y \___ | | \_\ \ | \/ / /_/ | /_____/ \ ___/| | \// __ \_ \n + |___| /\___ >\/\_/ |___| / ____| |___ /__| |__\____ | \___ >__| (____ / \n + \/ \/ \/\/ \/ \/ \/ \/ \n + __ ___ ___ .____ .____ ___________ __ \n + / / ___ __/ | \| | | | \_ _____/ \ \ \n + / / \ \/ / ~ \ | | | | __)_ \ \ \n + \ \ \ /\ Y / |___| |___ | \ / / \n + \_\ \_/ \___|_ /|_______ \_______ \/_______ / /_/ \n + \/ \/ \/ \/ \n + + \n###########################################################################################################\n""" + + print(Terminal_Out) + print("running hydro") + return + +def read_parameters(configFile): + # list of possible parameters + # parameter name, string to write, default value (0 where none) + parameters = { + "freezeoutOnly": ["freezeoutOnly", 0], + "eosType": ["eosType", 0], + "eosTypeHadron": ["eosTypeHadron", 0], + "nx": ["nx", 0], + "ny": ["ny", 0], + "nz": ["nz", 0], + "icModel": ["icModel", 0], + "glauberVar": ["glauberVar", 1], + "xmin": ["xmin", 0], + "xmax": ["xmax", 0], + "ymin": ["ymin", 0], + "ymax": ["ymax", 0], + "etamin": ["etamin", 0], + "etamax": ["etamax", 0], + "tau0": ["tau0", 0], + "tauMax": ["tauMax", 0], + "tauGridResize": ["tauGridResize", 4.0], + "dtau": ["dtau", 0], + "e_crit": ["e_crit", 0], + "zetaSparam": ["zeta/s param", 0], + "etaSparam": ["etaSparam", 0], + "etaS": ["eta/s", 0], + "al": ["al", 0], + "ah": ["ah", 0], + "aRho": ["aRho", 0], + "etaSMin": ["etaSMin", 0], + "T0": ["T0", 0], + "eEtaSmin": ["eEtaSmin", 0], + "zetaS": ["zeta/s" , 0], + "epsilon0": ["epsilon0", 0], + "Rg": ["Rgt", 0], + "Rgz": ["Rgz", 0], + "smoothingType": ["smoothingType", 0], + "impactPar": ["impactPar", 0], + "s0ScaleFactor": ["s0ScaleFactor", 0], + "VTK_output": ["VTK output", 0], + "VTK_output_values": ["VTK output values", " "], + "VTK_cartesian": ["VTK cartesian", 0] + } + + if os.path.isfile(configFile): + for line in open(configFile, "r"): + line = line.split() + for key in parameters: + if key in line: parameters[key][1] = line[1] + return parameters + +def print_parameters(): + print("vhlle: reading parameters from ", args.params) + print("vhlle: command line parameters are:") + print("collision system:") + print("ini.state input: ", args.ISinput) + print("output directory: ", args.outputDir) + parameters = read_parameters(args.params) + print("====== parameters ======") + print("outputDir = ", args.outputDir) + for key, value in parameters.items(): + print(value[0], " = ", value[1]) + print("======= end parameters =======") + return + +def create_folder(outputDirSpecified): + # create path if needed + if outputDirSpecified: + # fix format + if args.outputDir[-1] != "/": + args.outputDir += "/" + if not os.path.exists(args.outputDir): + os.makedirs(args.outputDir) + print("mkdir returns: 0") + return + +def check_command_line(): + # check if there are command# check if there is a config file + if len(sys.argv) < 2: + print("no CL params - exiting.") + sys.exit() + # check if config file exists + if not args.params == "": + if not os.path.isfile(args.params): + print("cannot open parameters file ", args.params) + sys.exit() + return + +def check_eos(): + # no real check at this point we assume the eos folder exists + # where hlle_visc executable is + # to be implemented later, override with True now + eosPath = "" + eosExists = os.path.exists(eosPath) + eosExists = True + if eosExists: + print("EoSaux: table eos/chiraleos.dat read, [emin,emax,nmin,nmax] = 0 146 0 6") + print("EoSaux: table eos/chiralsmall.dat read, [emin,emax,nmin,nmax] = 0 1.46 0 0.3") + print("EoSSMASH: table eos/hadgas_eos_SMASH.dat read, [emin,emax,nbmin,nbmax,qmin,qmax] = 0 1 0 0.5 -0.1 0.4") + else: + print("I/O error with eos/chiraleos.dat") + return + +def read_initial_state(): + messageExample = """particle E = 1442.11 Nbar = 367 Ncharge = 148 Ns = 0 +IC SMASH, center: 0 0 4.36586 1.18914 +hydrodynamic E = 1442.1 Pz = 36.5687 Nbar = 367 Ncharge = 148.001 Ns = 0 +Px = -0.463514 Py = 0.556379 +initial_entropy S_ini = 7880.36 +IC done +Init time = 9 [sec]""" + + print("fluid allocation done") + if os.path.exists(args.ISinput): + print(messageExample) + else: + print("I/O error with") + +def print_timestep(timestep): + randomList = [] + for i in range(1, 10): + number = round(random.random(), 2) + randomList.append(str(number)) + if timestep > 10: + randomList[8] = "-nan" + print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*randomList)) + return + + +def run_hydro(outputDirSpecified): + # create freezout hypersurface file + # only if output directory is specified + if outputDirSpecified: freezeout = open(args.outputDir+"hypersurface.dat", "w") + variableList = ["tau", "E", "Efull", "Nb", "Sfull", "EtotSurf", "elements", "susp.", "%cut"] + # run the black box + print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*variableList)) + for ts in range(1,13): + print_timestep(ts) + time.sleep(0.1) + if outputDirSpecified: + freezeout.write("This is a random line written at {}".format(datetime.datetime.now())) + freezeout.close() + return + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-params", required=False, + help="Path to vhlle_config", + default="") + parser.add_argument("-ISinput", required=False, + help="Path to initial state file", + default="") + parser.add_argument("-outputDir", required=False, + help="Path to the output folder", + default="") + args = parser.parse_args() + outputDirGiven = not args.outputDir == "" + + print_terminal_start() + check_command_line() + print_parameters() + check_eos() + read_initial_state() + create_folder(outputDirGiven) + run_hydro(outputDirGiven) + \ No newline at end of file From 2349071f0a0727e503a93a22dba23886fdb38039 Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Tue, 30 May 2023 16:35:04 +0200 Subject: [PATCH 073/549] Add no config behavior --- tests/mocks/vhlle_black-box.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/mocks/vhlle_black-box.py b/tests/mocks/vhlle_black-box.py index 17f47eb..d16cc02 100755 --- a/tests/mocks/vhlle_black-box.py +++ b/tests/mocks/vhlle_black-box.py @@ -109,6 +109,8 @@ def create_folder(outputDirSpecified): if not os.path.exists(args.outputDir): os.makedirs(args.outputDir) print("mkdir returns: 0") + else: + print("mkdir: missing operand") return def check_command_line(): @@ -138,6 +140,24 @@ def check_eos(): print("I/O error with eos/chiraleos.dat") return +def check_config(outputDirSpecified): + if args.params == "": + print("""EoHadron: table eos/eosHadronLog.dat read, [emin,emax,nmin,nmax] = 0.00336897 74.2066 -44.5239 44.5239 -44.5239 44.5239" +fluid allocation done +icModel = 0 not implemented +IC done +Init time = 6 [sec]""") + create_folder(outputDirSpecified) + variableList = ["tau", "E", "Efull", "Nb", "Sfull", "EtotSurf", "elements", "susp.", "%cut"] + valueList = ["0", "-0", "-0", "0", "-0", "0", "0", "0", "-nan"] + print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*variableList)) + print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*valueList)) + sys.exit() + return + + + + def read_initial_state(): messageExample = """particle E = 1442.11 Nbar = 367 Ncharge = 148 Ns = 0 IC SMASH, center: 0 0 4.36586 1.18914 @@ -197,6 +217,7 @@ def run_hydro(outputDirSpecified): print_terminal_start() check_command_line() print_parameters() + check_config(outputDirGiven) check_eos() read_initial_state() create_folder(outputDirGiven) From e90b6e98a1ba537950608c6bf7ce3ad3dcd9e347 Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Fri, 2 Jun 2023 10:32:56 +0200 Subject: [PATCH 074/549] Modify exit code - sys.exit(1) is added where needed - check_config() is renamed to exit_without_config() for readability --- tests/mocks/vhlle_black-box.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/mocks/vhlle_black-box.py b/tests/mocks/vhlle_black-box.py index d16cc02..26e5e3c 100755 --- a/tests/mocks/vhlle_black-box.py +++ b/tests/mocks/vhlle_black-box.py @@ -117,12 +117,12 @@ def check_command_line(): # check if there are command# check if there is a config file if len(sys.argv) < 2: print("no CL params - exiting.") - sys.exit() + sys.exit(1) # check if config file exists if not args.params == "": if not os.path.isfile(args.params): print("cannot open parameters file ", args.params) - sys.exit() + sys.exit(1) return def check_eos(): @@ -138,9 +138,10 @@ def check_eos(): print("EoSSMASH: table eos/hadgas_eos_SMASH.dat read, [emin,emax,nbmin,nbmax,qmin,qmax] = 0 1 0 0.5 -0.1 0.4") else: print("I/O error with eos/chiraleos.dat") + sys.exit(1) return -def check_config(outputDirSpecified): +def exit_without_config(outputDirSpecified): if args.params == "": print("""EoHadron: table eos/eosHadronLog.dat read, [emin,emax,nmin,nmax] = 0.00336897 74.2066 -44.5239 44.5239 -44.5239 44.5239" fluid allocation done @@ -152,12 +153,9 @@ def check_config(outputDirSpecified): valueList = ["0", "-0", "-0", "0", "-0", "0", "0", "0", "-nan"] print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*variableList)) print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*valueList)) - sys.exit() + sys.exit(1) return - - - def read_initial_state(): messageExample = """particle E = 1442.11 Nbar = 367 Ncharge = 148 Ns = 0 IC SMASH, center: 0 0 4.36586 1.18914 @@ -171,7 +169,8 @@ def read_initial_state(): if os.path.exists(args.ISinput): print(messageExample) else: - print("I/O error with") + print("I/O error with",args.ISinput) + sys.exit(1) def print_timestep(timestep): randomList = [] @@ -217,9 +216,8 @@ def run_hydro(outputDirSpecified): print_terminal_start() check_command_line() print_parameters() - check_config(outputDirGiven) + exit_without_config(outputDirGiven) check_eos() read_initial_state() create_folder(outputDirGiven) - run_hydro(outputDirGiven) - \ No newline at end of file + run_hydro(outputDirGiven) \ No newline at end of file From cd4b5dfc203d32b1448cd273cc4e23f94804180b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 2 Jun 2023 10:53:20 +0200 Subject: [PATCH 075/549] Implement function to ensure variables are set and not empty --- bash/utility_functions.bash | 17 ++++++++++++++++- tests/unit_tests_utility_functions.bash | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 8a703db..d050e40 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -171,10 +171,25 @@ function Call_Function_If_Existing_Or_No_Op() fi } +function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { + local variable_name label + for variable_name in "$@"; do + if [[ ! -v "${variable_name}" ]]; then + label='not set' + elif [[ "${!variable_name}" = '' ]]; then + label='set but empty' + fi + if [[ "${label}" != '' ]]; then + Print_Internal_And_Exit\ + "Variable \"${variable_name}\" ${label} in function \"${FUNCNAME[1]}\"." + fi + done +} + function Make_Functions_Defined_In_This_File_Readonly() { # Here we assume all functions are defined with the same stile, - # including empty parenteses and the braces on new lines! I.e. + # including empty parentheses and the braces on new lines! I.e. # # function nameOfTheFunction() # diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index fcf5530..1ec6d3f 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -157,3 +157,25 @@ function Unit_Test__utility-remove-comments-in-existing-file() fi rm "${file_containing_one_line_with_an_inline_comment}" } + +function Unit_Test__utility-check-shell-variables() +{ + local foo # Note that 'local' does not set a variable + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Checking unset variable succeeded.' + return 1 + fi + foo='' + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Checking set but empty variable succeeded.' + return 1 + fi + foo='bar' + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking set and not empty variable failed.' + return 1 + fi +} From ed0fc45dc3db38cd08383c8514e5712309ac02fe Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 2 Jun 2023 10:59:37 +0200 Subject: [PATCH 076/549] Express comment in code check --- bash/software_input_functionality.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 2a9bdc1..753d8e5 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -28,9 +28,9 @@ function Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File() esac } -# NOTE: The following functions use the local variables of the calling function function __static__Replace_Keys_Into_YAML_File() { + Ensure_That_Given_Variables_Are_Set_And_Not_Empty base_input_file keys_to_be_replaced # Use yq -P to bring all YAML to same format (crucial for later check on number of lines) if ! yq -P --inplace "${base_input_file}" 2> /dev/null; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ @@ -58,6 +58,7 @@ function __static__Replace_Keys_Into_YAML_File() function __static__Replace_Keys_Into_Txt_File() { + Ensure_That_Given_Variables_Are_Set_And_Not_Empty base_input_file keys_to_be_replaced # Impose that both file and new keys have two entries per line if ! awk 'NF != 2 { exit 1 }' "${base_input_file}"; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ From 57f78d26d85e3bedbdbc6259c685a3a960f95c6e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 2 Jun 2023 15:51:34 +0200 Subject: [PATCH 077/549] Implement function to ensure variables are simply set --- bash/utility_functions.bash | 20 +++++++++++++------- tests/unit_tests_utility_functions.bash | 20 ++++++++++++++++++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index d050e40..6da732c 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -171,17 +171,23 @@ function Call_Function_If_Existing_Or_No_Op() fi } -function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { - local variable_name label +function Ensure_That_Given_Variables_Are_Set() { + local variable_name for variable_name in "$@"; do if [[ ! -v "${variable_name}" ]]; then - label='not set' - elif [[ "${!variable_name}" = '' ]]; then - label='set but empty' + Print_Internal_And_Exit\ + "Variable \"${variable_name}\" not set in function \"${FUNCNAME[1]}\"." fi - if [[ "${label}" != '' ]]; then + done +} + +function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { + Ensure_That_Given_Variables_Are_Set "$@" + local variable_name + for variable_name in "$@"; do + if [[ "${!variable_name}" = '' ]]; then Print_Internal_And_Exit\ - "Variable \"${variable_name}\" ${label} in function \"${FUNCNAME[1]}\"." + "Variable \"${variable_name}\" set but empty in function \"${FUNCNAME[1]}\"." fi done } diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 1ec6d3f..75d22d7 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -158,15 +158,31 @@ function Unit_Test__utility-remove-comments-in-existing-file() rm "${file_containing_one_line_with_an_inline_comment}" } -function Unit_Test__utility-check-shell-variables() +function Unit_Test__utility-check-shell-variables-set() { local foo # Note that 'local' does not set a variable - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null ) + ( Ensure_That_Given_Variables_Are_Set foo &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Checking unset variable succeeded.' return 1 fi foo='' + ( Ensure_That_Given_Variables_Are_Set foo &> /dev/null ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking set but empty variable failed.' + return 1 + fi + foo='bar' + ( Ensure_That_Given_Variables_Are_Set foo ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking set and not empty variable failed.' + return 1 + fi +} + +function Unit_Test__utility-check-shell-variables-set-not-empty() +{ + local foo='' ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Checking set but empty variable succeeded.' From 1965dee097751c0e3f7c9dd982041aaa181e5364 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 2 Jun 2023 18:08:55 +0200 Subject: [PATCH 078/549] Add tests and fix bug in functions to check variables --- bash/utility_functions.bash | 32 ++++++++++++--- tests/unit_tests_utility_functions.bash | 53 +++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 6da732c..a5bc8ac 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -171,24 +171,46 @@ function Call_Function_If_Existing_Or_No_Op() fi } +# NOTE: In Bash there are several ways to declare variables and somehow these +# are (apparently) not consistent w.r.t. resulting set when tested via +# [[ -v ... ]] and therefore here we decided to use the 'declare' command. +# For example, 'local foo' is not setting a variable (the -v test fails, which +# makes sense). However, also 'foo=()' is not setting the array variable +# which is not what we want here. In this function "set" means declared in +# some way and 'foo=()' should not result in an error. function Ensure_That_Given_Variables_Are_Set() { local variable_name for variable_name in "$@"; do - if [[ ! -v "${variable_name}" ]]; then + if ! declare -p "${variable_name}" &>/dev/null; then Print_Internal_And_Exit\ "Variable \"${variable_name}\" not set in function \"${FUNCNAME[1]}\"." fi done } +# NOTE: See Ensure_That_Given_Variables_Are_Set comment. Moreover, since we indirectly +# access the variable through its name, we need to check separately the case +# in which the variable is an array. In bash, the "array length" of a non-array +# variable is 1 (as accessing an array without index returns the first entry). +# Hence, for 'foo=""', ${#foo[@]} would return 1 and a non zero length is not +# synonym of a non-empty variable. function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { - Ensure_That_Given_Variables_Are_Set "$@" local variable_name for variable_name in "$@"; do - if [[ "${!variable_name}" = '' ]]; then - Print_Internal_And_Exit\ - "Variable \"${variable_name}\" set but empty in function \"${FUNCNAME[1]}\"." + # The following can be done using the "${ref@A}" bash-5 expansion which + # would return the variable declared attributes (e.g. 'a' for arrays). + if [[ $(declare -p "${variable_name}") =~ ^declare\ -[aA] ]]; then + declare -n ref=${variable_name} + if [[ ${#ref[@]} -ne 0 ]]; then + continue + fi + else + if [[ "${!variable_name}" != '' ]]; then + continue + fi fi + Print_Internal_And_Exit\ + "Variable \"${variable_name}\" unset or empty in function \"${FUNCNAME[1]}\"." done } diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 75d22d7..8c59a7f 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -160,12 +160,17 @@ function Unit_Test__utility-remove-comments-in-existing-file() function Unit_Test__utility-check-shell-variables-set() { - local foo # Note that 'local' does not set a variable ( Ensure_That_Given_Variables_Are_Set foo &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Checking unset variable succeeded.' return 1 fi + local foo + ( Ensure_That_Given_Variables_Are_Set foo &> /dev/null ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking set but unassigned variable failed.' + return 1 + fi foo='' ( Ensure_That_Given_Variables_Are_Set foo &> /dev/null ) if [[ $? -ne 0 ]]; then @@ -175,14 +180,32 @@ function Unit_Test__utility-check-shell-variables-set() foo='bar' ( Ensure_That_Given_Variables_Are_Set foo ) if [[ $? -ne 0 ]]; then - Print_Error 'Checking set and not empty variable failed.' + Print_Error 'Checking set and not empty string-variable failed.' + return 1 + fi + foo=() + ( Ensure_That_Given_Variables_Are_Set foo ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking empty array variable failed.' + return 1 + fi + foo=( '' ) + ( Ensure_That_Given_Variables_Are_Set foo ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking array variable set to one empty entry failed.' return 1 fi } function Unit_Test__utility-check-shell-variables-set-not-empty() { - local foo='' + local foo + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Checking set but unassigned variable succeeded.' + return 1 + fi + foo='' ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Checking set but empty variable succeeded.' @@ -194,4 +217,28 @@ function Unit_Test__utility-check-shell-variables-set-not-empty() Print_Error 'Checking set and not empty variable failed.' return 1 fi + foo=() + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null) + if [[ $? -eq 0 ]]; then + Print_Error 'Checking empty array variable succeeded.' + return 1 + fi + foo=( '' ) + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking array variable set to one empty entry failed.' + return 1 + fi + declare -A bar + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Checking set but unassigned associative array succeeded.' + return 1 + fi + bar['key']='' + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar &> /dev/null ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking set associative array failed.' + return 1 + fi } From 368e232baacfc0d01d832b107a380df6fbd38e9e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 2 Jun 2023 19:14:52 +0200 Subject: [PATCH 079/549] Add implementation of validation of configuration file The parsing is still missing and, at the moment, the declaration of valid keys is in the function that validate the file. These will probably rather become global constants and the parsing of the keys will be added next. --- bash/configuration_parser.bash | 139 ++++++++++++++++++++++- bash/global_variables.bash | 6 +- bash/source_codebase_files.bash | 1 + tests/unit_tests_configuration.bash | 165 +++++++++++++++++++++++++++- 4 files changed, 299 insertions(+), 12 deletions(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index e743b78..97abeee 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -9,15 +9,142 @@ function Validate_And_Parse_Configuration_File() { - Print_Not_Implemented_Function_Error + Remove_Comments_In_File "${HYBRID_configuration_file}" # This checks for existence, too + # NOTE: We do not want to change the config file, hence we read sections into + # local (string) variables and then use these as input to yq to read keys + # and deleting the key from the variable content. + __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File + __static__Abort_If_Sections_Are_Violating_Any_Requirement + __static__Abort_If_Invalid_Keys_Were_Used # Needed steps: - # 1. Check existence 'HYBRID_configuration_file' - # 2. Validate YAML using yq (remove all comments first) - # 3. Validate top-level sections against 'HYBRID_valid_configuration_sections' - # -> here also the ordering should be validated - # 4. Extract and parse 'Hybrid-handler' section + # 3. Move valid keys to global constant arrays (?) + # 4. Parse 'Hybrid-handler' section # 5. Parse software sections setting all needed variables # -> see global_variables.bash # -> the software input keys must not be validated but simply put into 'HYBRID_software_input' # 6. Validate software to be later run for the given software sections (?) + # +} + +function __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File() +{ + if ! yq "${HYBRID_configuration_file}" &> /dev/null; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + 'The handler configuration file does not contain valid YAML syntax.' + fi +} + +function __static__Abort_If_Sections_Are_Violating_Any_Requirement() +{ + local input_section_labels label index valid_software_label software_sections_indices + # Here word splitting separates keys into array entries, hence we assume keys do not contain spaces! + input_section_labels=( $(yq 'keys | .[]' "${HYBRID_configuration_file}") ) + for label in "${input_section_labels[@]}"; do + if Element_In_Array_Equals_To "${label}" "${HYBRID_valid_auxiliary_configuration_sections[@]}"; then + continue + else + for index in "${!HYBRID_valid_software_configuration_sections[@]}"; do + valid_software_label=${HYBRID_valid_software_configuration_sections[index]} + if [[ "${valid_software_label}" = "${label}" ]]; then + software_sections_indices+=( ${index} ) + continue 2 + fi + done + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + "Invalid section \"${label}\" found in the handler configuration file." + fi + done + # Here all given sections are valid. Check possible duplicates/holes and ordering using the stored indices + if [[ ${#software_sections_indices[@]} -eq 0 ]]; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + 'No software section was specified in the handler configuration file.' + elif [[ ${#software_sections_indices[@]} -gt 1 ]]; then + if [[ $(sort -u <(printf '%d\n' "${software_sections_indices[@]}") | wc -l)\ + -ne ${#software_sections_indices[@]} ]]; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + 'The same software section in the handler configuration file cannot be repeated.' + fi + if ! sort -C <(printf '%d\n' "${software_sections_indices[@]}"); then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + 'Software sections in the handler configuration file are out of order.' + fi + local gaps_between_indices + gaps_between_indices=$(awk 'NR>1{print $1-x}{x=$1}' <(printf '%d\n' "${software_sections_indices[@]}") | sort -u) + if [[ "${gaps_between_indices}" != '1' ]]; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + 'Missing software section(s) in the handler configuration file.' + fi + fi +} + +function __static__Abort_If_Invalid_Keys_Were_Used() +{ + local -r common_software_keys=( + 'Executable' + 'Input_file' + 'Input_keys' + ) + local -r yaml_config="$(< "${HYBRID_configuration_file}")" + local valid_keys invalid_report + invalid_report=() # Use array entries to split lines to feed into logger + # Hybrid-Handler section + valid_keys=() + # IC section + valid_keys=( + "${common_software_keys[@]}" + ) + __static__Validate_Keys_Of_Section 'IC' + # Hydro section + valid_keys=( + "${common_software_keys[@]}" + ) + __static__Validate_Keys_Of_Section 'Hydro' + # Sampler section + valid_keys=( + "${common_software_keys[@]}" + ) + __static__Validate_Keys_Of_Section 'Sampler' + # Afterburner section + valid_keys=( + "${common_software_keys[@]}" + ) + __static__Validate_Keys_Of_Section 'Afterburner' + # Abort if some validation failed + if [[ "${#invalid_report[@]}" -ne 0 ]]; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + 'Following invalid keys found in the handler configuration file:'\ + '---------------------------------------------------------------'\ + "${invalid_report[@]/%/:}"\ + '---------------------------------------------------------------' + fi +} + +function __static__Validate_Keys_Of_Section() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty yaml_config + Ensure_That_Given_Variables_Are_Set invalid_report + local -r section_label=$1 + local invalid_keys yaml_section + if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then + yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" + invalid_keys=( $(__static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string "${yaml_section}") ) + if [[ ${#invalid_keys[@]} -ne 0 ]]; then + invalid_report+=( " ${section_label}" "${invalid_keys[@]/#/ }") + fi + fi +} + +# NOTE: It is assumed that keys do not contain spaces! +function __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty valid_keys + local input_keys key invalid_keys + input_keys=( $(yq 'keys | .[]' <<< "$1") ) + invalid_keys=() + for key in "${input_keys[@]}"; do + if ! Element_In_Array_Equals_To "${key}" "${valid_keys[@]}"; then + invalid_keys+=( "${key}" ) + fi + done + printf '%s ' "${invalid_keys[@]}" } diff --git a/bash/global_variables.bash b/bash/global_variables.bash index d937242..e8376f9 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -9,13 +9,15 @@ function Define_Further_Global_Variables() { - readonly HYBRID_valid_configuration_sections=( - 'Hybrid-handler' + readonly HYBRID_valid_software_configuration_sections=( 'IC' 'Hydro' 'Sampler' 'Afterburner' ) + readonly HYBRID_valid_auxiliary_configuration_sections=( + 'Hybrid-handler' + ) readonly HYBRID_default_configurations_folder="${HYBRID_repository_global_path}/configs" # Variables to be set from command line HYBRID_execution_mode='help' diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index e4dec77..388c6bf 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -16,6 +16,7 @@ function __static__Source_Codebase_Files() 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' 'command_line_parsers/sub_parser.bash' + 'configuration_parser.bash' 'dispatch_functions.bash' 'global_variables.bash' 'logger.bash' diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 6e4c0fd..aca1f8d 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -7,12 +7,169 @@ # #=================================================== -function Unit_Test__configuration-validate-1() +function Make_Test_Preliminary_Operations__configuration-validate-existence() { - return 0 + local file_to_be_sourced list_of_files + list_of_files=( + 'configuration_parser.bash' + 'global_variables.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables } -function Unit_Test__configuration-validate-2() +function Unit_Test__configuration-validate-existence() { - return 0 + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation not existing file succeeded.' + return 1 + fi } + +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__configuration-validate-YAML() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +function Unit_Test__configuration-validate-YAML() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + printf 'Scalar\nKey: Value\n' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of invalid YAML in configuration file succeeded.' + return 1 + fi + rm "${HYBRID_configuration_file}" +} + +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__configuration-validate-section-labels() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +function Unit_Test__configuration-validate-section-labels() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + # Test case 1 + printf 'Invalid: Value\n' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of configuration file with invalid section succeeded.' + return 1 + fi + # Test case 2 (wrong ordering of blocks) + printf 'Afterburner: Values\nIC: Values\nHydro: Values\n' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of configuration file with sections in wrong order succeeded.' + return 1 + fi + # Test case 3 (repeated block) + printf 'IC: Values\nSampler: Values\nIC: Again\n' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of configuration file with repeated section succeeded.' + return 1 + fi + # Test case 4 (ordering fine, but missing block) + printf 'IC: Values\nSampler: Values\n' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of configuration file with missing sections succeeded.' + return 1 + fi + # Test case 5 (no software section) + printf 'Hybrid-handler: Values\n' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of configuration file with no software section succeeded.' + return 1 + fi + rm "${HYBRID_configuration_file}" +} + +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__configuration-validate-all-keys() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +function Unit_Test__configuration-validate-all-keys() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + printf ' + IC: + Fake: Values + Invalid: 42 + Hydro: + Foo: Bar + Bar: Foo + Sampler: + Key: Value + Invalid: 17 + Afterburner: + Nope: 13 + Maybe: False + ' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of configuration file with only invalid keys succeeded.' + return 1 + fi + printf ' + IC: + Executable: /path/to/exec + Invalid: 42 + ' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of configuration file with invalid keys succeeded.' + return 1 + fi + printf ' + IC: + Executable: /path/to/exec + Hydro: + Input_file: /path/to/file + ' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File ) + if [[ $? -ne 0 ]]; then + Print_Error 'Validation of configuration file failed.' + return 1 + fi + rm "${HYBRID_configuration_file}" +} + +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__configuration-parse-general-section() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +# function Unit_Test__configuration-parse-general-section() +# { +# HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml +# printf 'Hybrid-handler:\n Key: Value\nIC:\n Fake: Values' > "${HYBRID_configuration_file}" +# ( Validate_And_Parse_Configuration_File ) +# if [[ $? -eq 0 ]]; then +# Print_Error 'Validation of invalid general section in configuration file succeeded.' +# return 1 +# fi +# printf 'Hybrid-handler:\n' > "${HYBRID_configuration_file}" + # ( Validate_And_Parse_Configuration_File ) + # if [[ $? -ne 0 ]]; then + # Print_Error 'Validation of configuration file with invalid keys succeeded.' + # return 1 + # fi +# rm "${HYBRID_configuration_file}" +# } From aa98c10fd3f0cbf0bb2a5a3458842db28b2c0911 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Jun 2023 10:18:34 +0200 Subject: [PATCH 080/549] Move sections valid keys into global constant arrays --- bash/configuration_parser.bash | 24 +++++++++++------------- bash/global_variables.bash | 10 ++++++++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 97abeee..b28acab 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -7,17 +7,16 @@ # #=================================================== +# NOTE: We do not want to change the config file, hence we read sections into +# local (string) variables and then use these as input to yq to read keys +# and deleting the key from the variable content. function Validate_And_Parse_Configuration_File() { Remove_Comments_In_File "${HYBRID_configuration_file}" # This checks for existence, too - # NOTE: We do not want to change the config file, hence we read sections into - # local (string) variables and then use these as input to yq to read keys - # and deleting the key from the variable content. __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File __static__Abort_If_Sections_Are_Violating_Any_Requirement __static__Abort_If_Invalid_Keys_Were_Used # Needed steps: - # 3. Move valid keys to global constant arrays (?) # 4. Parse 'Hybrid-handler' section # 5. Parse software sections setting all needed variables # -> see global_variables.bash @@ -79,11 +78,6 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() function __static__Abort_If_Invalid_Keys_Were_Used() { - local -r common_software_keys=( - 'Executable' - 'Input_file' - 'Input_keys' - ) local -r yaml_config="$(< "${HYBRID_configuration_file}")" local valid_keys invalid_report invalid_report=() # Use array entries to split lines to feed into logger @@ -91,22 +85,26 @@ function __static__Abort_If_Invalid_Keys_Were_Used() valid_keys=() # IC section valid_keys=( - "${common_software_keys[@]}" + "${HYBRID_valid_common_software_keys[@]}" + "${HYBRID_ic_valid_keys[@]}" ) __static__Validate_Keys_Of_Section 'IC' # Hydro section valid_keys=( - "${common_software_keys[@]}" + "${HYBRID_valid_common_software_keys[@]}" + "${HYBRID_hydro_valid_keys[@]}" ) __static__Validate_Keys_Of_Section 'Hydro' # Sampler section valid_keys=( - "${common_software_keys[@]}" + "${HYBRID_valid_common_software_keys[@]}" + "${HYBRID_sampler_valid_keys[@]}" ) __static__Validate_Keys_Of_Section 'Sampler' # Afterburner section valid_keys=( - "${common_software_keys[@]}" + "${HYBRID_valid_common_software_keys[@]}" + "${HYBRID_afterburner_valid_keys[@]}" ) __static__Validate_Keys_Of_Section 'Afterburner' # Abort if some validation failed diff --git a/bash/global_variables.bash b/bash/global_variables.bash index e8376f9..f467f77 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -9,6 +9,7 @@ function Define_Further_Global_Variables() { + # Constant information readonly HYBRID_valid_software_configuration_sections=( 'IC' 'Hydro' @@ -18,6 +19,15 @@ function Define_Further_Global_Variables() readonly HYBRID_valid_auxiliary_configuration_sections=( 'Hybrid-handler' ) + readonly HYBRID_valid_common_software_keys=( + 'Executable' + 'Input_file' + 'Input_keys' + ) + readonly HYBRID_ic_valid_keys=() + readonly HYBRID_hydro_valid_keys=() + readonly HYBRID_sampler_valid_keys=() + readonly HYBRID_afterburner_valid_keys=() readonly HYBRID_default_configurations_folder="${HYBRID_repository_global_path}/configs" # Variables to be set from command line HYBRID_execution_mode='help' From 706d290ef5c469c4322f8b1468aba67fa78d5be3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Jun 2023 14:42:53 +0200 Subject: [PATCH 081/549] Implement actual parsing of config file in global variables --- bash/configuration_parser.bash | 106 +++++++++++++++-- bash/global_variables.bash | 29 +++-- tests/unit_tests_configuration.bash | 174 +++++++++++++++++++++++++--- 3 files changed, 274 insertions(+), 35 deletions(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index b28acab..265d2eb 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -16,11 +16,12 @@ function Validate_And_Parse_Configuration_File() __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File __static__Abort_If_Sections_Are_Violating_Any_Requirement __static__Abort_If_Invalid_Keys_Were_Used + __static__Parse_Section 'Hybrid-handler' + __static__Parse_Section 'IC' + __static__Parse_Section 'Hydro' + __static__Parse_Section 'Sampler' + __static__Parse_Section 'Afterburner' # Needed steps: - # 4. Parse 'Hybrid-handler' section - # 5. Parse software sections setting all needed variables - # -> see global_variables.bash - # -> the software input keys must not be validated but simply put into 'HYBRID_software_input' # 6. Validate software to be later run for the given software sections (?) # } @@ -68,7 +69,8 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() 'Software sections in the handler configuration file are out of order.' fi local gaps_between_indices - gaps_between_indices=$(awk 'NR>1{print $1-x}{x=$1}' <(printf '%d\n' "${software_sections_indices[@]}") | sort -u) + gaps_between_indices=$(awk 'NR>1{print $1-x}{x=$1}'\ + <(printf '%d\n' "${software_sections_indices[@]}") | sort -u) if [[ "${gaps_between_indices}" != '1' ]]; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ 'Missing software section(s) in the handler configuration file.' @@ -82,7 +84,10 @@ function __static__Abort_If_Invalid_Keys_Were_Used() local valid_keys invalid_report invalid_report=() # Use array entries to split lines to feed into logger # Hybrid-Handler section - valid_keys=() + valid_keys=( + "${HYBRID_hybrid_handler_valid_keys[@]}" + ) + __static__Validate_Keys_Of_Section 'Hybrid-handler' # IC section valid_keys=( "${HYBRID_valid_common_software_keys[@]}" @@ -120,7 +125,7 @@ function __static__Abort_If_Invalid_Keys_Were_Used() function __static__Validate_Keys_Of_Section() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty yaml_config - Ensure_That_Given_Variables_Are_Set invalid_report + Ensure_That_Given_Variables_Are_Set invalid_report valid_keys local -r section_label=$1 local invalid_keys yaml_section if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then @@ -135,7 +140,7 @@ function __static__Validate_Keys_Of_Section() # NOTE: It is assumed that keys do not contain spaces! function __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty valid_keys + Ensure_That_Given_Variables_Are_Set valid_keys local input_keys key invalid_keys input_keys=( $(yq 'keys | .[]' <<< "$1") ) invalid_keys=() @@ -146,3 +151,88 @@ function __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string() done printf '%s ' "${invalid_keys[@]}" } + +function __static__Parse_Section() +{ + local -r\ + section_label=$1\ + yaml_config="$(< "${HYBRID_configuration_file}")" + local yaml_section + if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then + yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" + Call_Function_If_Existing_Or_Exit\ + __static__Parse_Section_${section_label} "${yaml_section}" + fi +} + +function __static__Parse_Section_Hybrid-handler() +{ + local yaml_section=$1 + # No key to be parsed for the moment + __static__YAML_section_must_be_empty "${yaml_section}" 'Hybrid-handler' +} + +function __static__Parse_Section_IC() +{ + local yaml_section key + yaml_section=$1 + HYBRID_given_software_section+=( "${yaml_section}" ) + __static__Parse_Key_And_Store_It 'Executable' HYBRID_software_executable[IC] + __static__Parse_Key_And_Store_It 'Input_file' HYBRID_software_base_config_file[IC] + __static__Parse_Key_And_Store_It 'Software_keys' HYBRID_software_new_input_keys[IC] + __static__YAML_section_must_be_empty "${yaml_section}" 'IC' +} + +function __static__Parse_Section_Hydro() +{ + local yaml_section key + yaml_section=$1 + HYBRID_given_software_section+=( "${yaml_section}" ) + __static__Parse_Key_And_Store_It 'Executable' HYBRID_software_executable[Hydro] + __static__Parse_Key_And_Store_It 'Input_file' HYBRID_software_base_config_file[Hydro] + __static__Parse_Key_And_Store_It 'Software_keys' HYBRID_software_new_input_keys[Hydro] + __static__YAML_section_must_be_empty "${yaml_section}" 'Hydro' +} + +function __static__Parse_Section_Sampler() +{ + local yaml_section key + yaml_section=$1 + HYBRID_given_software_section+=( "${yaml_section}" ) + __static__Parse_Key_And_Store_It 'Executable' HYBRID_software_executable[Sampler] + __static__Parse_Key_And_Store_It 'Input_file' HYBRID_software_base_config_file[Sampler] + __static__Parse_Key_And_Store_It 'Software_keys' HYBRID_software_new_input_keys[Sampler] + __static__YAML_section_must_be_empty "${yaml_section}" 'Sampler' +} + +function __static__Parse_Section_Afterburner() +{ + local yaml_section key + yaml_section=$1 + HYBRID_given_software_section+=( "${yaml_section}" ) + __static__Parse_Key_And_Store_It 'Executable' HYBRID_software_executable[Afterburner] + __static__Parse_Key_And_Store_It 'Input_file' HYBRID_software_base_config_file[Afterburner] + __static__Parse_Key_And_Store_It 'Software_keys' HYBRID_software_new_input_keys[Afterburner] + __static__YAML_section_must_be_empty "${yaml_section}" 'Afterburner' +} + +function __static__Parse_Key_And_Store_It() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty yaml_section + local key=$1 + declare -n store_variable=$2 + if Has_YAML_String_Given_Key "${yaml_section}" "${key}"; then + store_variable=$(Read_From_YAML_String_Given_Key "${yaml_section}" "${key}") + yaml_section="$(Print_YAML_String_Without_Given_Key "${yaml_section}" "${key}")" + fi +} + +function __static__YAML_section_must_be_empty() +{ + local yaml_section=$1 + if [[ "${yaml_section}" != '{}' ]]; then + Print_Internal_And_Exit\ + "Not all keys in ${2:-some} section have been parsed. Remaining:"\ + "\n${yaml_section}\n" + fi +} diff --git a/bash/global_variables.bash b/bash/global_variables.bash index f467f77..37b1d6c 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -22,8 +22,9 @@ function Define_Further_Global_Variables() readonly HYBRID_valid_common_software_keys=( 'Executable' 'Input_file' - 'Input_keys' + 'Software_keys' ) + readonly HYBRID_hybrid_handler_valid_keys=() readonly HYBRID_ic_valid_keys=() readonly HYBRID_hydro_valid_keys=() readonly HYBRID_sampler_valid_keys=() @@ -34,15 +35,23 @@ function Define_Further_Global_Variables() HYBRID_configuration_file='./config.yaml' HYBRID_output_directory='.' # Variables to be set from configuration/setup - HYBRID_ic_software_executable='' - HYBRID_hydro_software_executable='' - HYBRID_sampler_software_executable='' - HYBRID_Afterburner_software_executable='' HYBRID_given_software_sections=() - declare -gA HYBRID_software_input=( - ['IC']='' - ['Hydro']='' - ['Sampler']='' - ['Afterburner']='' + declare -gA HYBRID_software_executable=( + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' + ) + declare -gA HYBRID_software_base_config_file=( + [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" + [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" + [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" + [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" + ) + declare -gA HYBRID_software_new_input_keys=( + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) } diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index aca1f8d..0a9719a 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -107,6 +107,8 @@ function Unit_Test__configuration-validate-all-keys() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml printf ' + Hybrid-handler: + Try: 1 IC: Fake: Values Invalid: 42 @@ -126,6 +128,7 @@ function Unit_Test__configuration-validate-all-keys() return 1 fi printf ' + Hybrid-handler: {} IC: Executable: /path/to/exec Invalid: 42 @@ -136,6 +139,7 @@ function Unit_Test__configuration-validate-all-keys() return 1 fi printf ' + Hybrid-handler: {} IC: Executable: /path/to/exec Hydro: @@ -156,20 +160,156 @@ function Make_Test_Preliminary_Operations__configuration-parse-general-section() Make_Test_Preliminary_Operations__configuration-validate-existence } -# function Unit_Test__configuration-parse-general-section() -# { -# HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml -# printf 'Hybrid-handler:\n Key: Value\nIC:\n Fake: Values' > "${HYBRID_configuration_file}" -# ( Validate_And_Parse_Configuration_File ) -# if [[ $? -eq 0 ]]; then -# Print_Error 'Validation of invalid general section in configuration file succeeded.' -# return 1 -# fi -# printf 'Hybrid-handler:\n' > "${HYBRID_configuration_file}" - # ( Validate_And_Parse_Configuration_File ) - # if [[ $? -ne 0 ]]; then - # Print_Error 'Validation of configuration file with invalid keys succeeded.' - # return 1 - # fi -# rm "${HYBRID_configuration_file}" -# } +function Unit_Test__configuration-parse-general-section() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + printf 'Hybrid-handler: {}\nIC:\n Executable: foo\n' > "${HYBRID_configuration_file}" + ( Validate_And_Parse_Configuration_File ) + if [[ $? -ne 0 ]]; then + Print_Error 'Parsing of general section failed.' + return 1 + fi + rm "${HYBRID_configuration_file}" +} + +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__configuration-parse-IC-section() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +function Unit_Test__configuration-parse-IC-section() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + printf ' + IC: + Executable: foo + Input_file: bar + Software_keys: + General: + Randomseed: 12345 + ' > "${HYBRID_configuration_file}" + ( + Validate_And_Parse_Configuration_File + if [[ ${HYBRID_software_executable[IC]} != 'foo' ]]; then + Print_Fatal_And_Exit "Parsing of IC section failed (software executable)." + fi + if [[ ${HYBRID_software_base_config_file[IC]} != 'bar' ]]; then + Print_Fatal_And_Exit 'Parsing of IC section failed (input file).' + fi + if [[ ${HYBRID_software_new_input_keys[IC]} != $'General:\n Randomseed: 12345' ]]; then + Print_Fatal_And_Exit "Parsing of IC section failed (software keys)." + fi + ) + if [[ $? -ne 0 ]]; then + return 1 + fi + rm "${HYBRID_configuration_file}" +} + +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__configuration-parse-Hydro-section() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +function Unit_Test__configuration-parse-Hydro-section() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + printf ' + Hydro: + Executable: foo + Input_file: bar + Software_keys: + etaS: 0.12345 + ' > "${HYBRID_configuration_file}" + ( + Validate_And_Parse_Configuration_File + if [[ ${HYBRID_software_executable[Hydro]} != 'foo' ]]; then + Print_Fatal_And_Exit "Parsing of Hydro section failed (software executable)." + fi + if [[ ${HYBRID_software_base_config_file[Hydro]} != 'bar' ]]; then + Print_Fatal_And_Exit 'Parsing of Hydro section failed (input file).' + fi + if [[ ${HYBRID_software_new_input_keys[Hydro]} != 'etaS: 0.12345' ]]; then + Print_Fatal_And_Exit "Parsing of Hydro section failed (software keys)." + fi + ) + if [[ $? -ne 0 ]]; then + return 1 + fi + rm "${HYBRID_configuration_file}" +} + +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__configuration-parse-Sampler-section() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +function Unit_Test__configuration-parse-Sampler-section() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + printf ' + Sampler: + Executable: foo + Input_file: bar + Software_keys: + shear: 1.2345 + ' > "${HYBRID_configuration_file}" + ( + Validate_And_Parse_Configuration_File + if [[ ${HYBRID_software_executable[Sampler]} != 'foo' ]]; then + Print_Fatal_And_Exit "Parsing of Sampler section failed (software executable)." + fi + if [[ ${HYBRID_software_base_config_file[Sampler]} != 'bar' ]]; then + Print_Fatal_And_Exit 'Parsing of Sampler section failed (input file).' + fi + if [[ ${HYBRID_software_new_input_keys[Sampler]} != 'shear: 1.2345' ]]; then + Print_Fatal_And_Exit "Parsing of Sampler section failed (software keys)." + fi + ) + if [[ $? -ne 0 ]]; then + return 1 + fi + rm "${HYBRID_configuration_file}" +} + +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__configuration-parse-Afterburner-section() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +function Unit_Test__configuration-parse-Afterburner-section() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + printf ' + Afterburner: + Executable: foo + Input_file: bar + Software_keys: + General: + End_Time: 42000 + ' > "${HYBRID_configuration_file}" + ( + Validate_And_Parse_Configuration_File + if [[ ${HYBRID_software_executable[Afterburner]} != 'foo' ]]; then + Print_Fatal_And_Exit "Parsing of Afterburner section failed (software executable)." + fi + if [[ ${HYBRID_software_base_config_file[Afterburner]} != 'bar' ]]; then + Print_Fatal_And_Exit 'Parsing of Afterburner section failed (input file).' + fi + if [[ ${HYBRID_software_new_input_keys[Afterburner]} != $'General:\n End_Time: 42000' ]]; then + Print_Fatal_And_Exit "Parsing of Afterburner section failed (software keys)." + fi + ) + if [[ $? -ne 0 ]]; then + return 1 + fi + rm "${HYBRID_configuration_file}" +} From 302b6cb5c50dc653d868c7f22b577f1920ff3150 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Jun 2023 14:50:14 +0200 Subject: [PATCH 082/549] Extract common testing code in auxiliary testing function --- tests/unit_tests_configuration.bash | 71 ++++++++++------------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 0a9719a..27e0f61 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -174,6 +174,25 @@ function Unit_Test__configuration-parse-general-section() #------------------------------------------------------------------------------- +function __static__Test_Section_Parsing_In_Subshell() +( + local section executable input_file new_keys + section=$1 + executable=$2 + input_file=$3 + new_keys=$4 + Validate_And_Parse_Configuration_File + if [[ ${HYBRID_software_executable[${section}]} != "${executable}" ]]; then + Print_Fatal_And_Exit "Parsing of ${section} section failed (software executable)." + fi + if [[ ${HYBRID_software_base_config_file[${section}]} != "${input_file}" ]]; then + Print_Fatal_And_Exit 'Parsing of ${section} section failed (input file).' + fi + if [[ ${HYBRID_software_new_input_keys[${section}]} != "${new_keys}" ]]; then + Print_Fatal_And_Exit "Parsing of ${section} section failed (software keys)." + fi +) + function Make_Test_Preliminary_Operations__configuration-parse-IC-section() { Make_Test_Preliminary_Operations__configuration-validate-existence @@ -190,18 +209,7 @@ function Unit_Test__configuration-parse-IC-section() General: Randomseed: 12345 ' > "${HYBRID_configuration_file}" - ( - Validate_And_Parse_Configuration_File - if [[ ${HYBRID_software_executable[IC]} != 'foo' ]]; then - Print_Fatal_And_Exit "Parsing of IC section failed (software executable)." - fi - if [[ ${HYBRID_software_base_config_file[IC]} != 'bar' ]]; then - Print_Fatal_And_Exit 'Parsing of IC section failed (input file).' - fi - if [[ ${HYBRID_software_new_input_keys[IC]} != $'General:\n Randomseed: 12345' ]]; then - Print_Fatal_And_Exit "Parsing of IC section failed (software keys)." - fi - ) + __static__Test_Section_Parsing_In_Subshell 'IC' 'foo' 'bar' $'General:\n Randomseed: 12345' if [[ $? -ne 0 ]]; then return 1 fi @@ -225,18 +233,7 @@ function Unit_Test__configuration-parse-Hydro-section() Software_keys: etaS: 0.12345 ' > "${HYBRID_configuration_file}" - ( - Validate_And_Parse_Configuration_File - if [[ ${HYBRID_software_executable[Hydro]} != 'foo' ]]; then - Print_Fatal_And_Exit "Parsing of Hydro section failed (software executable)." - fi - if [[ ${HYBRID_software_base_config_file[Hydro]} != 'bar' ]]; then - Print_Fatal_And_Exit 'Parsing of Hydro section failed (input file).' - fi - if [[ ${HYBRID_software_new_input_keys[Hydro]} != 'etaS: 0.12345' ]]; then - Print_Fatal_And_Exit "Parsing of Hydro section failed (software keys)." - fi - ) + __static__Test_Section_Parsing_In_Subshell 'Hydro' 'foo' 'bar' 'etaS: 0.12345' if [[ $? -ne 0 ]]; then return 1 fi @@ -260,18 +257,7 @@ function Unit_Test__configuration-parse-Sampler-section() Software_keys: shear: 1.2345 ' > "${HYBRID_configuration_file}" - ( - Validate_And_Parse_Configuration_File - if [[ ${HYBRID_software_executable[Sampler]} != 'foo' ]]; then - Print_Fatal_And_Exit "Parsing of Sampler section failed (software executable)." - fi - if [[ ${HYBRID_software_base_config_file[Sampler]} != 'bar' ]]; then - Print_Fatal_And_Exit 'Parsing of Sampler section failed (input file).' - fi - if [[ ${HYBRID_software_new_input_keys[Sampler]} != 'shear: 1.2345' ]]; then - Print_Fatal_And_Exit "Parsing of Sampler section failed (software keys)." - fi - ) + __static__Test_Section_Parsing_In_Subshell 'Sampler' 'foo' 'bar' 'shear: 1.2345' if [[ $? -ne 0 ]]; then return 1 fi @@ -296,18 +282,7 @@ function Unit_Test__configuration-parse-Afterburner-section() General: End_Time: 42000 ' > "${HYBRID_configuration_file}" - ( - Validate_And_Parse_Configuration_File - if [[ ${HYBRID_software_executable[Afterburner]} != 'foo' ]]; then - Print_Fatal_And_Exit "Parsing of Afterburner section failed (software executable)." - fi - if [[ ${HYBRID_software_base_config_file[Afterburner]} != 'bar' ]]; then - Print_Fatal_And_Exit 'Parsing of Afterburner section failed (input file).' - fi - if [[ ${HYBRID_software_new_input_keys[Afterburner]} != $'General:\n End_Time: 42000' ]]; then - Print_Fatal_And_Exit "Parsing of Afterburner section failed (software keys)." - fi - ) + __static__Test_Section_Parsing_In_Subshell 'Afterburner' 'foo' 'bar' $'General:\n End_Time: 42000' if [[ $? -ne 0 ]]; then return 1 fi From fcce28247e40a91da8b8c94ce5245e76602d4bd7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Jun 2023 14:57:23 +0200 Subject: [PATCH 083/549] Delegate validation of config key values to ad hoc function --- Hybrid-handler | 1 + bash/configuration_parser.bash | 3 --- bash/sanity_checks.bash | 13 +++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 bash/sanity_checks.bash diff --git a/Hybrid-handler b/Hybrid-handler index d6dc878..0f89bab 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -83,6 +83,7 @@ function Make_Needed_Operations_Depending_On_Execution_Mode() do ) local software_section Validate_And_Parse_Configuration_File + Perform_Sanity_Checks_On_Provided_Input for software_section in "${HYBRID_given_software_sections[@]}"; do Prepare_Software_Input_File "${software_section}" Ensure_All_Needed_Input_Exists "${software_section}" diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 265d2eb..6ce9b32 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -21,9 +21,6 @@ function Validate_And_Parse_Configuration_File() __static__Parse_Section 'Hydro' __static__Parse_Section 'Sampler' __static__Parse_Section 'Afterburner' - # Needed steps: - # 6. Validate software to be later run for the given software sections (?) - # } function __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File() diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash new file mode 100644 index 0000000..10f7250 --- /dev/null +++ b/bash/sanity_checks.bash @@ -0,0 +1,13 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Perform_Sanity_Checks_On_Provided_Input() +{ + Print_Not_Implemented_Function_Error +} From beccdaf35d19e7bf932f12d881005b7df1a176b9 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Jun 2023 18:50:17 +0200 Subject: [PATCH 084/549] Make functions to test variable existence more general --- bash/utility_functions.bash | 18 ++++++++++++------ tests/unit_tests_utility_functions.bash | 24 +++++++++++++++++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index a5bc8ac..b2d0377 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -171,17 +171,23 @@ function Call_Function_If_Existing_Or_No_Op() fi } -# NOTE: In Bash there are several ways to declare variables and somehow these -# are (apparently) not consistent w.r.t. resulting set when tested via -# [[ -v ... ]] and therefore here we decided to use the 'declare' command. +# NOTE: In Bash there are several ways to declare variables and somehow these are +# (apparently) not consistent w.r.t. the variable resulting set when tested via +# [[ -v ... ]] and therefore here we decided to also use the 'declare' command. # For example, 'local foo' is not setting a variable (the -v test fails, which # makes sense). However, also 'foo=()' is not setting the array variable -# which is not what we want here. In this function "set" means declared in -# some way and 'foo=()' should not result in an error. +# which is not what we want here. In this function "set" means DECLARED IN +# SOME WAY and 'foo=()' should not result in an error. On the other hand, +# if this function is used to test existence of an entry of an array, then +# 'declare -p array[0]' would fail even if array[0] existed, while the test +# [[ -v array[0] ]] would succeed. Hence we treat this case separately. function Ensure_That_Given_Variables_Are_Set() { local variable_name for variable_name in "$@"; do if ! declare -p "${variable_name}" &>/dev/null; then + if [[ ${variable_name} =~ \]$ && -v ${variable_name} ]]; then + continue + fi Print_Internal_And_Exit\ "Variable \"${variable_name}\" not set in function \"${FUNCNAME[1]}\"." fi @@ -199,7 +205,7 @@ function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { for variable_name in "$@"; do # The following can be done using the "${ref@A}" bash-5 expansion which # would return the variable declared attributes (e.g. 'a' for arrays). - if [[ $(declare -p "${variable_name}") =~ ^declare\ -[aA] ]]; then + if [[ $(declare -p "${variable_name}" 2>/dev/null ) =~ ^declare\ -[aA] ]]; then declare -n ref=${variable_name} if [[ ${#ref[@]} -ne 0 ]]; then continue diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 8c59a7f..01e06c6 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -195,6 +195,17 @@ function Unit_Test__utility-check-shell-variables-set() Print_Error 'Checking array variable set to one empty entry failed.' return 1 fi + declare -A bar=([Hi]='') + ( Ensure_That_Given_Variables_Are_Set bar ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking associative array variable set to one empty entry failed.' + return 1 + fi + ( Ensure_That_Given_Variables_Are_Set bar[Hi] ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking associative array entry set to one empty entry failed.' + return 1 + fi } function Unit_Test__utility-check-shell-variables-set-not-empty() @@ -236,9 +247,20 @@ function Unit_Test__utility-check-shell-variables-set-not-empty() return 1 fi bar['key']='' - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar &> /dev/null ) + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar ) if [[ $? -ne 0 ]]; then Print_Error 'Checking set associative array failed.' return 1 fi + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar[key] &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Checking empty associative array entry succeeded.' + return 1 + fi + bar['key']='something' + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar[key] ) + if [[ $? -ne 0 ]]; then + Print_Error 'Checking set associative array entry failed.' + return 1 + fi } From 20155122cd6599ed35f666e8eba32d8ad6a78423 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Jun 2023 18:52:16 +0200 Subject: [PATCH 085/549] Add assertion in tests about configuration parsing --- tests/unit_tests_configuration.bash | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 27e0f61..1f86d36 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -182,6 +182,10 @@ function __static__Test_Section_Parsing_In_Subshell() input_file=$3 new_keys=$4 Validate_And_Parse_Configuration_File + if [[ "${#HYBRID_given_software_sections[@]}" -ne 1 ]] ||\ + [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then + Print_Fatal_And_Exit "Parsing of ${section} section failed (section storing)." + fi if [[ ${HYBRID_software_executable[${section}]} != "${executable}" ]]; then Print_Fatal_And_Exit "Parsing of ${section} section failed (software executable)." fi From 7a631f3635c43a48bf3c5e7d5d1000abecb7c94f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Jun 2023 18:53:50 +0200 Subject: [PATCH 086/549] Use associative arrays to globally declare valid keys This allowed to stay completely general when parsing sections, although some slightly involved indirection and some code duplication was needed. However, the advantage of this approach makes it preferable, because any further change to the code to add new configuration keys will be limited to the place where global variables are declared. --- bash/configuration_parser.bash | 94 ++++++----------------------- bash/global_variables.bash | 43 +++++++++---- tests/unit_tests_configuration.bash | 10 +-- 3 files changed, 54 insertions(+), 93 deletions(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 6ce9b32..7791b9b 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -16,7 +16,7 @@ function Validate_And_Parse_Configuration_File() __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File __static__Abort_If_Sections_Are_Violating_Any_Requirement __static__Abort_If_Invalid_Keys_Were_Used - __static__Parse_Section 'Hybrid-handler' + __static__Parse_Section 'Hybrid_handler' __static__Parse_Section 'IC' __static__Parse_Section 'Hydro' __static__Parse_Section 'Sampler' @@ -80,34 +80,20 @@ function __static__Abort_If_Invalid_Keys_Were_Used() local -r yaml_config="$(< "${HYBRID_configuration_file}")" local valid_keys invalid_report invalid_report=() # Use array entries to split lines to feed into logger - # Hybrid-Handler section - valid_keys=( - "${HYBRID_hybrid_handler_valid_keys[@]}" - ) - __static__Validate_Keys_Of_Section 'Hybrid-handler' + # Hybrid_handler section + valid_keys=( "${!HYBRID_hybrid_handler_valid_keys[@]}" ) + __static__Validate_Keys_Of_Section 'Hybrid_handler' # IC section - valid_keys=( - "${HYBRID_valid_common_software_keys[@]}" - "${HYBRID_ic_valid_keys[@]}" - ) + valid_keys=( "${!HYBRID_ic_valid_keys[@]}" ) __static__Validate_Keys_Of_Section 'IC' # Hydro section - valid_keys=( - "${HYBRID_valid_common_software_keys[@]}" - "${HYBRID_hydro_valid_keys[@]}" - ) + valid_keys=( "${!HYBRID_hydro_valid_keys[@]}" ) __static__Validate_Keys_Of_Section 'Hydro' # Sampler section - valid_keys=( - "${HYBRID_valid_common_software_keys[@]}" - "${HYBRID_sampler_valid_keys[@]}" - ) + valid_keys=( "${!HYBRID_sampler_valid_keys[@]}" ) __static__Validate_Keys_Of_Section 'Sampler' # Afterburner section - valid_keys=( - "${HYBRID_valid_common_software_keys[@]}" - "${HYBRID_afterburner_valid_keys[@]}" - ) + valid_keys=( "${!HYBRID_afterburner_valid_keys[@]}" ) __static__Validate_Keys_Of_Section 'Afterburner' # Abort if some validation failed if [[ "${#invalid_report[@]}" -ne 0 ]]; then @@ -154,65 +140,21 @@ function __static__Parse_Section() local -r\ section_label=$1\ yaml_config="$(< "${HYBRID_configuration_file}")" - local yaml_section + local yaml_section valid_key if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" - Call_Function_If_Existing_Or_Exit\ - __static__Parse_Section_${section_label} "${yaml_section}" + if [[ ${section_label} != 'Hybrid_handler' ]]; then + HYBRID_given_software_sections+=( "${section_label}" ) + fi + declare -n reference_to_map="HYBRID_${section_label,,}_valid_keys" + for valid_key in "${!reference_to_map[@]}"; do + Ensure_That_Given_Variables_Are_Set "${reference_to_map[${valid_key}]}" + __static__Parse_Key_And_Store_It "${valid_key}" "${reference_to_map[${valid_key}]}" + done + __static__YAML_section_must_be_empty "${yaml_section}" "${section_label}" fi } -function __static__Parse_Section_Hybrid-handler() -{ - local yaml_section=$1 - # No key to be parsed for the moment - __static__YAML_section_must_be_empty "${yaml_section}" 'Hybrid-handler' -} - -function __static__Parse_Section_IC() -{ - local yaml_section key - yaml_section=$1 - HYBRID_given_software_section+=( "${yaml_section}" ) - __static__Parse_Key_And_Store_It 'Executable' HYBRID_software_executable[IC] - __static__Parse_Key_And_Store_It 'Input_file' HYBRID_software_base_config_file[IC] - __static__Parse_Key_And_Store_It 'Software_keys' HYBRID_software_new_input_keys[IC] - __static__YAML_section_must_be_empty "${yaml_section}" 'IC' -} - -function __static__Parse_Section_Hydro() -{ - local yaml_section key - yaml_section=$1 - HYBRID_given_software_section+=( "${yaml_section}" ) - __static__Parse_Key_And_Store_It 'Executable' HYBRID_software_executable[Hydro] - __static__Parse_Key_And_Store_It 'Input_file' HYBRID_software_base_config_file[Hydro] - __static__Parse_Key_And_Store_It 'Software_keys' HYBRID_software_new_input_keys[Hydro] - __static__YAML_section_must_be_empty "${yaml_section}" 'Hydro' -} - -function __static__Parse_Section_Sampler() -{ - local yaml_section key - yaml_section=$1 - HYBRID_given_software_section+=( "${yaml_section}" ) - __static__Parse_Key_And_Store_It 'Executable' HYBRID_software_executable[Sampler] - __static__Parse_Key_And_Store_It 'Input_file' HYBRID_software_base_config_file[Sampler] - __static__Parse_Key_And_Store_It 'Software_keys' HYBRID_software_new_input_keys[Sampler] - __static__YAML_section_must_be_empty "${yaml_section}" 'Sampler' -} - -function __static__Parse_Section_Afterburner() -{ - local yaml_section key - yaml_section=$1 - HYBRID_given_software_section+=( "${yaml_section}" ) - __static__Parse_Key_And_Store_It 'Executable' HYBRID_software_executable[Afterburner] - __static__Parse_Key_And_Store_It 'Input_file' HYBRID_software_base_config_file[Afterburner] - __static__Parse_Key_And_Store_It 'Software_keys' HYBRID_software_new_input_keys[Afterburner] - __static__YAML_section_must_be_empty "${yaml_section}" 'Afterburner' -} - function __static__Parse_Key_And_Store_It() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty yaml_section diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 37b1d6c..cc7c29b 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -7,6 +7,12 @@ # #=================================================== +# ATTENTION: The top-level section labels (i.e. 'IC', 'Hydro', etc.) are used in variable +# names as well, although with lower-case letters only. In the codebase it has +# been exerted leverage on this aspect and at some point the name of variables +# are built using the section labels transformed into lower-case words. Hence, +# it is important that section labels do not contain characters that would break +# this mechanism, like dashes or spaces! function Define_Further_Global_Variables() { # Constant information @@ -17,19 +23,32 @@ function Define_Further_Global_Variables() 'Afterburner' ) readonly HYBRID_valid_auxiliary_configuration_sections=( - 'Hybrid-handler' - ) - readonly HYBRID_valid_common_software_keys=( - 'Executable' - 'Input_file' - 'Software_keys' - ) - readonly HYBRID_hybrid_handler_valid_keys=() - readonly HYBRID_ic_valid_keys=() - readonly HYBRID_hydro_valid_keys=() - readonly HYBRID_sampler_valid_keys=() - readonly HYBRID_afterburner_valid_keys=() + 'Hybrid_handler' + ) readonly HYBRID_default_configurations_folder="${HYBRID_repository_global_path}/configs" + # The following associative arrays declare maps between valid keys in the handler config + # file and bash variables in which the input information will be stored once parsed. + declare -rgA HYBRID_hybrid_handler_valid_keys=() + declare -rgA HYBRID_ic_valid_keys=( + [Executable]='HYBRID_software_executable[IC]' + [Input_file]='HYBRID_software_base_config_file[IC]' + [Software_keys]='HYBRID_software_new_input_keys[IC]' + ) + declare -rgA HYBRID_hydro_valid_keys=( + [Executable]='HYBRID_software_executable[Hydro]' + [Input_file]='HYBRID_software_base_config_file[Hydro]' + [Software_keys]='HYBRID_software_new_input_keys[Hydro]' + ) + declare -rgA HYBRID_sampler_valid_keys=( + [Executable]='HYBRID_software_executable[Sampler]' + [Input_file]='HYBRID_software_base_config_file[Sampler]' + [Software_keys]='HYBRID_software_new_input_keys[Sampler]' + ) + declare -rgA HYBRID_afterburner_valid_keys=( + [Executable]='HYBRID_software_executable[Afterburner]' + [Input_file]='HYBRID_software_base_config_file[Afterburner]' + [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' + ) # Variables to be set from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 1f86d36..dcc3e68 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -87,7 +87,7 @@ function Unit_Test__configuration-validate-section-labels() return 1 fi # Test case 5 (no software section) - printf 'Hybrid-handler: Values\n' > "${HYBRID_configuration_file}" + printf 'Hybrid_handler: Values\n' > "${HYBRID_configuration_file}" ( Validate_And_Parse_Configuration_File &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with no software section succeeded.' @@ -107,7 +107,7 @@ function Unit_Test__configuration-validate-all-keys() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml printf ' - Hybrid-handler: + Hybrid_handler: Try: 1 IC: Fake: Values @@ -128,7 +128,7 @@ function Unit_Test__configuration-validate-all-keys() return 1 fi printf ' - Hybrid-handler: {} + Hybrid_handler: {} IC: Executable: /path/to/exec Invalid: 42 @@ -139,7 +139,7 @@ function Unit_Test__configuration-validate-all-keys() return 1 fi printf ' - Hybrid-handler: {} + Hybrid_handler: {} IC: Executable: /path/to/exec Hydro: @@ -163,7 +163,7 @@ function Make_Test_Preliminary_Operations__configuration-parse-general-section() function Unit_Test__configuration-parse-general-section() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml - printf 'Hybrid-handler: {}\nIC:\n Executable: foo\n' > "${HYBRID_configuration_file}" + printf 'Hybrid_handler: {}\nIC:\n Executable: foo\n' > "${HYBRID_configuration_file}" ( Validate_And_Parse_Configuration_File ) if [[ $? -ne 0 ]]; then Print_Error 'Parsing of general section failed.' From 98ebc0083abb02fc593415af2e0aa983a78e093f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Jun 2023 19:22:35 +0200 Subject: [PATCH 087/549] Add array initialization and fix some inaccurate messages --- bash/configuration_parser.bash | 1 + tests/unit_tests_software_input_functionality.bash | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 7791b9b..6300d46 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -36,6 +36,7 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() local input_section_labels label index valid_software_label software_sections_indices # Here word splitting separates keys into array entries, hence we assume keys do not contain spaces! input_section_labels=( $(yq 'keys | .[]' "${HYBRID_configuration_file}") ) + software_sections_indices=() for label in "${input_section_labels[@]}"; do if Element_In_Array_Equals_To "${label}" "${HYBRID_valid_auxiliary_configuration_sections[@]}"; then continue diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index edbff0b..8976ee5 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -39,7 +39,7 @@ function Unit_Test__replace-in-software-input-YAML() keys_to_be_replaced='New_key: value' ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error 'Valid YAML replacement but with new key in valid file succeeded.' + Print_Error 'Valid YAML replacement but with non existent key in valid file succeeded.' return 1 fi # Test case 4: @@ -110,7 +110,7 @@ function Unit_Test__replace-in-software-input-TXT() keys_to_be_replaced='New_key value' ( __static__Replace_Keys_Into_Txt_File &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error 'Valid YAML replacement but with new key in valid file succeeded' + Print_Error 'Valid TXT replacement but with non existent key in valid file succeeded' return 1 fi # Test case 4: From 435345befe823bf0c3b3180fa0bfbbaf15c7af73 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 13 Jun 2023 11:07:23 +0200 Subject: [PATCH 088/549] Remove unnecessary comments that duplicated messages in code --- tests/unit_tests_configuration.bash | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index dcc3e68..1782b3a 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -58,35 +58,30 @@ function Make_Test_Preliminary_Operations__configuration-validate-section-labels function Unit_Test__configuration-validate-section-labels() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml - # Test case 1 printf 'Invalid: Value\n' > "${HYBRID_configuration_file}" ( Validate_And_Parse_Configuration_File &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with invalid section succeeded.' return 1 fi - # Test case 2 (wrong ordering of blocks) printf 'Afterburner: Values\nIC: Values\nHydro: Values\n' > "${HYBRID_configuration_file}" ( Validate_And_Parse_Configuration_File &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with sections in wrong order succeeded.' return 1 fi - # Test case 3 (repeated block) printf 'IC: Values\nSampler: Values\nIC: Again\n' > "${HYBRID_configuration_file}" ( Validate_And_Parse_Configuration_File &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with repeated section succeeded.' return 1 fi - # Test case 4 (ordering fine, but missing block) printf 'IC: Values\nSampler: Values\n' > "${HYBRID_configuration_file}" ( Validate_And_Parse_Configuration_File &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with missing sections succeeded.' return 1 fi - # Test case 5 (no software section) printf 'Hybrid_handler: Values\n' > "${HYBRID_configuration_file}" ( Validate_And_Parse_Configuration_File &> /dev/null ) if [[ $? -eq 0 ]]; then From b46767b72515accd3266262e6467b7de96bbcddc Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 13 Jun 2023 13:57:51 +0200 Subject: [PATCH 089/549] Adjust formatting using no tabs and few minor aspects --- bash/system_requirements.bash | 82 ++++++++++++----------- tests/unit_tests_system_requirements.bash | 30 ++++----- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index a4b7fcd..e2717dc 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -10,49 +10,50 @@ function __static__Declare_System_Requirements() { - declare -gA HYBRID_systemRequirements=( + declare -gA HYBRID_system_requirements=( ['bash']='4.4.0' ['awk']='4.1.0' ['sed']='4.2.1' - ['tput']='5.9.0' # the last number is a date - ['yq']='4.0' + ['tput']='5.9.0' # the last number is a date + ['yq']='4.0' ) } function Check_System_Requirements() { - local program requirements_present + local program requirements_present min_version requirements_present=0 declare -A system_found_versions __static__Declare_System_Requirements - for program in "${!HYBRID_systemRequirements[@]}"; do + for program in "${!HYBRID_system_requirements[@]}"; do + min_version=${HYBRID_system_requirements["${program}"]} if ! __static__Try_Find_Requirement "${program}"; then - Print_Error "${program}" 'not found! Minimum version' "${HYBRID_systemRequirements[${program}]}" 'is required.' - requirements_present=1 - continue - fi - if ! __static__Try_Find_Version "${program}"; then - Print_Warning "Unable to find version of ${program}, skipping version check! Please ensure that current version is at least ${HYBRID_systemRequirements[${program}]}." - continue - fi - if ! __static__Check_Version_Suffices "${program}"; then - Print_Error "${program} version ${system_found_versions[${program}]} found, but version ${HYBRID_systemRequirements[${program}]} is required." - requirements_present=1 + Print_Error "'${program}' command not found! Minimum version ${min_version} is required." + requirements_present=1 + continue + fi + if ! __static__Try_Find_Version "${program}"; then + Print_Warning "Unable to find version of '${program}', skipping version check!"\ + "Please ensure that current version is at least ${min_version}." + continue + fi + if ! __static__Check_Version_Suffices "${program}"; then + Print_Error "'${program}' version ${system_found_versions[${program}]} found,"\ + " but version ${min_version} is required." + requirements_present=1 fi done if [[ ${requirements_present} -ne 0 ]]; then Print_Fatal_And_Exit 'Please install (maybe locally) the required versions of the above programs.' - else - return ${requirements_present} fi } function __static__Try_Find_Requirement() { - if hash $1 2>/dev/null; then + if hash "$1" 2>/dev/null; then return 0 else - return 1 + return 1 fi } @@ -63,22 +64,23 @@ function __static__Try_Find_Version() bash ) found_version="${BASH_VERSINFO[@]:0:3}" found_version="${found_version// /.}" - ;; - awk ) - found_version=$(awk --version | head -n1 | grep -o "[0-9.]\+" | head -n1) - ;; - sed ) - found_version=$(sed --version | head -n1 | grep -o "[0-9.]\+" | head -n1) - ;; - tput ) - found_version=$(tput -V | grep -o "[0-9.]\+" | cut -d'.' -f1,2) - ;; - yq ) - found_version=$(yq --version | grep -o "v[0-9.]\+" | cut -d'v' -f2) - ;; - *) - return 1 - esac + ;; + awk ) + found_version=$(awk --version | head -n1 | grep -o "[0-9.]\+" | head -n1) + ;; + sed ) + found_version=$(sed --version | head -n1 | grep -o "[0-9.]\+" | head -n1) + ;; + tput ) + found_version=$(tput -V | grep -o "[0-9.]\+" | cut -d'.' -f1,2) + ;; + yq ) + found_version=$(yq --version | grep -o "v[0-9.]\+" | cut -d'v' -f2) + ;; + *) + return 1 + ;; + esac if [[ ${found_version} =~ ^[0-9]([.0-9])*$ ]]; then system_found_versions["$1"]="${found_version}" else @@ -90,15 +92,15 @@ function __static__Check_Version_Suffices() { # Here we assume that the programs follow the semantic versioning standard local required found versioning - required=($(echo "${HYBRID_systemRequirements[$1]}" | tr '.' ' ')) + required=($(echo "${HYBRID_system_requirements[$1]}" | tr '.' ' ')) found=($(echo "${system_found_versions[$1]}" | tr '.' ' ')) if [[ "${required[${versioning}]}" -eq "${found[${versioning}]}" ]]; then - return 0 + return 0 fi for versioning in ${!required[@]}; do if [[ "${required[${versioning}]}" -lt "${found[${versioning}]}" ]]; then - return 0 - fi + return 0 + fi done return 1 } diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 46a55de..84b07f1 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -7,12 +7,19 @@ # #=================================================== +# NOTE: The following unit test checks the system requirements code by defining functions +# with the same name as the required system commands. This will make bash run these +# instead of the system ones (remember that functions have higher priority than +# external commands). As each fake command print the variable ${version} as version +# number, it is then possible to mimic a system complying with the requirements and +# another one violating them. + function __static__Fake_Command_Version() { if [[ "$3" = "$1" ]]; then - echo $2 + printf "$2\n" else - Print_Fatal_And_Exit "wrong usage of ${FUNCNAME[1]}" + Print_Fatal_And_Exit "Wrong usage of ${FUNCNAME[1]} function." fi } @@ -32,31 +39,24 @@ function __static__Inhibit_Commands_Version() } function yq() { - __static__Fake_Command_Version '--version' "yq (https://github.com/mikefarah/yq/) version v${version}" "$@" + __static__Fake_Command_Version '--version' "yq (https://github.com/mikefarah/yq/) version v${version}" "$@" } } -# This tests asks Check_System_Requirements first for success, in case of a 'good' input; -# and for failure, in case of a 'bad' one. Here 99.0.0 and 1.0 respectively should suffice -# for most commands. Since the check only uses the commands to look for their version, they -# are faked here to only accept the specific version flag, and fail otherwise. - function Unit_Test__system-requirements() { __static__Inhibit_Commands_Version - local good_version='99.0.0' - local bad_version='1.0' - + local good_version='999.0.0' bad_version='0.0' local version=${good_version} - ( Check_System_Requirements &> /dev/null ) + ( Check_System_Requirements ) if [[ $? -ne 0 ]]; then - Print_Error "${good_version} is lower than some requirements" + Print_Error "Check system requirements of good system failed." return 1 fi version=${bad_version} ( Check_System_Requirements &> /dev/null ) - if [[ $? -ne 1 ]]; then - Print_Error "${bad_version} is higher than some requirements" + if [[ $? -eq 0 ]]; then + Print_Error "Check system requirements of bad system succeeded." return 1 fi } From 0a34d0429df426ac99d072b6653c3d052b3cb9fa Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 13 Jun 2023 14:11:01 +0200 Subject: [PATCH 090/549] Make system requirement tests more precise and failing --- tests/unit_tests_system_requirements.bash | 28 +++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 84b07f1..34e6a6e 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -10,7 +10,7 @@ # NOTE: The following unit test checks the system requirements code by defining functions # with the same name as the required system commands. This will make bash run these # instead of the system ones (remember that functions have higher priority than -# external commands). As each fake command print the variable ${version} as version +# external commands). As each fake command print the variable ${...version} as version # number, it is then possible to mimic a system complying with the requirements and # another one violating them. @@ -27,34 +27,44 @@ function __static__Inhibit_Commands_Version() { function awk() { - __static__Fake_Command_Version '--version' "GNU Awk ${version}, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)" "$@" + __static__Fake_Command_Version\ + '--version' "GNU Awk ${awk_version}, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)" "$@" } function sed() { - __static__Fake_Command_Version '--version' "sed (GNU sed) ${version} Packaged by Debian" "$@" + __static__Fake_Command_Version\ + '--version' "sed (GNU sed) ${sed_version} Packaged by Debian" "$@" } function tput() { - __static__Fake_Command_Version '-V' "ncurses ${version}" "$@" + __static__Fake_Command_Version\ + '-V' "ncurses ${tput_version}" "$@" } function yq() { - __static__Fake_Command_Version '--version' "yq (https://github.com/mikefarah/yq/) version v${version}" "$@" + __static__Fake_Command_Version\ + '--version' "yq (https://github.com/mikefarah/yq/) version v${yq_version}" "$@" } } function Unit_Test__system-requirements() { __static__Inhibit_Commands_Version - local good_version='999.0.0' bad_version='0.0' - local version=${good_version} + local {awk,sed,tput,yq}_version + awk_version=4.1 + sed_version=4.2.1 + tput_version=5.9 + yq_version=4 ( Check_System_Requirements ) if [[ $? -ne 0 ]]; then Print_Error "Check system requirements of good system failed." return 1 fi - version=${bad_version} - ( Check_System_Requirements &> /dev/null ) + awk_version=4.0.9 + sed_version=4.2.0 + tput_version=5.8 + yq_version=3.9.98 + ( Check_System_Requirements &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error "Check system requirements of bad system succeeded." return 1 From bc27b4bc858ec874af7c7c3b7d7c47c8db5a1b36 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Jun 2023 16:30:34 +0200 Subject: [PATCH 091/549] Let tests runner run all tests from dedicated folder --- tests/tests_runner | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/tests_runner b/tests/tests_runner index 907af25..e8aa902 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -112,6 +112,8 @@ function Run_Tests() (( HYBRIDT_tests_run++ )) # Run in sub-shell to have the same starting environment ( + # Make sure each test is run from the same folder (no matter from where tests are run) + cd "${HYBRIDT_folder_to_run_tests}" || exit ${HYBRID_fatal_builtin} Call_through_interface 'Make_Test_Preliminary_Operations' "${test_name}" Announce_Running_Test "${test_name}" Call_through_interface 'Run_Test' "${test_name}" From acabc0ac5c8e017aba05b7a65fae8f130491a21b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Jun 2023 17:53:12 +0200 Subject: [PATCH 092/549] Improve unit tests framework and global variables definition --- bash/global_variables.bash | 1 + tests/tests_runner | 2 +- tests/unit_tests.bash | 18 +++++++++++------- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index cc7c29b..8b21706 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -15,6 +15,7 @@ # this mechanism, like dashes or spaces! function Define_Further_Global_Variables() { + Ensure_That_Given_Variables_Are_Set_And_Not_Empty HYBRID_repository_global_path # Constant information readonly HYBRID_valid_software_configuration_sections=( 'IC' diff --git a/tests/tests_runner b/tests/tests_runner index e8aa902..20d9971 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -113,9 +113,9 @@ function Run_Tests() # Run in sub-shell to have the same starting environment ( # Make sure each test is run from the same folder (no matter from where tests are run) + Announce_Running_Test "${test_name}" cd "${HYBRIDT_folder_to_run_tests}" || exit ${HYBRID_fatal_builtin} Call_through_interface 'Make_Test_Preliminary_Operations' "${test_name}" - Announce_Running_Test "${test_name}" Call_through_interface 'Run_Test' "${test_name}" local test_outcome=$? Call_through_interface 'Clean_Tests_Environment_For_Following_Test' "${test_name}" diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index abe3501..105760d 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -27,22 +27,26 @@ function Define_Available_Tests() } function Make_Test_Preliminary_Operations() -{ - Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 -} - -function Run_Test() { local test_name=$1 { + # The following global variable is needed whe defining the software global variables + # and since it is likely that most unit tests need it, let's always define it + readonly HYBRID_repository_global_path="${HYBRIDT_repository_top_level_path}" + # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" - Unit_Test__${test_name} + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 } &>> "${HYBRIDT_log_file}" } +function Run_Test() +{ + Unit_Test__$1 &>> "${HYBRIDT_log_file}" +} + function Clean_Tests_Environment_For_Following_Test() { - Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" } #======================================================================================================================= From 659f2d89c6cea76f383d22f632360dcfeb526b64 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Jun 2023 10:19:21 +0200 Subject: [PATCH 093/549] Implement parsing of execution mode --- Hybrid-handler | 6 +-- bash/command_line_parsers/main_parser.bash | 31 +++++++++++- tests/unit_tests_command_line_parser.bash | 56 +++++++++++++++++++++- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index 0f89bab..e0dfa1f 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -54,7 +54,7 @@ function Define_Repository_Global_Path() function Store_Command_Line_Options_Into_Global_Variable() { - HYBRID_specified_command_line_options=( "$@" ) + HYBRID_command_line_options_to_parse=( "$@" ) } function Source_Codebase_Files() @@ -65,13 +65,13 @@ function Source_Codebase_Files() function Act_And_Exit_If_User_Ran_Auxiliary_Modes() { case "${HYBRID_execution_mode}" in - help ) + *help ) Give_Required_Help ;;& version ) Print_Software_Version ;;& - help | version ) + *help | version ) exit ${HYBRID_success_exit_code} ;; esac diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 32ad068..413bf08 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -9,10 +9,39 @@ function Parse_Execution_Mode() { - Print_Not_Implemented_Function_Error + if [[ "${#HYBRID_command_line_options_to_parse[@]}" -eq 0 ]]; then + return 0 + fi + #Locally set function arguments to take advantage of shift + set -- "${HYBRID_command_line_options_to_parse[@]}" + case "$1" in + help | --help ) + HYBRID_execution_mode='help' + ;; + version | --version ) + HYBRID_execution_mode='version' + ;; + do ) + HYBRID_execution_mode='do' + ;; + * ) + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + "Specified mode '$1' not valid! Run 'Hybrid-handler help' to get further information." + esac + shift + # Update the global array with remaining options to be parsed + HYBRID_command_line_options_to_parse=( "$@" ) + # Ignore any command line option in some specific cases + if [[ ${HYBRID_execution_mode} =~ ^(help|version)$ ]]; then + HYBRID_command_line_options_to_parse=() + elif Element_In_Array_Equals_To '--help' "${HYBRID_command_line_options_to_parse[@]}"; then + HYBRID_execution_mode+='-help' + HYBRID_command_line_options_to_parse=() + fi } function Parse_Command_Line_Options() { Print_Not_Implemented_Function_Error + return 1 } diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index c95367b..18bf21a 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -7,12 +7,64 @@ # #=================================================== -function Unit_Test__parse-command-line-options() +function Make_Test_Preliminary_Operations__parse-execution-mode() { - return 0 + local file_to_be_sourced list_of_files + list_of_files=( + 'command_line_parsers/main_parser.bash' + 'global_variables.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables } +function __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success() +( + local expected_option=$1\ + expected_size=$2\ + first_option="${HYBRID_command_line_options_to_parse[0]}" + Parse_Execution_Mode + if [[ $? -ne 0 ]] ||\ + [[ "${HYBRID_execution_mode}" != "${expected_option}" ]] ||\ + [[ ${#HYBRID_command_line_options_to_parse[@]} -ne "${expected_size}" ]]; then + Print_Error "Parsing of valid execution mode '${first_option}' failed." + return 1 + fi +) + function Unit_Test__parse-execution-mode() +{ + HYBRID_command_line_options_to_parse=() + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'help' 0 || return 1 + HYBRID_command_line_options_to_parse=( 'help' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'help' 0 || return 1 + HYBRID_command_line_options_to_parse=( '--help' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'help' 0 || return 1 + HYBRID_command_line_options_to_parse=( 'version' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'version' 0 || return 1 + HYBRID_command_line_options_to_parse=( '--version' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'version' 0 || return 1 + HYBRID_command_line_options_to_parse=( 'help' 'with-invalid' 'irrelevant-options' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'help' 0 || return 1 + HYBRID_command_line_options_to_parse=( 'version' 'with-invalid' 'irrelevant-options' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'version' 0 || return 1 + HYBRID_command_line_options_to_parse=( 'do' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'do' 0 || return 1 + HYBRID_command_line_options_to_parse=( 'do' '-o' '/path/to/folder' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'do' 2 || return 1 + HYBRID_command_line_options_to_parse=( 'do' '-o' '/path/to/folder' '--help' ) + __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'do-help' 0 || return 1 + HYBRID_command_line_options_to_parse=( 'invalid-mode' ) + ( Parse_Execution_Mode ) + if [[ $? -eq 0 ]]; then + Print_Error 'Parsing of invalid execution mode succeeded.' + return 1 + fi +} + +function Unit_Test__parse-command-line-options() { return 0 } From f2504804ae4223fb00a5b35a358b9de57bb588a3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Jun 2023 15:05:43 +0200 Subject: [PATCH 094/549] Implement printing of helper messages to user At the moment, only in-terminal messages are used to help the user. A general help message is given in 'help' mode and another different one is created for each execution mode to explain available options. --- bash/command_line_parsers/helper.bash | 107 +++++++++++++++++++++++++- tests/unit_tests_help_for_user.bash | 32 ++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests_help_for_user.bash diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index 58f3dec..b2a41c0 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -9,5 +9,110 @@ function Give_Required_Help() { - Print_Not_Implemented_Function_Error + case "${HYBRID_execution_mode}" in + help ) + __static__Print_Main_Help_Message + ;; + do-help ) + __static__Print_Do_Help_Message + ;; + * ) + Print_Internal_And_Exit\ + 'Unexpected value of HYBRID_execution_mode=${HYBRID_execution_mode} in ${FUNCNAME}' + ;; + esac + +} + +function __static__Print_Main_Help_Message() +{ + declare -A section_headers auxiliary_modes_description execution_modes_description + section_headers=( + [auxiliary_modes_description]='Auxiliary modes for help, information or setup' + [execution_modes_description]='Prepare needed files and folders and/or submit/run new simulation(s)' + ) + auxiliary_modes_description=( + [help]='Display this help message' + [version]='Get information about the version in use' + ) + execution_modes_description=( + [do]='Do everything is necessary to run the workflow given in the configuration file' + ) + __static__Print_Handler_Header_And_Usage_Synopsis + __static__Print_Modes_Description + Check_System_Requirements_And_Make_Report 2> /dev/null || true +} + +function __static__Print_Do_Help_Message() +{ + printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n'\ + 'You can specify the following command line options to the' 'do' 'execution mode:' + __static__Print_Command_Line_Option_Help\ + '-o | --output-directory' "${HYBRID_output_directory}"\ + "Directory where the run folder(s) will be created." + __static__Print_Command_Line_Option_Help\ + '-c | --configuration-file' "${HYBRID_configuration_file}"\ + "YAML configuration file to be used by the handler." +} + +function __static__Print_Handler_Header_And_Usage_Synopsis() +{ + printf '\e[96m%s\e[0m\n'\ + ' #----------------------------------------------------------------------------#'\ + ' # __ __ __ _ __ __ __ ____ #'\ + ' # / / / /_ __/ /_ _____(_)___/ / / / / /___ _____ ____/ / /__ _____ #'\ + ' # / /_/ / / / / __ \/ ___/ / __ / / /_/ / __ `/ __ \/ __ / / _ \/ ___/ #'\ + ' # / __ / /_/ / /_/ / / / / /_/ / / __ / /_/ / / / / /_/ / / __/ / #'\ + ' # /_/ /_/\__, /_.___/_/ /_/\__,_/ /_/ /_/\__,_/_/ /_/\__,_/_/\___/_/ #'\ + ' # /____/ #'\ + ' # #'\ + ' #----------------------------------------------------------------------------#' + printf '\n' + printf '\e[38;5;85m%s\e[0m\n'\ + ' USAGE: Hybrid-handler [--help] [--version]'\ + ' [...]' + printf '\n' +} + +function __static__Print_Modes_Description() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty section_headers "${!section_headers[@]}" + local section mode + printf '\e[38;5;38m %s\e[0m\n\n'\ + 'Here in the following you find an overview of the existing execution modes.' + for section in "${!section_headers[@]}"; do + printf "\n \e[93m${section_headers[${section}]}\e[0m\n" + declare -n list_of_modes="${section}" + for mode in "${!list_of_modes[@]}"; do + printf '\e[38;5;85m%15s \e[96m%s\e[0m\n'\ + "${mode}"\ + "${list_of_modes[${mode}]}" + done | sort --ignore-leading-blanks + done + printf '\n\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\n\n'\ + 'Use' '--help' 'after each non auxiliary mode to get further information about it.' +} + +function __static__Print_Command_Line_Option_Help() +{ + local -r length_option=30\ + indentation=' '\ + column_sep=' '\ + options_color='\e[38;5;85m'\ + text_color='\e[96m'\ + default_value_color='\e[93m'\ + default_text='\e[0m' + local name default_value description left_column left_column_length + name=$1 + default_value=$2 + description=$3 + shift 3 + printf -v left_column "${indentation}${options_color}%*s${column_sep}" ${length_option} "${name}" + left_column_length=$(( ${#indentation} + ${length_option} + ${#column_sep} )) + printf "\n${left_column}${text_color}%s${default_text}\n" "${description}" + while [[ $# -gt 0 ]]; do + printf "%${left_column_length}s${text_color}%s${default_text}\n" '' "$1" + shift + done + printf "%${left_column_length}sDefault: ${default_value_color}${default_value}${default_text}\n" '' } diff --git a/tests/unit_tests_help_for_user.bash b/tests/unit_tests_help_for_user.bash new file mode 100644 index 0000000..4833d3b --- /dev/null +++ b/tests/unit_tests_help_for_user.bash @@ -0,0 +1,32 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Test_Preliminary_Operations__give-requested-help() +{ + source "${HYBRIDT_repository_top_level_path}/bash/command_line_parsers/helper.bash" || exit ${HYBRID_fatal_builtin} + HYBRID_configuration_file='./config.yaml' + HYBRID_output_directory='.' +} + +function __static__Run_Helper_Expecting_Success() +{ + ( Give_Required_Help ) + if [[ $? -ne 0 ]]; then + Print_Error "Providing help in '${HYBRID_execution_mode}' execution mode failed." + return 1 + fi +} + +function Unit_Test__give-requested-help() +{ + HYBRID_execution_mode='help' + __static__Run_Helper_Expecting_Success || return 1 + HYBRID_execution_mode='do-help' + __static__Run_Helper_Expecting_Success || return 1 +} From 3e44824ac3b555200890b40f56d73dfd66c72daa Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Jun 2023 15:07:48 +0200 Subject: [PATCH 095/549] Make invalid option specification function an utility --- bash/utility_functions.bash | 6 ++++++ tests/command_line_parser_for_tests.bash | 10 ++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index b2d0377..6ab6d91 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -120,6 +120,12 @@ function Print_Centered_Line() "${padding_utility}" } +function Print_Option_Specification_Error_And_Exit() +{ + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + "The value of the option \"$1\" was not correctly specified (either forgotten or invalid)!" +} + function Print_Not_Implemented_Function_Error() { Print_Error "Function \"${FUNCNAME[1]}\" not implemented yet, skipping it." diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 5459c52..08b1b8e 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -40,7 +40,7 @@ function Parse_Tests_Command_Line_Options() if [[ $2 =~ ^[0-3]$ ]]; then readonly HYBRIDT_report_level=$2 else - __static__Print_Option_Specification_Error_And_Exit "$1" + Print_Option_Specification_Error_And_Exit "$1" fi shift 2 ;; -t | --run-tests ) @@ -50,7 +50,7 @@ function Parse_Tests_Command_Line_Options() elif [[ $2 =~ ^[[:alpha:]*?] ]]; then __static__Set_Tests_To_Be_Run_Using_Globbing "$2" else - __static__Print_Option_Specification_Error_And_Exit "$1" + Print_Option_Specification_Error_And_Exit "$1" fi else __static__Print_List_Of_Tests @@ -175,11 +175,5 @@ function __static__Print_List_Of_Tests() done | column -c "${width_of_list}" } -function __static__Print_Option_Specification_Error_And_Exit() -{ - exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ - "The value of the option \"$1\" was not correctly specified (either forgotten or invalid)!" -} - Make_Functions_Defined_In_This_File_Readonly From c67081f260f8e1817d933747ae76c5d700a5f2db Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Jun 2023 10:58:35 +0200 Subject: [PATCH 096/549] Fix system versions check and refactor system information Now the analysis of system available commands and their feature is delegated to a function and this will be reused next to implement the printing of a system report (useful for the handler help). --- bash/system_requirements.bash | 98 ++++++++++++++++++----- tests/unit_tests_system_requirements.bash | 2 +- 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index e2717dc..dfa3bbf 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -11,34 +11,41 @@ function __static__Declare_System_Requirements() { declare -gA HYBRID_system_requirements=( - ['bash']='4.4.0' - ['awk']='4.1.0' - ['sed']='4.2.1' - ['tput']='5.9.0' # the last number is a date - ['yq']='4.0' + [bash]='4.4.0' + [awk]='4.1.0' + [sed]='4.2.1' + [tput]='5.7' + [yq]='4.0' ) } function Check_System_Requirements() { + __static__Declare_System_Requirements local program requirements_present min_version requirements_present=0 - declare -A system_found_versions - __static__Declare_System_Requirements + # NOTE: The following associative array will be used to store system information + # and since bash does not support arrays entries in associative arrays, then + # just strings with information will be used. Each entry of this array + # contains whether the command was found, its version and whether the version + # meets the requirement or not. A '|' is used to separate the fields in the + # string and '---' is used to indicate a negative outcome in the field + declare -A system_information + __static__Analyze_System_Properties for program in "${!HYBRID_system_requirements[@]}"; do min_version=${HYBRID_system_requirements["${program}"]} - if ! __static__Try_Find_Requirement "${program}"; then + if [[ $(cut -d'|' -f1 <<< "${system_information[${program}]}") = '---' ]]; then Print_Error "'${program}' command not found! Minimum version ${min_version} is required." requirements_present=1 continue fi - if ! __static__Try_Find_Version "${program}"; then + if [[ $(cut -d'|' -f2 <<< "${system_information[${program}]}") = '---' ]]; then Print_Warning "Unable to find version of '${program}', skipping version check!"\ "Please ensure that current version is at least ${min_version}." continue fi - if ! __static__Check_Version_Suffices "${program}"; then - Print_Error "'${program}' version ${system_found_versions[${program}]} found,"\ + if [[ $(cut -d'|' -f3 <<< "${system_information[${program}]}") = '---' ]]; then + Print_Error "'${program}' version ${system_information[${program}]} found,"\ " but version ${min_version} is required." requirements_present=1 fi @@ -48,6 +55,29 @@ function Check_System_Requirements() fi } +function __static__Analyze_System_Properties() +{ + Ensure_That_Given_Variables_Are_Set system_information + local program + for program in "${!HYBRID_system_requirements[@]}"; do + min_version=${HYBRID_system_requirements["${program}"]} + if __static__Try_Find_Requirement "${program}"; then + system_information[${program}]='found|' + else + system_information[${program}]='---||' # Empty following fields + continue + fi + if ! __static__Try_Find_Version "${program}"; then + continue + fi + if __static__Check_Version_Suffices "${program}"; then + system_information[${program}]+='OK' + else + system_information[${program}]+='---' + fi + done +} + function __static__Try_Find_Requirement() { if hash "$1" 2>/dev/null; then @@ -82,25 +112,53 @@ function __static__Try_Find_Version() ;; esac if [[ ${found_version} =~ ^[0-9]([.0-9])*$ ]]; then - system_found_versions["$1"]="${found_version}" + system_information["$1"]+="${found_version}|" else + system_information["$1"]+='---|' return 1 fi } +# NOTE: This function would be almost a one-liner using 'sort -V', but at the moment we do not +# impose to have GNU coreutils installed, which we should probably do next... function __static__Check_Version_Suffices() { - # Here we assume that the programs follow the semantic versioning standard - local required found versioning - required=($(echo "${HYBRID_system_requirements[$1]}" | tr '.' ' ')) - found=($(echo "${system_found_versions[$1]}" | tr '.' ' ')) - if [[ "${required[${versioning}]}" -eq "${found[${versioning}]}" ]]; then + # Here we assume that the programs were found and their version, too + local program version_required version_found index + program=$1 + version_required="${HYBRID_system_requirements[${program}]}" + version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") + # Drop dot at the end of versions, if any is there (it would break version filling) + version_required=${version_required%.} + version_found=${version_found%.} + # Ensure versions are of the same length to make following algorithm work + if [[ ${#version_found} -ne ${#version_required} ]]; then + if [[ ${#version_found} -lt ${#version_required} ]]; then + declare -n shorter_array=version_found + declare -n longer_array=version_required + else + declare -n shorter_array=version_required + declare -n longer_array=version_found + fi + while [[ ${#version_found[@]} -ne ${#version_required[@]} ]]; do + shorter_array+='.0' # Add zeroes to shorter string + done + fi + # If versions are equal, we're done + if [[ "${version_required}" = "${version_found}" ]]; then return 0 fi - for versioning in ${!required[@]}; do - if [[ "${required[${versioning}]}" -lt "${found[${versioning}]}" ]]; then + # Split version strings into array of numbers replacing '.' by ' ' and let word splitting do the split + version_required=( ${version_required//./ } ) + version_found=( ${version_found//./ } ) + # Now version arrays have same number of entries, compare them + for index in ${!version_required[@]}; do + if [[ "${version_required[index]}" -eq "${version_found[index]}" ]]; then + continue + elif [[ "${version_required[index]}" -lt "${version_found[index]}" ]]; then return 0 + else + return 1 fi done - return 1 } diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 34e6a6e..5c5a29f 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -64,7 +64,7 @@ function Unit_Test__system-requirements() sed_version=4.2.0 tput_version=5.8 yq_version=3.9.98 - ( Check_System_Requirements &> /dev/null ) + ( Check_System_Requirements &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error "Check system requirements of bad system succeeded." return 1 From 090576aa44765bf27fcc889d4cb66556f14e90b9 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Jun 2023 11:30:46 +0200 Subject: [PATCH 097/549] Implement system requirements report functionality --- bash/system_requirements.bash | 57 +++++++++++++++++++++-- tests/unit_tests_system_requirements.bash | 12 ++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index dfa3bbf..db82e7d 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -11,11 +11,11 @@ function __static__Declare_System_Requirements() { declare -gA HYBRID_system_requirements=( - [bash]='4.4.0' - [awk]='4.1.0' + [bash]='4.4' + [awk]='4.1' [sed]='4.2.1' [tput]='5.7' - [yq]='4.0' + [yq]='4' ) } @@ -55,6 +55,18 @@ function Check_System_Requirements() fi } +function Check_System_Requirements_And_Make_Report() +{ + __static__Declare_System_Requirements + local program command_found_string required_version_label + declare -A system_information + __static__Analyze_System_Properties + printf "\n \e[93mSystem requirements overview:${default}\n\n" + for program in "${!HYBRID_system_requirements[@]}"; do + __static__Print_Requirement_Report_Line "${program}" + done +} + function __static__Analyze_System_Properties() { Ensure_That_Given_Variables_Are_Set system_information @@ -162,3 +174,42 @@ function __static__Check_Version_Suffices() fi done } + +function __static__Print_Requirement_Report_Line() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information + local -r emph_color='\e[96m'\ + red='\e[91m'\ + green='\e[92m'\ + yellow='\e[93m'\ + text_color='\e[38;5;38m'\ + default='\e[0m' + local line found version_found version_ok tmp_array program=$1 + tmp_array=( ${system_information[${program}]//|/ } ) # Unquoted to let word splitting act + found=${tmp_array[0]} + version_found=${tmp_array[1]} + version_ok=${tmp_array[2]} + printf -v line " ${text_color}Command ${emph_color}%6s${text_color}: ${default}" "${program}" + if [[ ${found} = '---' ]]; then + line+="${red}NOT " + else + line+="${green} " + fi + line+=$(printf "found ${text_color}Required version: ${emph_color}%5s${default}"\ + "${HYBRID_system_requirements[${program}]}") + if [[ ${found} != '---' ]]; then + line+=" ${text_color}System version:${default} " + if [[ ${version_found} = '---' ]]; then + line+="${yellow}Unable to recover" + else + if [[ ${version_ok} = '---' ]]; then + line+="${red}" + else + line+="${green}" + fi + line+="${version_found}" + fi + line+="${default}" + fi + printf "${line}\n" +} diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 5c5a29f..56e8f3a 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -60,13 +60,23 @@ function Unit_Test__system-requirements() Print_Error "Check system requirements of good system failed." return 1 fi + ( Check_System_Requirements_And_Make_Report ) + if [[ $? -ne 0 ]]; then + Print_Error "Check system requirements making report of good system failed." + return 1 + fi awk_version=4.0.9 sed_version=4.2.0 - tput_version=5.8 + tput_version=5.9 yq_version=3.9.98 ( Check_System_Requirements &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error "Check system requirements of bad system succeeded." return 1 fi + ( Check_System_Requirements_And_Make_Report ) + if [[ $? -ne 0 ]]; then + Print_Error "Check system requirements making report of bad system failed." + return 1 + fi } From 61b122f8dfa680114253140b626e3a51ab57609d Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Jun 2023 11:53:19 +0200 Subject: [PATCH 098/549] Improve versions regex and make functions more solid --- bash/system_requirements.bash | 11 +++++++---- tests/tests_runner | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index db82e7d..34f3d3c 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -101,6 +101,7 @@ function __static__Try_Find_Requirement() function __static__Try_Find_Version() { + Ensure_That_Given_Variables_Are_Set system_information local found_version case "$1" in bash ) @@ -123,7 +124,7 @@ function __static__Try_Find_Version() return 1 ;; esac - if [[ ${found_version} =~ ^[0-9]([.0-9])*$ ]]; then + if [[ ${found_version} =~ ^[0-9](.[0-9]+)*$ ]]; then system_information["$1"]+="${found_version}|" else system_information["$1"]+='---|' @@ -135,14 +136,16 @@ function __static__Try_Find_Version() # impose to have GNU coreutils installed, which we should probably do next... function __static__Check_Version_Suffices() { + Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information # Here we assume that the programs were found and their version, too local program version_required version_found index program=$1 version_required="${HYBRID_system_requirements[${program}]}" version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") - # Drop dot at the end of versions, if any is there (it would break version filling) - version_required=${version_required%.} - version_found=${version_found%.} + if [[ ! ${version_found} =~ ^[0-9](.[0-9]+)*$ ]] ||\ + [[ ! ${version_required} =~ ^[0-9](.[0-9]+)*$ ]]; then + Print_Internal_And_Exit "Wrong syntax in version strings in ${FUNCNAME}." + fi # Ensure versions are of the same length to make following algorithm work if [[ ${#version_found} -ne ${#version_required} ]]; then if [[ ${#version_found} -lt ${#version_required} ]]; then diff --git a/tests/tests_runner b/tests/tests_runner index 20d9971..3faf7ef 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -95,7 +95,7 @@ function Prepare_Test_Environment() local postfix postfix=$(date +'%Y-%m-%d_%H%M%S') if [[ -d "${HYBRIDT_folder_to_run_tests}" ]]; then - Print_Warning "Found \"${HYBRIDT_folder_to_run_tests}\", renaming it!" + Print_Warning "Found \"${HYBRIDT_folder_to_run_tests}\", renaming it!\n" mv "${HYBRIDT_folder_to_run_tests}"\ "${HYBRIDT_folder_to_run_tests}_${postfix}" || exit ${HYBRID_fatal_builtin} fi @@ -105,7 +105,7 @@ function Prepare_Test_Environment() function Run_Tests() { if [[ ${HYBRIDT_report_level} -eq 3 ]]; then - Print_Info "\nRunning ${#HYBRIDT_tests_to_be_run[@]} test(s):\n" + Print_Info "Running ${#HYBRIDT_tests_to_be_run[@]} test(s):\n" fi local test_name for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do From 56a35ede73dbe92c68e9180002c855f0599ed474 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Jun 2023 17:58:28 +0200 Subject: [PATCH 099/549] Make global arrays with system requirements readonly --- bash/system_requirements.bash | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 34f3d3c..e6c32af 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -10,13 +10,16 @@ function __static__Declare_System_Requirements() { - declare -gA HYBRID_system_requirements=( - [bash]='4.4' - [awk]='4.1' - [sed]='4.2.1' - [tput]='5.7' - [yq]='4' - ) + if ! declare -p HYBRID_versions_requirements &> /dev/null; then + declare -rgA HYBRID_versions_requirements=( + [bash]='4.4' + [awk]='4.1' + [sed]='4.2.1' + [tput]='5.7' + [yq]='4' + ) + declare -rga HYBRID_gnu_programs_required=( awk sed sort wc ) + fi } function Check_System_Requirements() From b198331d734f8f8c5498dbe86632ca0596e6d2f6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Jun 2023 17:59:48 +0200 Subject: [PATCH 100/549] Let fake commands call original ones if version not asked After all in the tests we want to fake the version behaviour of needed commands, but not forbid to use this in tests. In particular, this is needed for tput which is the reliable way to get the width of the terminal. --- tests/unit_tests_system_requirements.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 56e8f3a..4224ee7 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -17,9 +17,9 @@ function __static__Fake_Command_Version() { if [[ "$3" = "$1" ]]; then - printf "$2\n" + printf "$2\n" # If version was requested print fake string else - Print_Fatal_And_Exit "Wrong usage of ${FUNCNAME[1]} function." + command ${FUNCNAME[1]} "${@:3}" # otherwise run original commands with given arguments fi } From 87c0c2d3bcb9f87824541aea5f0d4b7d4f6e6925 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Jun 2023 18:10:08 +0200 Subject: [PATCH 101/549] Implement check and report of required GNU commands --- bash/system_requirements.bash | 92 ++++++++++++++++++++--- tests/unit_tests_system_requirements.bash | 10 ++- 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index e6c32af..dfb4742 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -32,11 +32,15 @@ function Check_System_Requirements() # just strings with information will be used. Each entry of this array # contains whether the command was found, its version and whether the version # meets the requirement or not. A '|' is used to separate the fields in the - # string and '---' is used to indicate a negative outcome in the field + # string and '---' is used to indicate a negative outcome in the field. + # It has been decided to use the same array to store the availability of GNU + # tools. For these, the key is prefixed by 'GNU-' and the content has a first + # "field" that is 'found' or '---' and a second one that is either 'OK' or '---' + # to indicate whether the GNU version of the command is in use or not. declare -A system_information __static__Analyze_System_Properties - for program in "${!HYBRID_system_requirements[@]}"; do - min_version=${HYBRID_system_requirements["${program}"]} + for program in "${!HYBRID_versions_requirements[@]}"; do + min_version=${HYBRID_versions_requirements["${program}"]} if [[ $(cut -d'|' -f1 <<< "${system_information[${program}]}") = '---' ]]; then Print_Error "'${program}' command not found! Minimum version ${min_version} is required." requirements_present=1 @@ -56,26 +60,50 @@ function Check_System_Requirements() if [[ ${requirements_present} -ne 0 ]]; then Print_Fatal_And_Exit 'Please install (maybe locally) the required versions of the above programs.' fi + for program in "${HYBRID_gnu_programs_required[@]}"; do + if [[ $(cut -d'|' -f2 <<< "${system_information[${program}]}") = '---' ]]; then + Print_Error "'${program#GNU-}' either not found or non-GNU version in use."\ + "Please, ensure that '${program}' is installed and in use." + requirements_present=1 + fi + done + if [[ ${requirements_present} -ne 0 ]]; then + Print_Fatal_And_Exit 'The GNU version of the above programs is needed.' + fi } function Check_System_Requirements_And_Make_Report() { __static__Declare_System_Requirements - local program command_found_string required_version_label + local program gnu_report=() declare -A system_information __static__Analyze_System_Properties printf "\n \e[93mSystem requirements overview:${default}\n\n" - for program in "${!HYBRID_system_requirements[@]}"; do - __static__Print_Requirement_Report_Line "${program}" + for program in "${!HYBRID_versions_requirements[@]}"; do + __static__Print_Requirement_Version_Report_Line "${program}" + done + printf '\n' + for program in "${HYBRID_gnu_programs_required[@]}"; do + gnu_report+=( "$(__static__Get_Gnu_Requirement_Report_For_Single_Program "${program}")" ) done + # Because of coloured output, we cannot use a tool like 'column' here and + # we manually determine how many columns to use. + local -r single_field_length=15 + local -r num_cols=$(( $(tput cols) * 4 / 5 / single_field_length )) + local index printf_descriptor + printf_descriptor="%${single_field_length}s" # At least one column + for ((index=1; index https://stackoverflow.com/a/61767587/14967071 + if [[ $("$1" --version | grep -c 'GNU') -gt 0 ]]; then + return 0 + else + return 1 + fi +} + function __static__Try_Find_Version() { Ensure_That_Given_Variables_Are_Set system_information @@ -143,7 +194,7 @@ function __static__Check_Version_Suffices() # Here we assume that the programs were found and their version, too local program version_required version_found index program=$1 - version_required="${HYBRID_system_requirements[${program}]}" + version_required="${HYBRID_versions_requirements[${program}]}" version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") if [[ ! ${version_found} =~ ^[0-9](.[0-9]+)*$ ]] ||\ [[ ! ${version_required} =~ ^[0-9](.[0-9]+)*$ ]]; then @@ -181,7 +232,7 @@ function __static__Check_Version_Suffices() done } -function __static__Print_Requirement_Report_Line() +function __static__Print_Requirement_Version_Report_Line() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information local -r emph_color='\e[96m'\ @@ -202,7 +253,7 @@ function __static__Print_Requirement_Report_Line() line+="${green} " fi line+=$(printf "found ${text_color}Required version: ${emph_color}%5s${default}"\ - "${HYBRID_system_requirements[${program}]}") + "${HYBRID_versions_requirements[${program}]}") if [[ ${found} != '---' ]]; then line+=" ${text_color}System version:${default} " if [[ ${version_found} = '---' ]]; then @@ -219,3 +270,22 @@ function __static__Print_Requirement_Report_Line() fi printf "${line}\n" } + +function __static__Get_Gnu_Requirement_Report_For_Single_Program() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information + local -r emph_color='\e[96m'\ + red='\e[91m'\ + green='\e[92m'\ + yellow='\e[93m'\ + text_color='\e[38;5;38m'\ + default='\e[0m' + local line program="GNU-$1" + printf -v line " ${emph_color}%6s${text_color}: ${default}" "${program}" + if [[ $(cut -d'|' -f2 <<< "${system_information[${program}]}") = '---' ]]; then + line+="${red}✘" + else + line+="${green}✔︎" + fi + printf "${line}${default}" +} diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 4224ee7..ef6f445 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -12,7 +12,7 @@ # instead of the system ones (remember that functions have higher priority than # external commands). As each fake command print the variable ${...version} as version # number, it is then possible to mimic a system complying with the requirements and -# another one violating them. +# another one violating them. The same is valid for the ${gnu} variable. function __static__Fake_Command_Version() { @@ -28,12 +28,12 @@ function __static__Inhibit_Commands_Version() function awk() { __static__Fake_Command_Version\ - '--version' "GNU Awk ${awk_version}, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)" "$@" + '--version' "${gnu} Awk ${awk_version}, API: 3.0 (${gnu} MPFR 4.1.0, ${gnu} MP 6.2.1)" "$@" } function sed() { __static__Fake_Command_Version\ - '--version' "sed (GNU sed) ${sed_version} Packaged by Debian" "$@" + '--version' "sed (${gnu} sed) ${sed_version} Packaged by Debian" "$@" } function tput() { @@ -50,7 +50,8 @@ function __static__Inhibit_Commands_Version() function Unit_Test__system-requirements() { __static__Inhibit_Commands_Version - local {awk,sed,tput,yq}_version + local gnu {awk,sed,tput,yq}_version + gnu='GNU' awk_version=4.1 sed_version=4.2.1 tput_version=5.9 @@ -65,6 +66,7 @@ function Unit_Test__system-requirements() Print_Error "Check system requirements making report of good system failed." return 1 fi + gnu='BSD' awk_version=4.0.9 sed_version=4.2.0 tput_version=5.9 From 99067c29fc0071bd9593b6bfeb1716d1556fe2a6 Mon Sep 17 00:00:00 2001 From: gabriele-inghirami Date: Fri, 16 Jun 2023 20:40:12 +0200 Subject: [PATCH 102/549] Add yq installation in GH actions config file Additionally, reduce the verbosity of wget --- .github/workflows/github-actions-on-github-servers.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index 1bb85f8..e277526 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -59,6 +59,9 @@ jobs: fi # we print the bash version to check that we used the desired one printf "\n$(${BASH_EXE} --version)\n" + # now we install yq + wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 + chmod +x /usr/local/bin/yq # now we enter into the tests directory and execute the tests with maximum detail (-r 3) cd $main_dir/tests ${BASH_EXE} tests_runner unit -r 3 From f843f6da6f28b42ac8fd550d9b07b2d85e55f5ee Mon Sep 17 00:00:00 2001 From: gabriele-inghirami Date: Fri, 16 Jun 2023 23:09:19 +0200 Subject: [PATCH 103/549] Print details of the tests if they fail (in GH actions) --- .../workflows/github-actions-on-github-servers.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index e277526..91ecb97 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -48,7 +48,7 @@ jobs: export main_dir=${PWD} # if we are not using the default bash version, we download and compile the desired one if [ ${BASH_TESTED_VERSION} != "default" ]; then - wget https://ftp.gnu.org/gnu/bash/${BASH_TESTED_VERSION}.tar.gz + wget -nv https://ftp.gnu.org/gnu/bash/${BASH_TESTED_VERSION}.tar.gz tar xf ${BASH_TESTED_VERSION}.tar.gz cd ${BASH_TESTED_VERSION} ./configure @@ -60,9 +60,12 @@ jobs: # we print the bash version to check that we used the desired one printf "\n$(${BASH_EXE} --version)\n" # now we install yq - wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 + wget -nv -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 chmod +x /usr/local/bin/yq # now we enter into the tests directory and execute the tests with maximum detail (-r 3) cd $main_dir/tests - ${BASH_EXE} tests_runner unit -r 3 - ${BASH_EXE} tests_runner functional -r 3 + ${BASH_EXE} tests_runner unit -r 3 -k ||\ + { printf "\nDetailed output of unit tests for debugging purposes:\n"; cat run_tests/*; exit 1; } + rm -rf run_tests + ${BASH_EXE} tests_runner functional -r 3 -k ||\ + { printf "\nDetailed output of functional tests for debugging purposes:\n"; cat run_tests/*; exit 1; } From 8fcbcabe147f2f80eba0bee0a9a39027a23399a2 Mon Sep 17 00:00:00 2001 From: gabriele-inghirami Date: Fri, 16 Jun 2023 23:13:47 +0200 Subject: [PATCH 104/549] Set TERM env variable in GH actions tput and perhaps other utilities which could be used in the tests extract the type of terminal from this variable --- .github/workflows/github-actions-on-github-servers.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index 91ecb97..f629f8f 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -62,6 +62,8 @@ jobs: # now we install yq wget -nv -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 chmod +x /usr/local/bin/yq + # we set the TERM environment variable (used by utilities exploited in the tests) + export TERM="xterm" # now we enter into the tests directory and execute the tests with maximum detail (-r 3) cd $main_dir/tests ${BASH_EXE} tests_runner unit -r 3 -k ||\ From cb80391adb5c24f1a98845e203cf68fb66e3f095 Mon Sep 17 00:00:00 2001 From: gabriele-inghirami Date: Mon, 19 Jun 2023 11:08:30 +0200 Subject: [PATCH 105/549] Print only log if tests fail on GH actions --- .github/workflows/github-actions-on-github-servers.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index f629f8f..7d85c30 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -67,7 +67,7 @@ jobs: # now we enter into the tests directory and execute the tests with maximum detail (-r 3) cd $main_dir/tests ${BASH_EXE} tests_runner unit -r 3 -k ||\ - { printf "\nDetailed output of unit tests for debugging purposes:\n"; cat run_tests/*; exit 1; } + { printf "\nDetailed output of unit tests for debugging purposes:\n"; cat run_tests/tests_runner.log; exit 1; } rm -rf run_tests ${BASH_EXE} tests_runner functional -r 3 -k ||\ - { printf "\nDetailed output of functional tests for debugging purposes:\n"; cat run_tests/*; exit 1; } + { printf "\nDetailed output of functional tests for debugging purposes:\n"; cat run_tests/tests_runner.log; exit 1; } From 8036c9e205799dbd1d4044d4446b668dcbc670c6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 19 Jun 2023 15:16:49 +0200 Subject: [PATCH 106/549] Extract version regex into constant global variable The version finding of yq has been fixed to work with the oldest versions accepted. --- bash/system_requirements.bash | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index dfb4742..8187af5 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -11,6 +11,7 @@ function __static__Declare_System_Requirements() { if ! declare -p HYBRID_versions_requirements &> /dev/null; then + readonly HYBRID_version_regex='[0-9](.[0-9]+)*' declare -rgA HYBRID_versions_requirements=( [bash]='4.4' [awk]='4.1' @@ -163,22 +164,25 @@ function __static__Try_Find_Version() found_version="${found_version// /.}" ;; awk ) - found_version=$(awk --version | head -n1 | grep -o "[0-9.]\+" | head -n1) + found_version=$(awk --version | head -n1 | grep -oE "${HYBRID_version_regex}" | head -n1) ;; sed ) - found_version=$(sed --version | head -n1 | grep -o "[0-9.]\+" | head -n1) + found_version=$(sed --version | head -n1 | grep -oE "${HYBRID_version_regex}" | head -n1) ;; tput ) - found_version=$(tput -V | grep -o "[0-9.]\+" | cut -d'.' -f1,2) + found_version=$(tput -V | grep -oE "${HYBRID_version_regex}" | cut -d'.' -f1,2) ;; yq ) - found_version=$(yq --version | grep -o "v[0-9.]\+" | cut -d'v' -f2) + # Old versions close to 4.0.0 do not have the 'v' prefix + found_version=$(yq --version |\ + grep -oE "version [v]?${HYBRID_version_regex}" |\ + grep -oE "${HYBRID_version_regex}") ;; *) return 1 ;; esac - if [[ ${found_version} =~ ^[0-9](.[0-9]+)*$ ]]; then + if [[ ${found_version} =~ ^${HYBRID_version_regex}$ ]]; then system_information["$1"]+="${found_version}|" else system_information["$1"]+='---|' @@ -196,8 +200,8 @@ function __static__Check_Version_Suffices() program=$1 version_required="${HYBRID_versions_requirements[${program}]}" version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") - if [[ ! ${version_found} =~ ^[0-9](.[0-9]+)*$ ]] ||\ - [[ ! ${version_required} =~ ^[0-9](.[0-9]+)*$ ]]; then + if [[ ! ${version_found} =~ ^${HYBRID_version_regex}$ ]] ||\ + [[ ! ${version_required} =~ ^${HYBRID_version_regex}$ ]]; then Print_Internal_And_Exit "Wrong syntax in version strings in ${FUNCNAME}." fi # Ensure versions are of the same length to make following algorithm work From 09890da09535e1b349ca4afb4cf2b4cf14f4e9f8 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 19 Jun 2023 16:10:08 +0200 Subject: [PATCH 107/549] Request more recent version of yq software Since version 4.18.1 the eval action has been make the default one in yq and with precedent versions such an action would have always been needed. This would not be a big deal, but till version 4.11.0 feeding a string into yq using process substitution <() was failing as yq was attempting to modify that input (not permitted), resulting into a Error: seek /dev/fd/63: illegal seek error. This has been fixed into version 4.11.1 and, given the extremely simple way to download yq, we decided to bump the yq minimum requirement to the more user friendly one. --- bash/system_requirements.bash | 14 +++++------ ...it_tests_software_input_functionality.bash | 24 +++++++++---------- tests/unit_tests_system_requirements.bash | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 8187af5..edea438 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -17,7 +17,7 @@ function __static__Declare_System_Requirements() [awk]='4.1' [sed]='4.2.1' [tput]='5.7' - [yq]='4' + [yq]='4.18.1' ) declare -rga HYBRID_gnu_programs_required=( awk sed sort wc ) fi @@ -26,7 +26,7 @@ function __static__Declare_System_Requirements() function Check_System_Requirements() { __static__Declare_System_Requirements - local program requirements_present min_version + local program requirements_present min_version version_found requirements_present=0 # NOTE: The following associative array will be used to store system information # and since bash does not support arrays entries in associative arrays, then @@ -47,14 +47,14 @@ function Check_System_Requirements() requirements_present=1 continue fi - if [[ $(cut -d'|' -f2 <<< "${system_information[${program}]}") = '---' ]]; then + version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") + if [[ ${version_found} = '---' ]]; then Print_Warning "Unable to find version of '${program}', skipping version check!"\ "Please ensure that current version is at least ${min_version}." continue fi if [[ $(cut -d'|' -f3 <<< "${system_information[${program}]}") = '---' ]]; then - Print_Error "'${program}' version ${system_information[${program}]} found,"\ - " but version ${min_version} is required." + Print_Error "'${program}' version ${version_found} found, but version ${min_version} is required." requirements_present=1 fi done @@ -173,7 +173,7 @@ function __static__Try_Find_Version() found_version=$(tput -V | grep -oE "${HYBRID_version_regex}" | cut -d'.' -f1,2) ;; yq ) - # Old versions close to 4.0.0 do not have the 'v' prefix + # Versions before v4.30.3 do not have the 'v' prefix found_version=$(yq --version |\ grep -oE "version [v]?${HYBRID_version_regex}" |\ grep -oE "${HYBRID_version_regex}") @@ -256,7 +256,7 @@ function __static__Print_Requirement_Version_Report_Line() else line+="${green} " fi - line+=$(printf "found ${text_color}Required version: ${emph_color}%5s${default}"\ + line+=$(printf "found ${text_color}Required version: ${emph_color}%6s${default}"\ "${HYBRID_versions_requirements[${program}]}") if [[ ${found} != '---' ]]; then line+=" ${text_color}System version:${default} " diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 8976ee5..8dac17d 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -43,24 +43,21 @@ function Unit_Test__replace-in-software-input-YAML() return 1 fi # Test case 4: - printf\ - ' - Array: - - 1 - - 2 - - 3 + printf 'Array: + - 1 + - 2 + - 3 - Map: - Key_1: Hi - Key_2: Bye - Foo: Bar +Map: + Key_1: Hi + Key_2: Bye +Foo: Bar ' > "${base_input_file}" keys_to_be_replaced=' Array: [5,6,7] Foo: BarBar ' - expected_result=' -Array: + expected_result='Array: - 5 - 6 - 7 @@ -69,6 +66,9 @@ Map: Key_2: Bye Foo: BarBar' __static__Replace_Keys_Into_YAML_File + # yq in v4.30.6 fixed the behavior of keeping leading empty lines + # so it is important here to have no leading empty lines, otherwise + # this test would succeed/fail depending on yq version available! if [[ "$(cat "${base_input_file}")" != "${expected_result}" ]]; then Print_Error "YAML replacement failed!"\ '---- OBTAINED: ----' "$(cat "${base_input_file}")"\ diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index ef6f445..57e7fff 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -55,7 +55,7 @@ function Unit_Test__system-requirements() awk_version=4.1 sed_version=4.2.1 tput_version=5.9 - yq_version=4 + yq_version=4.18.1 ( Check_System_Requirements ) if [[ $? -ne 0 ]]; then Print_Error "Check system requirements of good system failed." From a70f2a0e98405c8312f59f55e6e72419de72e23a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 19 Jun 2023 16:17:25 +0200 Subject: [PATCH 108/549] Let one of the two action use the oldest supported yq --- .../workflows/github-actions-on-github-servers.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index 7d85c30..cfc3493 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -48,20 +48,23 @@ jobs: export main_dir=${PWD} # if we are not using the default bash version, we download and compile the desired one if [ ${BASH_TESTED_VERSION} != "default" ]; then + printf "Download and compile bash-${BASH_TESTED_VERSION}...\n" wget -nv https://ftp.gnu.org/gnu/bash/${BASH_TESTED_VERSION}.tar.gz tar xf ${BASH_TESTED_VERSION}.tar.gz + output_file=configure_make.log cd ${BASH_TESTED_VERSION} - ./configure - make -j$(nproc) + ./configure > "${output_file}" 2>&1 || { cat "${output_file}"; exit 1; } + make -j$(nproc) > "${output_file}" 2>&1 || { cat "${output_file}"; exit 1; } BASH_EXE=$(pwd)/bash + # now we install yq in the minimum version supported by the handler (otherwise a more recent version is available in GitHub actions) + printf "Download yq-v4.18.1...\n" + wget -nv -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.18.1/yq_linux_amd64 + chmod +x /usr/local/bin/yq else BASH_EXE=$(which bash) fi # we print the bash version to check that we used the desired one printf "\n$(${BASH_EXE} --version)\n" - # now we install yq - wget -nv -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 - chmod +x /usr/local/bin/yq # we set the TERM environment variable (used by utilities exploited in the tests) export TERM="xterm" # now we enter into the tests directory and execute the tests with maximum detail (-r 3) From fa2427fd9196ab63b15f69a5d1b7ced7a8df757d Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 19 Jun 2023 17:33:05 +0200 Subject: [PATCH 109/549] Rename variable about top-level path --- Hybrid-handler | 4 ++-- bash/global_variables.bash | 4 ++-- bash/source_codebase_files.bash | 4 ++-- tests/unit_tests.bash | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index e0dfa1f..9994ec7 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -49,7 +49,7 @@ function Setup_Initial_And_Final_Output_Space() function Define_Repository_Global_Path() { - readonly HYBRID_repository_global_path=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")") + readonly HYBRID_top_level_path=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")") } function Store_Command_Line_Options_Into_Global_Variable() @@ -59,7 +59,7 @@ function Store_Command_Line_Options_Into_Global_Variable() function Source_Codebase_Files() { - source "${HYBRID_repository_global_path}/bash/source_codebase_files.bash" || exit 1 + source "${HYBRID_top_level_path}/bash/source_codebase_files.bash" || exit 1 } function Act_And_Exit_If_User_Ran_Auxiliary_Modes() diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 8b21706..182873a 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -15,7 +15,7 @@ # this mechanism, like dashes or spaces! function Define_Further_Global_Variables() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty HYBRID_repository_global_path + Ensure_That_Given_Variables_Are_Set_And_Not_Empty HYBRID_top_level_path # Constant information readonly HYBRID_valid_software_configuration_sections=( 'IC' @@ -26,7 +26,7 @@ function Define_Further_Global_Variables() readonly HYBRID_valid_auxiliary_configuration_sections=( 'Hybrid_handler' ) - readonly HYBRID_default_configurations_folder="${HYBRID_repository_global_path}/configs" + readonly HYBRID_default_configurations_folder="${HYBRID_top_level_path}/configs" # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=() diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 388c6bf..811c0bd 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -11,7 +11,7 @@ function __static__Source_Codebase_Files() { local list_of_files file_to_be_sourced # Source error codes and fail with hard-coded generic error - source "${HYBRID_repository_global_path}/bash/error_codes.bash" || exit 1 + source "${HYBRID_top_level_path}/bash/error_codes.bash" || exit 1 list_of_files=( 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' @@ -25,7 +25,7 @@ function __static__Source_Codebase_Files() 'version.bash' ) for file_to_be_sourced in "${list_of_files[@]}"; do - source "${HYBRID_repository_global_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + source "${HYBRID_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done } diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 1b0db52..e5e672f 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -32,7 +32,7 @@ function Make_Test_Preliminary_Operations() { # The following global variable is needed whe defining the software global variables # and since it is likely that most unit tests need it, let's always define it - readonly HYBRID_repository_global_path="${HYBRIDT_repository_top_level_path}" + readonly HYBRID_top_level_path="${HYBRIDT_repository_top_level_path}" # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 From 7ab05e8f72371a618bb84f305ae5b40e9f222eb2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 21 Jun 2023 14:56:10 +0200 Subject: [PATCH 110/549] Implement utility function to strip ANSI codes from string --- bash/utility_functions.bash | 6 ++++++ tests/unit_tests_utility_functions.bash | 28 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 6ab6d91..7b216be 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -151,6 +151,12 @@ function Remove_Comments_In_File() fi } +function Strip_ANSI_Color_Codes_From_String() +{ + # Adjusted from https://stackoverflow.com/a/18000433/14967071 + sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]//g" <<< "$1" +} + function Call_Function_If_Existing_Or_Exit() { local name_of_the_function=$1 diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 01e06c6..94d0c61 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -264,3 +264,31 @@ function Unit_Test__utility-check-shell-variables-set-not-empty() return 1 fi } + +function __static__Test_ANSI_Code_Removal() +{ + Ensure_That_Given_Variables_Are_Set output + Ensure_That_Given_Variables_Are_Set_And_Not_Empty input expected_output + output=$(Strip_ANSI_Color_Codes_From_String "${input}") + if [[ "${output}" != "${expected_output}" ]]; then + Print_Error "Removing format code from '${expected_output}' failed." + return 1 + fi +} + +function Unit_Test__utility-strip-ANSI-codes() +{ + local input output expected_output + input=$(printf '\e[1mBold\e[22m text\e[0m') + expected_output='Bold text' + __static__Test_ANSI_Code_Removal || return 1 + input=$(printf '\e[1;96mBold color text\e[0m') + expected_output='Bold color text' + __static__Test_ANSI_Code_Removal || return 1 + input=$(printf '\e[38;5;202mComplex color text\e[0m') + expected_output='Complex color text' + __static__Test_ANSI_Code_Removal || return 1 + input=$(printf '\e[1;38;5;202mComplex bold-color text\e[0m') + expected_output='Complex bold-color text' + __static__Test_ANSI_Code_Removal || return 1 +} From 328f9d39927674c0643e6bcbc156a801e5ab3bba Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 21 Jun 2023 15:53:16 +0200 Subject: [PATCH 111/549] Add requirement check about environment variables --- bash/system_requirements.bash | 69 +++++++++++++++++------ tests/unit_tests_system_requirements.bash | 4 +- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index edea438..961adc6 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -20,13 +20,14 @@ function __static__Declare_System_Requirements() [yq]='4.18.1' ) declare -rga HYBRID_gnu_programs_required=( awk sed sort wc ) + declare -rga HYBRID_env_variables_required=( TERM ) fi } function Check_System_Requirements() { __static__Declare_System_Requirements - local program requirements_present min_version version_found + local program requirements_present min_version version_found name requirements_present=0 # NOTE: The following associative array will be used to store system information # and since bash does not support arrays entries in associative arrays, then @@ -34,10 +35,12 @@ function Check_System_Requirements() # contains whether the command was found, its version and whether the version # meets the requirement or not. A '|' is used to separate the fields in the # string and '---' is used to indicate a negative outcome in the field. - # It has been decided to use the same array to store the availability of GNU - # tools. For these, the key is prefixed by 'GNU-' and the content has a first - # "field" that is 'found' or '---' and a second one that is either 'OK' or '---' - # to indicate whether the GNU version of the command is in use or not. + # The same array is used to store the availability of GNU tools. For these, + # the key is prefixed by 'GNU-' and the content has a first "field" that is + # 'found' or '---' and a second one that is either 'OK' or '---' to indicate + # whether the GNU version of the command is in use or not. + # Finally, the same array is used for environment variables and, in this case, + # the content is either 'OK' or '---'. declare -A system_information __static__Analyze_System_Properties for program in "${!HYBRID_versions_requirements[@]}"; do @@ -50,7 +53,7 @@ function Check_System_Requirements() version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") if [[ ${version_found} = '---' ]]; then Print_Warning "Unable to find version of '${program}', skipping version check!"\ - "Please ensure that current version is at least ${min_version}." + "Please ensure that current version is at least ${min_version}." continue fi if [[ $(cut -d'|' -f3 <<< "${system_information[${program}]}") = '---' ]]; then @@ -71,38 +74,63 @@ function Check_System_Requirements() if [[ ${requirements_present} -ne 0 ]]; then Print_Fatal_And_Exit 'The GNU version of the above programs is needed.' fi + for name in "${HYBRID_env_variables_required[@]}"; do + if [[ ${system_information[${name}]} = '---' ]]; then + Print_Error "'${name}' environment variable either unset or empty."\ + "Please, ensure that '${name}' is properly set." + requirements_present=1 + fi + done + if [[ ${requirements_present} -ne 0 ]]; then + Print_Fatal_And_Exit 'Some needed environment variables are not correctly set.' + fi } function Check_System_Requirements_And_Make_Report() { __static__Declare_System_Requirements - local program gnu_report=() + local program name gnu_env_report=() declare -A system_information __static__Analyze_System_Properties printf "\n \e[93mSystem requirements overview:${default}\n\n" for program in "${!HYBRID_versions_requirements[@]}"; do __static__Print_Requirement_Version_Report_Line "${program}" - done + done | sort -b -k3 # the third column is that containing the program name printf '\n' for program in "${HYBRID_gnu_programs_required[@]}"; do - gnu_report+=( "$(__static__Get_Gnu_Requirement_Report_For_Single_Program "${program}")" ) + gnu_env_report+=( + "$(__static__Get_Single_Tick_Cross_Requirement_Report\ + "GNU ${program}"\ + "$(cut -d'|' -f2 <<< "${system_information["GNU-${program}"]}")" + )" + ) + done + for name in "${HYBRID_env_variables_required[@]}"; do + gnu_env_report+=( + "$(__static__Get_Single_Tick_Cross_Requirement_Report\ + "ENV ${name}"\ + "${system_information[${name}]}" + )" + ) done # Because of coloured output, we cannot use a tool like 'column' here and # we manually determine how many columns to use. local -r single_field_length=15 - local -r num_cols=$(( $(tput cols) * 4 / 5 / single_field_length )) + # NOTE: tput needs the TERM environment variable to be set. Here, since that is a + # requirement, we use 'xterm' in case that environment variable was not set. + local -r num_cols=$(( $(tput -T ${TERM:-xterm} cols) * 4 / 5 / single_field_length )) local index printf_descriptor printf_descriptor="%${single_field_length}s" # At least one column for ((index=1; index /dev/null ) + if [[ $? -eq 0 ]]; then + system_information["${name}"]='OK' + else + system_information["${name}"]='---' + fi + done } function __static__Try_Find_Requirement() @@ -275,18 +311,17 @@ function __static__Print_Requirement_Version_Report_Line() printf "${line}\n" } -function __static__Get_Gnu_Requirement_Report_For_Single_Program() +function __static__Get_Single_Tick_Cross_Requirement_Report() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information local -r emph_color='\e[96m'\ red='\e[91m'\ green='\e[92m'\ - yellow='\e[93m'\ text_color='\e[38;5;38m'\ default='\e[0m' - local line program="GNU-$1" - printf -v line " ${emph_color}%6s${text_color}: ${default}" "${program}" - if [[ $(cut -d'|' -f2 <<< "${system_information[${program}]}") = '---' ]]; then + local line name="$1" status=$2 + printf -v line " ${emph_color}%6s${text_color}: ${default}" "${name}" + if [[ ${status} = '---' ]]; then line+="${red}✘" else line+="${green}✔︎" diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 57e7fff..01f0f6b 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -69,14 +69,14 @@ function Unit_Test__system-requirements() gnu='BSD' awk_version=4.0.9 sed_version=4.2.0 - tput_version=5.9 + tput_version='' yq_version=3.9.98 ( Check_System_Requirements &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error "Check system requirements of bad system succeeded." return 1 fi - ( Check_System_Requirements_And_Make_Report ) + ( unset -v 'TERM'; Check_System_Requirements_And_Make_Report ) if [[ $? -ne 0 ]]; then Print_Error "Check system requirements making report of bad system failed." return 1 From f562aaa656565e8cfa50cd1b5807171de01a9dd7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 21 Jun 2023 16:02:56 +0200 Subject: [PATCH 112/549] Check GNU tools first and use sort -V to compare versions --- bash/system_requirements.bash | 60 ++++++++++------------------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 961adc6..102b4f6 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -43,6 +43,16 @@ function Check_System_Requirements() # the content is either 'OK' or '---'. declare -A system_information __static__Analyze_System_Properties + for program in "${HYBRID_gnu_programs_required[@]}"; do + if [[ $(cut -d'|' -f2 <<< "${system_information[${program}]}") = '---' ]]; then + Print_Error "'${program#GNU-}' either not found or non-GNU version in use."\ + "Please, ensure that '${program}' is installed and in use." + requirements_present=1 + fi + done + if [[ ${requirements_present} -ne 0 ]]; then + Print_Fatal_And_Exit 'The GNU version of the above programs is needed.' + fi for program in "${!HYBRID_versions_requirements[@]}"; do min_version=${HYBRID_versions_requirements["${program}"]} if [[ $(cut -d'|' -f1 <<< "${system_information[${program}]}") = '---' ]]; then @@ -64,16 +74,6 @@ function Check_System_Requirements() if [[ ${requirements_present} -ne 0 ]]; then Print_Fatal_And_Exit 'Please install (maybe locally) the required versions of the above programs.' fi - for program in "${HYBRID_gnu_programs_required[@]}"; do - if [[ $(cut -d'|' -f2 <<< "${system_information[${program}]}") = '---' ]]; then - Print_Error "'${program#GNU-}' either not found or non-GNU version in use."\ - "Please, ensure that '${program}' is installed and in use." - requirements_present=1 - fi - done - if [[ ${requirements_present} -ne 0 ]]; then - Print_Fatal_And_Exit 'The GNU version of the above programs is needed.' - fi for name in "${HYBRID_env_variables_required[@]}"; do if [[ ${system_information[${name}]} = '---' ]]; then Print_Error "'${name}' environment variable either unset or empty."\ @@ -226,50 +226,24 @@ function __static__Try_Find_Version() fi } -# NOTE: This function would be almost a one-liner using 'sort -V', but at the moment we do not -# impose to have GNU coreutils installed, which we should probably do next... function __static__Check_Version_Suffices() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information # Here we assume that the programs were found and their version, too - local program version_required version_found index + local program version_required version_found newer_version program=$1 version_required="${HYBRID_versions_requirements[${program}]}" version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") - if [[ ! ${version_found} =~ ^${HYBRID_version_regex}$ ]] ||\ - [[ ! ${version_required} =~ ^${HYBRID_version_regex}$ ]]; then - Print_Internal_And_Exit "Wrong syntax in version strings in ${FUNCNAME}." - fi - # Ensure versions are of the same length to make following algorithm work - if [[ ${#version_found} -ne ${#version_required} ]]; then - if [[ ${#version_found} -lt ${#version_required} ]]; then - declare -n shorter_array=version_found - declare -n longer_array=version_required - else - declare -n shorter_array=version_required - declare -n longer_array=version_found - fi - while [[ ${#version_found[@]} -ne ${#version_required[@]} ]]; do - shorter_array+='.0' # Add zeroes to shorter string - done - fi # If versions are equal, we're done if [[ "${version_required}" = "${version_found}" ]]; then return 0 fi - # Split version strings into array of numbers replacing '.' by ' ' and let word splitting do the split - version_required=( ${version_required//./ } ) - version_found=( ${version_found//./ } ) - # Now version arrays have same number of entries, compare them - for index in ${!version_required[@]}; do - if [[ "${version_required[index]}" -eq "${version_found[index]}" ]]; then - continue - elif [[ "${version_required[index]}" -lt "${version_found[index]}" ]]; then - return 0 - else - return 1 - fi - done + newer_version=$(printf '%s\n%s' ${version_required} ${version_found} | sort -V | tail -n 1) + if [[ ${newer_version} = ${version_required} ]]; then + return 1 + else + return 0 + fi } function __static__Print_Requirement_Version_Report_Line() From 6e000388ef4187aa3a7c9547917527a027d5357b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 21 Jun 2023 16:12:21 +0200 Subject: [PATCH 113/549] Redirect all logger output in tests to log file As the logger uses fd 3 for its standard output, not all Hybrid-handler output was redirected to the log file in tests, because there we were using &>> redirecting fd 1 and 2 only. --- tests/unit_tests.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 105760d..1b0db52 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -36,17 +36,17 @@ function Make_Test_Preliminary_Operations() # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 - } &>> "${HYBRIDT_log_file}" + } &>> "${HYBRIDT_log_file}" 3>&1 # The fd 3 is used by the logger. } function Run_Test() { - Unit_Test__$1 &>> "${HYBRIDT_log_file}" + Unit_Test__$1 &>> "${HYBRIDT_log_file}" 3>&1 # The fd 3 is used by the logger. } function Clean_Tests_Environment_For_Following_Test() { - Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 3>&1 # The fd 3 is used by the logger. } #======================================================================================================================= From 64010cf8d09bf615b5a860ab6272179682c02f25 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Jun 2023 15:09:02 +0200 Subject: [PATCH 114/549] Implement parsing of command line options --- bash/command_line_parsers/main_parser.bash | 31 +++++++++++++- tests/unit_tests_command_line_parser.bash | 48 +++++++++++++++++++++- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 413bf08..33ef0ac 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -42,6 +42,33 @@ function Parse_Execution_Mode() function Parse_Command_Line_Options() { - Print_Not_Implemented_Function_Error - return 1 + if [[ ${HYBRID_execution_mode} != 'do' ]]; then + Print_Internal_And_Exit 'Command line options are allowed only in "do" mode for now.' + fi + set -- "${HYBRID_command_line_options_to_parse[@]}" + while [[ $# -gt 0 ]]; do + case "$1" in + -o | --output-directory ) + if [[ ${2:-} =~ ^(-|$) ]]; then + Print_Option_Specification_Error_And_Exit "$1" + else + readonly HYBRID_output_directory=$2 + fi + shift 2 + ;; + -c | --configuration-file ) + if [[ ${2:-} =~ ^(-|$) ]]; then + Print_Option_Specification_Error_And_Exit "$1" + else + readonly HYBRID_configuration_file=$2 + fi + shift 2 + ;; + * ) + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + "Invalid option \"$1\" specified in '${HYBRID_execution_mode}' execution mode!"\ + 'Use the "--help" option to get further information.' + ;; + esac + done } diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index 18bf21a..781bca0 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -57,14 +57,58 @@ function Unit_Test__parse-execution-mode() HYBRID_command_line_options_to_parse=( 'do' '-o' '/path/to/folder' '--help' ) __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'do-help' 0 || return 1 HYBRID_command_line_options_to_parse=( 'invalid-mode' ) - ( Parse_Execution_Mode ) + ( Parse_Execution_Mode &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Parsing of invalid execution mode succeeded.' return 1 fi } +#------------------------------------------------------------------------------- + +function Make_Test_Preliminary_Operations__parse-command-line-options() +{ + Make_Test_Preliminary_Operations__parse-execution-mode +} + +function __static__Test_CLO_Parsing_Missing_Value() +{ + ( Parse_Command_Line_Options &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error "Parsing of CLO '${HYBRID_command_line_options_to_parse[0]}' with missing value succeeded." + return 1 + fi +} + +function __static__Test_Single_CLO_Parsing_In_Subshell() +( + Parse_Command_Line_Options + if [[ $? -ne 0 ]] || [[ ${!1} != "$2" ]]; then + Print_Error 'Parsing of '${HYBRID_command_line_options_to_parse[0]}' with valid value failed.' + return 1 + fi +) + function Unit_Test__parse-command-line-options() { - return 0 + ( Parse_Command_Line_Options &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Parsing of CLO in wrong execution mode succeeded.' + return 1 + fi + HYBRID_execution_mode='do' + HYBRID_command_line_options_to_parse=( --output-directory ) + __static__Test_CLO_Parsing_Missing_Value || return 1 + HYBRID_command_line_options_to_parse=( --configuration-file ) + __static__Test_CLO_Parsing_Missing_Value || return 1 + HYBRID_command_line_options_to_parse=() + ( Parse_Command_Line_Options ) + if [[ $? -ne 0 ]]; then + Print_Error 'Parsing of CLO with no CLO failed.' + return 1 + fi + HYBRID_command_line_options_to_parse=( -o /path/to/dir ) + __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_output_directory '/path/to/dir' || return 1 + HYBRID_command_line_options_to_parse=( -c /path/to/file ) + __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_configuration_file '/path/to/file' || return 1 } From 190445bf5ce4a1c3c37b9187215b19069cd23e5b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 21 Jun 2023 16:35:45 +0200 Subject: [PATCH 115/549] Adjust some spacing and properly do system requirement check --- bash/command_line_parsers/helper.bash | 6 +++--- bash/system_requirements.bash | 2 +- tests/unit_tests_help_for_user.bash | 1 + tests/unit_tests_system_requirements.bash | 2 ++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index b2a41c0..2718a39 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -40,7 +40,7 @@ function __static__Print_Main_Help_Message() ) __static__Print_Handler_Header_And_Usage_Synopsis __static__Print_Modes_Description - Check_System_Requirements_And_Make_Report 2> /dev/null || true + Check_System_Requirements_And_Make_Report } function __static__Print_Do_Help_Message() @@ -78,7 +78,7 @@ function __static__Print_Modes_Description() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty section_headers "${!section_headers[@]}" local section mode - printf '\e[38;5;38m %s\e[0m\n\n'\ + printf '\e[38;5;38m %s\e[0m\n'\ 'Here in the following you find an overview of the existing execution modes.' for section in "${!section_headers[@]}"; do printf "\n \e[93m${section_headers[${section}]}\e[0m\n" @@ -89,7 +89,7 @@ function __static__Print_Modes_Description() "${list_of_modes[${mode}]}" done | sort --ignore-leading-blanks done - printf '\n\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\n\n'\ + printf '\n\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n\n'\ 'Use' '--help' 'after each non auxiliary mode to get further information about it.' } diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 102b4f6..6ef58bf 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -92,7 +92,7 @@ function Check_System_Requirements_And_Make_Report() local program name gnu_env_report=() declare -A system_information __static__Analyze_System_Properties - printf "\n \e[93mSystem requirements overview:${default}\n\n" + printf "\e[1m System requirements overview:\e[0m\n\n" for program in "${!HYBRID_versions_requirements[@]}"; do __static__Print_Requirement_Version_Report_Line "${program}" done | sort -b -k3 # the third column is that containing the program name diff --git a/tests/unit_tests_help_for_user.bash b/tests/unit_tests_help_for_user.bash index 4833d3b..c12b4a9 100644 --- a/tests/unit_tests_help_for_user.bash +++ b/tests/unit_tests_help_for_user.bash @@ -27,6 +27,7 @@ function Unit_Test__give-requested-help() { HYBRID_execution_mode='help' __static__Run_Helper_Expecting_Success || return 1 + printf '\n' HYBRID_execution_mode='do-help' __static__Run_Helper_Expecting_Success || return 1 } diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 01f0f6b..7b4762c 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -71,11 +71,13 @@ function Unit_Test__system-requirements() sed_version=4.2.0 tput_version='' yq_version=3.9.98 + printf '\n' ( Check_System_Requirements &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error "Check system requirements of bad system succeeded." return 1 fi + printf '\n' ( unset -v 'TERM'; Check_System_Requirements_And_Make_Report ) if [[ $? -ne 0 ]]; then Print_Error "Check system requirements making report of bad system failed." From 1f0235f00ce7b83304e40d40c1af5bf508c20caf Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 21 Jun 2023 15:00:25 +0200 Subject: [PATCH 116/549] Implement functionality to print version of the codebase --- Hybrid-handler | 2 ++ bash/version.bash | 60 ++++++++++++++++++++++++++++++++++- tests/unit_tests_version.bash | 22 ++++++++++++- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index 9994ec7..18c9349 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -19,6 +19,8 @@ # # #----------------------------------------------------------------------------# +readonly HYBRID_codebase_version='SMASH-vHLLE-hybrid-1.0' + function Main() { Enable_Desired_Shell_Behavior diff --git a/bash/version.bash b/bash/version.bash index b14be93..49e8e95 100644 --- a/bash/version.bash +++ b/bash/version.bash @@ -9,5 +9,63 @@ function Print_Software_Version() { - Print_Not_Implemented_Function_Error + Ensure_That_Given_Variables_Are_Set HYBRID_codebase_version + # First handle cases where git is not available, or codebase downloaded as archive and not cloned + # NOTE: Git introduced -C option in version 1.8.5 + if ! hash git &> /dev/null ||\ + ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null ||\ + __static__Is_Git_Version_Older_Than '1.8.3'; then + __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" + return 0 + fi + local git_tag_short git_tag_long tag_date + if ! git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags 2> /dev/null); then + Print_Warning "It was not possible to obtain the version in use!"\ + "This probably (but not necessarily) means that you are"\ + "behind any release in the Hybrid-handler history.\n" + __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" + return 0 + fi + if ! git_tag_short=$(git -C "${HYBRID_top_level_path}" describe --tags --abbr=0 2> /dev/null); then + Print_Internal_And_Exit "Unexpected error in \"${FUNCNAME}\" trying to obtain the closest git tag." + fi + tag_date=$(date -d "$(git -C "${HYBRID_top_level_path}" log -1 --format=%ai "${git_tag_short}")" +'%d %B %Y') + if [[ "${git_tag_short}" != "${git_tag_long}" ]]; then + if __static__Is_Git_Version_Older_Than '2.13'; then + git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags --dirty 2>/dev/null) + else + git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags --dirty --broken 2>/dev/null) + fi + local last_stable_release_string=$(printf '\e[38;5;202m%s (%s)' "${git_tag_short}" "${tag_date}") + Print_Warning "You are not using an official release of the Hybrid-handler."\ + "Unless you have a reason not to do so, it would be better"\ + "to checkout a stable release. The last stable release behind"\ + "the commit you are using is: ${last_stable_release_string}\n"\ + "The repository state is $(printf '\e[38;5;202m%s\e[93m' "${git_tag_long}")"\ + "(see git-describe documentation for more information)." + else + __static__Print_Pretty_Version_Line "${git_tag_short}" "${tag_date}" + fi +} + +function __static__Print_Pretty_Version_Line() +{ + local version_name=$1 release_date=${2-} + printf '\e[96mThis is \e[38;5;85m%s\e[96m' "${version_name}" + if [[ ${release_date} != '' ]]; then + printf ' released on \e[93m%s' "${release_date}" + fi + printf '\e[0m\n' +} + +function __static__Is_Git_Version_Older_Than() +{ + local required_version older_version + required_version="git version $1" + older_version=$(printf '%s\n%s\n' "$(git --version)" "${required_version}" | sort -V | head -n 1) + if [[ "${older_version}" == "${required_version}" ]]; then + return 1 + else + return 0 + fi } diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash index b44e8cf..50d86eb 100644 --- a/tests/unit_tests_version.bash +++ b/tests/unit_tests_version.bash @@ -7,7 +7,27 @@ # #=================================================== +function Make_Test_Preliminary_Operations__version() +{ + source "${HYBRIDT_repository_top_level_path}"/bash/version.bash || exit "${HYBRID_fatal_builtin}" +} + function Unit_Test__version() { - return 0 + HYBRID_codebase_version='SMASH-vHLLE-hybrid-1.0' + local std_output expected_output + # Unsetting PATH in the subshell so that 'git' will not be found + std_output=$(PATH=''; Print_Software_Version) + if [[ $? -ne 0 ]] ||\ + [[ $(Strip_ANSI_Color_Codes_From_String "${std_output}") != "This is ${HYBRID_codebase_version}" ]] ; then + Print_Error "Version printing without git available failed." + return 1 + fi + if hash git &> /dev/null; then + std_output=$(Print_Software_Version 3>&1) # We want to capture here a logger message that goes to fd 3 + if [[ $? -ne 0 || $(grep -c "${HYBRID_codebase_version}" <<< "${std_output}") -eq 0 ]] ; then + Print_Error "Version printing with git failed." + return 1 + fi + fi } From 26692910c3ee3f7beedf0c231cbab71cb9993fcf Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 22 Jun 2023 13:44:20 +0200 Subject: [PATCH 117/549] Add suffix to hard-coded version as on an unreleased commit --- Hybrid-handler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hybrid-handler b/Hybrid-handler index 18c9349..99c1b91 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -19,7 +19,7 @@ # # #----------------------------------------------------------------------------# -readonly HYBRID_codebase_version='SMASH-vHLLE-hybrid-1.0' +readonly HYBRID_codebase_version='SMASH-vHLLE-hybrid-1.0-unreleased' function Main() { From 0ebdf4496eb55c1f3160e76fb67d32a450ce7a98 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 14 Jun 2023 17:34:42 +0200 Subject: [PATCH 118/549] Implement first step of Prepare_Software_Input_File_IC --- bash/IC_functionality.bash | 20 ++++++++++++-- tests/unit_tests_IC_functionality.bash | 38 ++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 tests/unit_tests_IC_functionality.bash diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 49d1ef6..ce9319d 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -9,12 +9,28 @@ function Prepare_Software_Input_File_IC() { - Print_Not_Implemented_Function_Error + # check if output directory exists and create it if not + local IC_output_directory="${HYBRID_output_directory}/IC" + if [[ ! -d "${IC_output_directory}" ]]; then + mkdir "${IC_output_directory}" || exit ${HYBRID_fatal_builtin} + fi + + echo "${HYBRID_default_configurations_folder}" + # check if config already exists in the output directory, exit if yes + IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") + IC_input_file_path="${IC_output_directory}/${IC_config_name}" + if [[ ! -f "${IC_input_file_path}" ]]; then + cp "${HYBRID_software_base_config_file[IC]}" "${IC_output_directory}" || exit ${HYBRID_fatal_builtin} + # rewrite stuff + else + exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + "File \"${IC_input_file_path}\" is already there." + fi } function Ensure_All_Needed_Input_Exists_IC() { - Print_Not_Implemented_Function_Error + Print_Not_Implemented_Function_Error } function Run_Software_IC() diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash new file mode 100644 index 0000000..f03c9fd --- /dev/null +++ b/tests/unit_tests_IC_functionality.bash @@ -0,0 +1,38 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Test_Preliminary_Operations__IC-create-input-file() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'IC_functionality.bash' + 'global_variables.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + HYBRID_repository_global_path="${HYBRIDT_repository_top_level_path}" + Define_Further_Global_Variables +} + +function Unit_Test__IC-create-input-file() +{ + HYBRID_output_directory="./test_dir_IC" + if [[ -d "${HYBRID_output_directory}" ]]; then + rm -r "${HYBRID_output_directory}" + fi + mkdir "${HYBRID_output_directory}" + Prepare_Software_Input_File_IC + IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") + IC_input_file_path="${HYBRID_output_directory}/IC/${IC_config_name}" + if [[ ! -f "${IC_input_file_path}" ]]; then + Print_Error 'File was not created.' + return 1 + fi +} From d93554bafd265b885ad7a42fc49165c9d9bb5ed6 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 21 Jun 2023 17:01:08 +0200 Subject: [PATCH 119/549] Add Ensure_All_Needed_Input_Exists_IC and Run_Software_IC functionality. --- bash/IC_functionality.bash | 38 ++++++++++--- tests/unit_tests_IC_functionality.bash | 77 +++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 10 deletions(-) diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index ce9319d..be16aae 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -9,19 +9,20 @@ function Prepare_Software_Input_File_IC() { - # check if output directory exists and create it if not + # check if output directory exists local IC_output_directory="${HYBRID_output_directory}/IC" if [[ ! -d "${IC_output_directory}" ]]; then mkdir "${IC_output_directory}" || exit ${HYBRID_fatal_builtin} fi - echo "${HYBRID_default_configurations_folder}" - # check if config already exists in the output directory, exit if yes - IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") - IC_input_file_path="${IC_output_directory}/${IC_config_name}" + # check if config already exists + local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") + local IC_input_file_path="${IC_output_directory}/${IC_config_name}" if [[ ! -f "${IC_input_file_path}" ]]; then cp "${HYBRID_software_base_config_file[IC]}" "${IC_output_directory}" || exit ${HYBRID_fatal_builtin} - # rewrite stuff + if [[ "${HYBRID_software_new_input_keys[IC]}" != "" ]]; then + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File "YAML" "${IC_input_file_path}" "${HYBRID_software_new_input_keys[IC]}" + fi else exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ "File \"${IC_input_file_path}\" is already there." @@ -30,10 +31,31 @@ function Prepare_Software_Input_File_IC() function Ensure_All_Needed_Input_Exists_IC() { - Print_Not_Implemented_Function_Error + # check if path exists + local IC_output_directory="${HYBRID_output_directory}/IC" + if [[ ! -d "${IC_output_directory}" ]]; then + exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + "Folder \"${IC_output_directory}\" does not exist." + fi + # check if config exists + local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") + local IC_input_file_path="${IC_output_directory}/${IC_config_name}" + if [[ ! -f "${IC_input_file_path}" ]]; then + exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + "The config \"${IC_input_file_path}\" does not exist." + fi } function Run_Software_IC() { - Print_Not_Implemented_Function_Error + local IC_output_directory="${HYBRID_output_directory}/IC" + local IC_terminal_output="${HYBRID_output_directory}/IC/Terminal_Output.txt" + local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") + local IC_input_file_path="${IC_output_directory}/${IC_config_name}" + + ./"${HYBRID_software_executable[IC]}" \ + "-i" "${IC_input_file_path}" \ + "-o" "${IC_output_directory}" \ + "-n" \ + >> "${IC_terminal_output}" } diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index f03c9fd..3355234 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -13,11 +13,11 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() list_of_files=( 'IC_functionality.bash' 'global_variables.bash' + 'software_input_functionality.bash' ) for file_to_be_sourced in "${list_of_files[@]}"; do source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done - HYBRID_repository_global_path="${HYBRIDT_repository_top_level_path}" Define_Further_Global_Variables } @@ -28,11 +28,84 @@ function Unit_Test__IC-create-input-file() rm -r "${HYBRID_output_directory}" fi mkdir "${HYBRID_output_directory}" + Prepare_Software_Input_File_IC IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") IC_input_file_path="${HYBRID_output_directory}/IC/${IC_config_name}" if [[ ! -f "${IC_input_file_path}" ]]; then - Print_Error 'File was not created.' + Print_Error 'The config was not properly created.' + return 1 + fi + ( Prepare_Software_Input_File_IC &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Failed checking the existens of the config.' + return 1 + fi + return 0 +} + +function Make_Test_Preliminary_Operations__IC-check-all-input() +{ + Make_Test_Preliminary_Operations__IC-create-input-file +} + +function Unit_Test__IC-check-all-input() +{ + HYBRID_output_directory="./test_dir_IC" + if [[ -d "${HYBRID_output_directory}" ]]; then + rm -r "${HYBRID_output_directory}" + fi + mkdir "${HYBRID_output_directory}" + + ( Ensure_All_Needed_Input_Exists_IC &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of input directory failed.' + return 1 + fi + mkdir "${HYBRID_output_directory}/IC" + ( Ensure_All_Needed_Input_Exists_IC &> /dev/null ) + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of the config file failed.' + return 1 + fi + return 0 +} + +function Make_Test_Preliminary_Operations__IC-test-run-software() +{ + Make_Test_Preliminary_Operations__IC-create-input-file +} + +function Unit_Test__IC-test-run-software() +{ + HYBRID_output_directory="test_dir_IC" + if [[ -d "${HYBRID_output_directory}" ]]; then + rm -r "${HYBRID_output_directory}" + fi + local IC_exec_directory="${HYBRID_output_directory}/IC" + mkdir -p "${IC_exec_directory}" + + HYBRID_software_executable[IC]="${HYBRID_output_directory}/dummy_exec_IC.sh" + touch "${HYBRID_software_executable[IC]}" + echo "#!/bin/bash" >> "${HYBRID_software_executable[IC]}" + echo "echo \$@" >> "${HYBRID_software_executable[IC]}" + chmod u+x "${HYBRID_software_executable[IC]}" + + local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") + local IC_input_file_path="${HYBRID_output_directory}/IC/${IC_config_name}" + touch "${IC_input_file_path}" + + Run_Software_IC + local IC_terminal_output="${HYBRID_output_directory}/IC/Terminal_Output.txt" + if [[ ! -f "${IC_terminal_output}" ]]; then + Print_Error 'The terminal output was not created.' + return 1 + fi + local terminal_output_result=$(head "${IC_terminal_output}") + local correct_result="-i ${IC_input_file_path} -o ${IC_exec_directory} -n" + if [[ "${terminal_output_result}" != "${correct_result}" ]]; then + Print_Error 'The terminal output has not the expected content.' return 1 fi + return 0 } From 2c0c5fdaa8324e4854ede7dda68fa7bf757ef7cb Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 22 Jun 2023 15:34:58 +0200 Subject: [PATCH 120/549] Adjust minor formatting and phrasing --- bash/IC_functionality.bash | 19 ++++++++++--------- tests/unit_tests_IC_functionality.bash | 8 ++++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index be16aae..ed9a624 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -20,11 +20,12 @@ function Prepare_Software_Input_File_IC() local IC_input_file_path="${IC_output_directory}/${IC_config_name}" if [[ ! -f "${IC_input_file_path}" ]]; then cp "${HYBRID_software_base_config_file[IC]}" "${IC_output_directory}" || exit ${HYBRID_fatal_builtin} - if [[ "${HYBRID_software_new_input_keys[IC]}" != "" ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File "YAML" "${IC_input_file_path}" "${HYBRID_software_new_input_keys[IC]}" + if [[ "${HYBRID_software_new_input_keys[IC]}" != '' ]]; then + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + 'YAML' "${IC_input_file_path}" "${HYBRID_software_new_input_keys[IC]}" fi else - exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ "File \"${IC_input_file_path}\" is already there." fi } @@ -34,14 +35,14 @@ function Ensure_All_Needed_Input_Exists_IC() # check if path exists local IC_output_directory="${HYBRID_output_directory}/IC" if [[ ! -d "${IC_output_directory}" ]]; then - exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ "Folder \"${IC_output_directory}\" does not exist." fi # check if config exists local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") local IC_input_file_path="${IC_output_directory}/${IC_config_name}" if [[ ! -f "${IC_input_file_path}" ]]; then - exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ "The config \"${IC_input_file_path}\" does not exist." fi } @@ -52,10 +53,10 @@ function Run_Software_IC() local IC_terminal_output="${HYBRID_output_directory}/IC/Terminal_Output.txt" local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") local IC_input_file_path="${IC_output_directory}/${IC_config_name}" - + ./"${HYBRID_software_executable[IC]}" \ - "-i" "${IC_input_file_path}" \ - "-o" "${IC_output_directory}" \ - "-n" \ + '-i' "${IC_input_file_path}" \ + '-o' "${IC_output_directory}" \ + '-n' \ >> "${IC_terminal_output}" } diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 3355234..93eade0 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -33,12 +33,12 @@ function Unit_Test__IC-create-input-file() IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") IC_input_file_path="${HYBRID_output_directory}/IC/${IC_config_name}" if [[ ! -f "${IC_input_file_path}" ]]; then - Print_Error 'The config was not properly created.' + Print_Error 'The input file(s) were not properly created.' return 1 fi ( Prepare_Software_Input_File_IC &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error 'Failed checking the existens of the config.' + Print_Error 'Preparation of input with existent config succeeded.' return 1 fi return 0 @@ -59,13 +59,13 @@ function Unit_Test__IC-check-all-input() ( Ensure_All_Needed_Input_Exists_IC &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error 'Validation of input directory failed.' + Print_Error 'Ensuring existence of not-existing output directory succeeded.' return 1 fi mkdir "${HYBRID_output_directory}/IC" ( Ensure_All_Needed_Input_Exists_IC &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error 'Validation of the config file failed.' + Print_Error 'Ensuring existence of not-existing config file succeeded.' return 1 fi return 0 From 6d7b5d860ec7ef0e6ba31079c92dc02b262e39fb Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 22 Jun 2023 15:36:10 +0200 Subject: [PATCH 121/549] Implement sanity checks and set constant global variables This was easily possible, because the names of the sections are stored together in a global, constant array. After the sanity checks are done and the global variables are set, these are made readonly. --- bash/global_variables.bash | 17 +++++++++++++++-- bash/sanity_checks.bash | 27 ++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 182873a..ac0db5d 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -50,11 +50,11 @@ function Define_Further_Global_Variables() [Input_file]='HYBRID_software_base_config_file[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' ) - # Variables to be set from command line + # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' HYBRID_output_directory='.' - # Variables to be set from configuration/setup + # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_given_software_sections=() declare -gA HYBRID_software_executable=( [IC]='' @@ -74,4 +74,17 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) + # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded + declare -gA HYBRID_software_output_directory=( + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' + ) + declare -gA HYBRID_software_configuration_file=( + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' + ) } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 10f7250..856e754 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -9,5 +9,30 @@ function Perform_Sanity_Checks_On_Provided_Input() { - Print_Not_Implemented_Function_Error + local key base_file + for key in "${HYBRID_valid_software_configuration_sections[@]}"; do + if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then + __static__Ensure_Executable_Exists 'IC' + HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/${key}" + base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") + HYBRID_software_configuration_file[IC]="${HYBRID_software_output_directory[${key}]}/${base_file}" + fi + done + readonly HYBRID_software_output_directory HYBRID_software_configuration_file +} + +function __static__Ensure_Executable_Exists() +{ + local label=$1 file_path + file_path="${HYBRID_software_executable[${label}]}" + if [[ "${file_path}" = '' ]]; then + exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ + "Software executable for '${label}' run was not specified." + elif [[ ! -f "${file_path}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + "The executable file for the '${label}' run was not found." + elif [[ ! -x "${file_path}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + "The executable file for the '${label}' run is not executable." + fi } From 95553064d1aca5bd954808e09644134b7ddb0ddd Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 22 Jun 2023 16:22:53 +0200 Subject: [PATCH 122/549] Use auxiliary globals in IC code and improve tests The global variables that are declared after the sanity checks are now used in the IC functionality. By doing that the code has been cleaned from repeated local variables in different functions. Some checks have been reordered and improved. Tests have been better structured (thanks to the auxiliary globals) and a clean up of files has been done in the passing case. --- bash/IC_functionality.bash | 54 +++++++---------- tests/unit_tests_IC_functionality.bash | 83 ++++++++++++++------------ 2 files changed, 66 insertions(+), 71 deletions(-) diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index ed9a624..cf18960 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -9,54 +9,40 @@ function Prepare_Software_Input_File_IC() { - # check if output directory exists - local IC_output_directory="${HYBRID_output_directory}/IC" - if [[ ! -d "${IC_output_directory}" ]]; then - mkdir "${IC_output_directory}" || exit ${HYBRID_fatal_builtin} - fi - - # check if config already exists - local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") - local IC_input_file_path="${IC_output_directory}/${IC_config_name}" - if [[ ! -f "${IC_input_file_path}" ]]; then - cp "${HYBRID_software_base_config_file[IC]}" "${IC_output_directory}" || exit ${HYBRID_fatal_builtin} - if [[ "${HYBRID_software_new_input_keys[IC]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ - 'YAML' "${IC_input_file_path}" "${HYBRID_software_new_input_keys[IC]}" - fi - else + mkdir -p "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} + if [[ -f "${HYBRID_software_configuration_file[IC]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - "File \"${IC_input_file_path}\" is already there." + "Configuration file \"${HYBRID_software_configuration_file[IC]}\" is already existing." + elif [[ ! -f "${HYBRID_software_base_config_file[IC]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + "Base configuration file \"${HYBRID_software_base_config_file[IC]}\" was not found." + fi + cp "${HYBRID_software_base_config_file[IC]}"\ + "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} + if [[ "${HYBRID_software_new_input_keys[IC]}" != '' ]]; then + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + 'YAML' "${HYBRID_software_configuration_file[IC]}" "${HYBRID_software_new_input_keys[IC]}" fi } function Ensure_All_Needed_Input_Exists_IC() { - # check if path exists - local IC_output_directory="${HYBRID_output_directory}/IC" - if [[ ! -d "${IC_output_directory}" ]]; then + if [[ ! -d "${HYBRID_software_output_directory[IC]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - "Folder \"${IC_output_directory}\" does not exist." + "Folder \"${HYBRID_software_output_directory[IC]}\" does not exist." fi - # check if config exists - local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") - local IC_input_file_path="${IC_output_directory}/${IC_config_name}" - if [[ ! -f "${IC_input_file_path}" ]]; then + if [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - "The config \"${IC_input_file_path}\" does not exist." + "The configuration file \"${HYBRID_software_configuration_file[IC]}\" was not found." fi } function Run_Software_IC() { - local IC_output_directory="${HYBRID_output_directory}/IC" - local IC_terminal_output="${HYBRID_output_directory}/IC/Terminal_Output.txt" - local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") - local IC_input_file_path="${IC_output_directory}/${IC_config_name}" - + local ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" ./"${HYBRID_software_executable[IC]}" \ - '-i' "${IC_input_file_path}" \ - '-o' "${IC_output_directory}" \ + '-i' "${HYBRID_software_configuration_file[IC]}" \ + '-o' "${HYBRID_software_output_directory[IC]}" \ '-n' \ - >> "${IC_terminal_output}" + >> "${ic_terminal_output}" } diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 93eade0..13ffd9f 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -14,26 +14,31 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() 'IC_functionality.bash' 'global_variables.bash' 'software_input_functionality.bash' + 'sanity_checks.bash' ) for file_to_be_sourced in "${list_of_files[@]}"; do source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done Define_Further_Global_Variables + HYBRID_output_directory="./test_dir_IC" + HYBRID_software_base_config_file[IC]='my_cool_conf.yaml' + HYBRID_given_software_sections=( 'IC' ) + HYBRID_software_executable[IC]=$(which ls) # Use command as fake executable + Perform_Sanity_Checks_On_Provided_Input } function Unit_Test__IC-create-input-file() { - HYBRID_output_directory="./test_dir_IC" - if [[ -d "${HYBRID_output_directory}" ]]; then - rm -r "${HYBRID_output_directory}" + touch "${HYBRID_software_base_config_file[IC]}" + Prepare_Software_Input_File_IC + if [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then + Print_Error 'The output directory and/or software input file were not properly created.' + return 1 fi - mkdir "${HYBRID_output_directory}" - + rm -r "${HYBRID_output_directory}/"* Prepare_Software_Input_File_IC - IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") - IC_input_file_path="${HYBRID_output_directory}/IC/${IC_config_name}" - if [[ ! -f "${IC_input_file_path}" ]]; then - Print_Error 'The input file(s) were not properly created.' + if [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then + Print_Error 'The input file was not properly created in the output folder.' return 1 fi ( Prepare_Software_Input_File_IC &> /dev/null ) @@ -44,6 +49,12 @@ function Unit_Test__IC-create-input-file() return 0 } +function Clean_Tests_Environment_For_Following_Test__IC-create-input-file() +{ + rm "${HYBRID_software_base_config_file[IC]}" + rm -r "${HYBRID_output_directory}" +} + function Make_Test_Preliminary_Operations__IC-check-all-input() { Make_Test_Preliminary_Operations__IC-create-input-file @@ -51,26 +62,31 @@ function Make_Test_Preliminary_Operations__IC-check-all-input() function Unit_Test__IC-check-all-input() { - HYBRID_output_directory="./test_dir_IC" - if [[ -d "${HYBRID_output_directory}" ]]; then - rm -r "${HYBRID_output_directory}" - fi - mkdir "${HYBRID_output_directory}" - ( Ensure_All_Needed_Input_Exists_IC &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing output directory succeeded.' return 1 fi - mkdir "${HYBRID_output_directory}/IC" + mkdir -p "${HYBRID_software_output_directory[IC]}" ( Ensure_All_Needed_Input_Exists_IC &> /dev/null ) if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing config file succeeded.' return 1 fi + touch "${HYBRID_software_configuration_file[IC]}" + ( Ensure_All_Needed_Input_Exists_IC ) + if [[ $? -ne 0 ]]; then + Print_Error 'Ensuring existence of existing folder/file failed.' + return 1 + fi return 0 } +function Clean_Tests_Environment_For_Following_Test__IC-check-all-input() +{ + rm -r "${HYBRID_output_directory}" +} + function Make_Test_Preliminary_Operations__IC-test-run-software() { Make_Test_Preliminary_Operations__IC-create-input-file @@ -78,34 +94,27 @@ function Make_Test_Preliminary_Operations__IC-test-run-software() function Unit_Test__IC-test-run-software() { - HYBRID_output_directory="test_dir_IC" - if [[ -d "${HYBRID_output_directory}" ]]; then - rm -r "${HYBRID_output_directory}" - fi - local IC_exec_directory="${HYBRID_output_directory}/IC" - mkdir -p "${IC_exec_directory}" - - HYBRID_software_executable[IC]="${HYBRID_output_directory}/dummy_exec_IC.sh" - touch "${HYBRID_software_executable[IC]}" - echo "#!/bin/bash" >> "${HYBRID_software_executable[IC]}" - echo "echo \$@" >> "${HYBRID_software_executable[IC]}" - chmod u+x "${HYBRID_software_executable[IC]}" - - local IC_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") - local IC_input_file_path="${HYBRID_output_directory}/IC/${IC_config_name}" - touch "${IC_input_file_path}" - + HYBRID_software_executable[IC]="${HYBRID_output_directory}/dummy_exec_IC.bash" + local -r ic_terminal_output="${HYBRID_output_directory}/IC/Terminal_Output.txt" + mkdir -p "${HYBRID_software_output_directory[IC]}" + printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[IC]}" + chmod a+x "${HYBRID_software_executable[IC]}" + local terminal_output_result correct_result Run_Software_IC - local IC_terminal_output="${HYBRID_output_directory}/IC/Terminal_Output.txt" - if [[ ! -f "${IC_terminal_output}" ]]; then + if [[ ! -f "${ic_terminal_output}" ]]; then Print_Error 'The terminal output was not created.' return 1 fi - local terminal_output_result=$(head "${IC_terminal_output}") - local correct_result="-i ${IC_input_file_path} -o ${IC_exec_directory} -n" + terminal_output_result=$(< "${ic_terminal_output}") + correct_result="-i ${HYBRID_software_configuration_file[IC]} -o ${HYBRID_software_output_directory[IC]} -n" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then Print_Error 'The terminal output has not the expected content.' return 1 fi return 0 } + +function Clean_Tests_Environment_For_Following_Test__IC-test-run-software() +{ + Clean_Tests_Environment_For_Following_Test__IC-check-all-input +} From d58d7da08dbc31d2a8e9c649d58f73d3f7fdba90 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 22 Jun 2023 16:29:27 +0200 Subject: [PATCH 123/549] Remove return 0 at the end of some unit tests This is not needed as if the function arrives there, it will return 0 as exit code of the last executed command. --- bash/IC_functionality.bash | 2 +- tests/unit_tests_IC_functionality.bash | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index cf18960..9c4b631 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -18,7 +18,7 @@ function Prepare_Software_Input_File_IC() "Base configuration file \"${HYBRID_software_base_config_file[IC]}\" was not found." fi cp "${HYBRID_software_base_config_file[IC]}"\ - "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} + "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[IC]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[IC]}" "${HYBRID_software_new_input_keys[IC]}" diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 13ffd9f..c0fac0c 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -46,7 +46,6 @@ function Unit_Test__IC-create-input-file() Print_Error 'Preparation of input with existent config succeeded.' return 1 fi - return 0 } function Clean_Tests_Environment_For_Following_Test__IC-create-input-file() @@ -79,7 +78,6 @@ function Unit_Test__IC-check-all-input() Print_Error 'Ensuring existence of existing folder/file failed.' return 1 fi - return 0 } function Clean_Tests_Environment_For_Following_Test__IC-check-all-input() @@ -111,7 +109,6 @@ function Unit_Test__IC-test-run-software() Print_Error 'The terminal output has not the expected content.' return 1 fi - return 0 } function Clean_Tests_Environment_For_Following_Test__IC-test-run-software() From c58372c32898eae3dbde2b42ff464cc05254195c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 22 Jun 2023 17:17:33 +0200 Subject: [PATCH 124/549] Rename function to reflect its responsibility --- Hybrid-handler | 2 +- bash/sanity_checks.bash | 2 +- tests/unit_tests_IC_functionality.bash | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index 99c1b91..a6c3618 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -85,7 +85,7 @@ function Make_Needed_Operations_Depending_On_Execution_Mode() do ) local software_section Validate_And_Parse_Configuration_File - Perform_Sanity_Checks_On_Provided_Input + Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables for software_section in "${HYBRID_given_software_sections[@]}"; do Prepare_Software_Input_File "${software_section}" Ensure_All_Needed_Input_Exists "${software_section}" diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 856e754..5c99f61 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -7,7 +7,7 @@ # #=================================================== -function Perform_Sanity_Checks_On_Provided_Input() +function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables() { local key base_file for key in "${HYBRID_valid_software_configuration_sections[@]}"; do diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index c0fac0c..e8f0804 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -24,7 +24,7 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() HYBRID_software_base_config_file[IC]='my_cool_conf.yaml' HYBRID_given_software_sections=( 'IC' ) HYBRID_software_executable[IC]=$(which ls) # Use command as fake executable - Perform_Sanity_Checks_On_Provided_Input + Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } function Unit_Test__IC-create-input-file() From bb332c0ecd38586a88710ab812c7224b19c9100a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 23 Jun 2023 18:57:13 +0200 Subject: [PATCH 125/549] Fix bug in checking system requirements --- bash/system_requirements.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 6ef58bf..b310744 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -44,8 +44,8 @@ function Check_System_Requirements() declare -A system_information __static__Analyze_System_Properties for program in "${HYBRID_gnu_programs_required[@]}"; do - if [[ $(cut -d'|' -f2 <<< "${system_information[${program}]}") = '---' ]]; then - Print_Error "'${program#GNU-}' either not found or non-GNU version in use."\ + if [[ $(cut -d'|' -f2 <<< "${system_information[GNU-${program}]}") = '---' ]]; then + Print_Error "'${program}' either not found or non-GNU version in use."\ "Please, ensure that '${program}' is installed and in use." requirements_present=1 fi From 7998c0e0ccf1b8a86daf8982492b7a26c8f53c74 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 23 Jun 2023 18:06:05 +0200 Subject: [PATCH 126/549] Update BashLogger to version v0.2 and source it with options The new BashLogger version allows not to have to change the logger source to use it in the codebase and the changes we had to make are now possible to be injected using command line options at source time. --- bash/error_codes.bash | 2 +- bash/logger.bash | 258 ++++++++++++++++++++++++-------- bash/source_codebase_files.bash | 3 +- tests/tests_runner | 5 +- 4 files changed, 203 insertions(+), 65 deletions(-) diff --git a/bash/error_codes.bash b/bash/error_codes.bash index 7f2ac31..288dcca 100644 --- a/bash/error_codes.bash +++ b/bash/error_codes.bash @@ -21,4 +21,4 @@ readonly HYBRID_fatal_value_error=68 readonly HYBRID_fatal_logic_error=110 readonly HYBRID_fatal_missing_feature=111 readonly HYBRID_fatal_variable_unset=112 -readonly HYBRID_internal=113 +readonly HYBRID_internal_exit_code=113 diff --git a/bash/logger.bash b/bash/logger.bash index 8cad999..6203308 100644 --- a/bash/logger.bash +++ b/bash/logger.bash @@ -7,12 +7,13 @@ # #=================================================== # -# This file has been taken and adapted from the BashLogger project -# and therefore its original license header is reported here below. +# This file has been taken from the BashLogger project (v0.2) and, as +# requested, its original license header is reported here below. # #---------------------------------------------------------------------------------------- # -# Copyright (c) 2019 Alessandro Sciarra +# Copyright (c) 2019,2023 +# Alessandro Sciarra # # This file is part of BashLogger. # @@ -31,58 +32,128 @@ # #---------------------------------------------------------------------------------------- # -# The logger will print output to new file descriptor 3 to be able to use the logger in -# functions that "return" printing to stdout to be called in $(). +# The logger will print output to the chosen file descriptor (by default 42). This is +# done (instead of simply let it print to standard output) to be able to use the logger +# in functions that "return" by printing to stdout and that are meant to be called in $(). # -# ATTENTION: It might be checked if fd 3 exists and in case open it in the Logger itself. -# However, if the first Logger call is done in a subshell, the fd 3 is not -# open globally for the script and, therefore, we should ensure to open the -# fd 3 for the script. We do then this at source time. +# ATTENTION: It might be checked if the chosen fd exists and in case open it in the Logger +# function itself. However, if the first Logger call is done in a subshell, then +# the chosen fd would not be open globally for the script and following calls to +# the Logger would fail. Hence, we open the fd at source time and not close it +# in the Logger. # # NOTE: Nothing is done if this file is executed and not sourced! # if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - exec 3>&1 + BSHLGGR_outputFd=42 + BSHLGGR_defaultExitCode=1 + while [[ $# -gt 0 ]]; do + case "$1" in + --fd ) + if [[ ! $2 =~ ^[1-9][0-9]*$ ]] || (( $2>254 )); then + printf "Error sourcing BashLogger. '$1' option needs an integer value between 1 and 254.\n" + return 1 + else + BSHLGGR_outputFd=$2 + shift 2 + fi + ;; + --default-exit-code ) + if [[ ! $2 =~ ^[0-9]+$ ]] || (( $2>255 )); then + printf "Error sourcing BashLogger. '$1' option needs an integer value between 0 and 255.\n" + return 1 + else + BSHLGGR_defaultExitCode=$2 + shift 2 + fi + ;; + *) + printf "Error sourcing BashLogger. Unknown option '$1'.\n" + return 1 + ;; + esac + done + # Probably redundant check, but we want guarantee that 'eval' is safe to use here + # e.g. that the BSHLGGR_outputFd cannot be set to '; rm -rf /; #' (AAARGH!). + if [[ ! ${BSHLGGR_outputFd} =~ ^[1-9][0-9]*$ ]]; then + printf "Unexpected error sourcing BashLogger. Please contact developers.\n" + return 1 + else + eval "exec ${BSHLGGR_outputFd}>&1" + fi + readonly BSHLGGR_outputFd BSHLGGR_defaultExitCode fi -function Print_Trace() +function PrintTrace() { __static__Logger 'TRACE' "$@" } +function Print_Trace() +{ + PrintTrace "$@" +} -function Print_Debug() +function PrintDebug() { __static__Logger 'DEBUG' "$@" } +function Print_Debug() +{ + PrintDebug "$@" +} -function Print_Info() +function PrintInfo() { __static__Logger 'INFO' "$@" } +function Print_Info() +{ + PrintInfo "$@" +} -function Print_Attention() +function PrintAttention() { __static__Logger 'ATTENTION' "$@" } +function Print_Attention() +{ + PrintAttention "$@" +} -function Print_Warning() +function PrintWarning() { __static__Logger 'WARNING' "$@" } +function Print_Warning() +{ + PrintWarning "$@" +} -function Print_Error() +function PrintError() { __static__Logger 'ERROR' "$@" } +function Print_Error() +{ + PrintError "$@" +} -function Print_Fatal_And_Exit() +function PrintFatalAndExit() { __static__Logger 'FATAL' "$@" } +function Print_Fatal_And_Exit() +{ + PrintFatalAndExit "$@" +} +function PrintInternalAndExit() +{ + __static__Logger 'INTERNAL' "$@" +} function Print_Internal_And_Exit() { - exit_code=${HYBRID_internal:-1} __static__Logger 'INTERNAL' "$@" + PrintInternalAndExit "$@" } function __static__Logger() @@ -90,49 +161,55 @@ function __static__Logger() if [[ $# -lt 1 ]]; then __static__Logger 'INTERNAL' "${FUNCNAME} called without label!" fi - local label label_length label_to_be_printed color string final_endline restore_default - final_endline='\n' - restore_default='\e[0m' - label_length=10 + local label labelLength labelToBePrinted color emphColor finalEndline restoreDefault + finalEndline='\n' + restoreDefault='\e[0m' + labelLength=10 label="$1"; shift - label_to_be_printed=$(printf "%${label_length}s" "${label}:") + labelToBePrinted=$(printf "%${labelLength}s" "${label}:") if [[ ! ${label} =~ ^(INTERNAL|FATAL|ERROR|WARNING|ATTENTION|INFO|DEBUG|TRACE)$ ]]; then __static__Logger 'INTERNAL' "${FUNCNAME} called with unknown label '${label}'!" fi - __static__Is_Level_On "${label}" || return 0 + __static__IsLevelOn "${label}" || return 0 exec 4>&1 # duplicate fd 1 to restore it later case "${label}" in ERROR|FATAL ) - color='\e[91m' - exec 1>&2 ;; # here stdout to stderr! + # ;;& means go on in case matching following patterns + color='\e[91m' ;;& INTERNAL ) - color='\e[38;5;202m' + color='\e[38;5;202m' ;;& + ERROR|FATAL|INTERNAL ) + emphColor='\e[93m' exec 1>&2 ;; # here stdout to stderr! INFO ) - color='\e[92m' # ;;& means go on in case matching following -> do *) - ;;& + color='\e[92m' + emphColor='\e[96m' ;;& ATTENTION ) - color='\e[38;5;200m' ;;& + color='\e[38;5;200m' + emphColor='\e[38;5;141m' ;;& WARNING ) - color='\e[93m' ;;& + color='\e[93m' + emphColor='\e[38;5;202m' ;;& DEBUG ) - color='\e[38;5;38m' ;;& + color='\e[38;5;38m' + emphColor='\e[38;5;48m' ;;& TRACE ) - color='\e[38;5;247m' ;;& + color='\e[38;5;247m' + emphColor='\e[38;5;256m' ;;& * ) - exec 1>&3 ;; # here stdout to fd 3! + exec 1>&"${BSHLGGR_outputFd}" ;; # here stdout to chosen fd esac - if __static__Is_Element_In_Array '--' "$@"; then + if __static__IsElementInArray '--' "$@"; then while [[ "$1" != '--' ]]; do case "$1" in -n ) - final_endline='' + finalEndline='' shift ;; -l ) - label_to_be_printed="$(printf "%${label_length}s" '')" + labelToBePrinted="$(printf "%${labelLength}s" '')" shift ;; -d ) - restore_default='' + restoreDefault='' shift ;; * ) __static__Logger 'INTERNAL' "${FUNCNAME} called with unknown option \"$1\"!" ;; @@ -140,36 +217,87 @@ function __static__Logger() done shift fi - if [[ $# -eq 0 ]]; then - __static__Logger 'INTERNAL' "${FUNCNAME} called without message!" - fi + # Print out initial new-lines before label suppressing first argument if it was endlines only while [[ $1 =~ ^\\n ]]; do printf '\n' - set -- "${1/#\\n/}" "${@:2}" + if [[ "${1/#\\n/}" = '' ]]; then + shift + else + set -- "${1/#\\n/}" "${@:2}" + fi done - printf "\e[1m${color}${label_to_be_printed}\e[22m ${1//%/%%}" - shift + # Ensure something to print was given if [[ $# -eq 0 ]]; then - printf "${final_endline}" # If nothing more to print use 'final_endline' - else - printf '\n' - while [[ $# -gt 1 ]]; do - printf "${label_to_be_printed//?/ } ${1//%/%%}\n" - shift - done - printf "${label_to_be_printed//?/ } ${1//%/%%}${final_endline}" + __static__Logger 'INTERNAL' "${FUNCNAME} called without message (or with new lines only)!" fi + # Parse all arguments and save messages for later, possibly modified + local messagesToBePrinted emphNextString lastStringWasEmph indentation index + messagesToBePrinted=() + emphNextString='FALSE' + lastStringWasEmph='FALSE' + indentation='' + while [[ $# -gt 0 ]]; do + case "$1" in + --emph ) + # Here two cases should be handled: either '--emph' is an option or a string + # -> it is literal if the last command line option or if following the option + # -> it is an option otherwise + # Use a fallthrough to continue matching in the case construct and use *) case + # to print. However, shift and continue must be given if '--emph' was an option. + if [[ $# -eq 1 || ${emphNextString} = 'TRUE' ]]; then + : + else + emphNextString='TRUE' + shift + continue + fi + ;;& + *) + if [[ ${#messagesToBePrinted[@]} -gt 0 ]]; then + indentation="${labelToBePrinted//?/ } " + fi + # Set color and replace % by %% for later printf + if [[ ${emphNextString} = 'TRUE' ]]; then + messagesToBePrinted+=( "${emphColor}${1//%/%%}" ) + lastStringWasEmph='TRUE' + else + if [[ ${lastStringWasEmph} = 'FALSE' ]]; then + if [[ ${#messagesToBePrinted[@]} -gt 0 ]]; then + messagesToBePrinted[-1]+='\n' + fi + else + indentation='' + fi + messagesToBePrinted+=( "${indentation}${color}${1//%/%%}" ) + lastStringWasEmph='FALSE' + fi + emphNextString='FALSE' + ;; + esac + shift + done + # Last message has no endline, add 'finalEndline' to it + messagesToBePrinted[-1]+="${finalEndline}" + set -- "${messagesToBePrinted[@]}" + # Print first line + printf "\e[1m${color}${labelToBePrinted}\e[0m $1" + shift + # Print possible additional lines + while [[ $# -gt 0 ]]; do + printf "$1" + shift + done if [[ ${label} = 'INTERNAL' ]]; then - printf "${label_to_be_printed//?/ } Please, contact developers.\n" + printf "${labelToBePrinted//?/ } Please, contact developers.\n" fi - printf "${restore_default}" - exec 1>&4- # restore fd 1 and close fd 4 and not close fd 3 (it must stay open, see top of the file!) + printf "${restoreDefault}" + exec 1>&4- # restore fd 1 and close fd 4 and not close chosen fd (it must stay open, see top of the file!) if [[ ${label} =~ ^(FATAL|INTERNAL)$ ]]; then - exit "${exit_code:-1}" + exit "${exit_code:-${BSHLGGR_defaultExitCode}}" fi } -function __static__Is_Level_On() +function __static__IsLevelOn() { local label label="$1" @@ -178,7 +306,7 @@ function __static__Is_Level_On() return 0 fi # VERBOSE environment variable defines how verbose the output should be: - # - unset or empty -> till INFO (no DEBUG TRACE) + # - unset, empty, invalid value -> till INFO (no DEBUG TRACE) # - numeric -> till that level (1=ERROR, 2=WARNING, ...) # - string -> till that level local loggerLevels loggerLevelsOn level index @@ -206,7 +334,7 @@ function __static__Is_Level_On() return 1 } -function __static__Is_Element_In_Array() +function __static__IsElementInArray() { # ATTENTION: Since this function is used in the middle of the logger, the # logger cannot be used in this function otherwise fd 4 is closed! @@ -223,6 +351,14 @@ function __static__Is_Element_In_Array() #-----------------------------------------------------------------# #Set functions readonly to avoid possible redefinitions elsewhere readonly -f \ + PrintTrace \ + PrintDebug \ + PrintInfo \ + PrintAttention \ + PrintWarning \ + PrintError \ + PrintFatalAndExit \ + PrintInternalAndExit \ Print_Trace \ Print_Debug \ Print_Info \ @@ -232,5 +368,5 @@ readonly -f \ Print_Fatal_And_Exit \ Print_Internal_And_Exit \ __static__Logger \ - __static__Is_Level_On \ - __static__Is_Element_In_Array + __static__IsLevelOn \ + __static__IsElementInArray diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 811c0bd..cc1c59f 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -12,6 +12,8 @@ function __static__Source_Codebase_Files() local list_of_files file_to_be_sourced # Source error codes and fail with hard-coded generic error source "${HYBRID_top_level_path}/bash/error_codes.bash" || exit 1 + source "${HYBRID_top_level_path}/bash/logger.bash"\ + --fd 3 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} list_of_files=( 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' @@ -19,7 +21,6 @@ function __static__Source_Codebase_Files() 'configuration_parser.bash' 'dispatch_functions.bash' 'global_variables.bash' - 'logger.bash' 'system_requirements.bash' 'utility_functions.bash' 'version.bash' diff --git a/tests/tests_runner b/tests/tests_runner index 3faf7ef..278ab9a 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -70,9 +70,10 @@ function Define_Tests_Global_Variables () function Source_Needed_Files() { - source ${HYBRIDT_repository_top_level_path}/bash/error_codes.bash || exit 1 + source "${HYBRIDT_repository_top_level_path}/bash/error_codes.bash" || exit 1 + source "${HYBRIDT_repository_top_level_path}/bash/logger.bash"\ + --fd 3 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} local -r files_to_be_sourced=( - "${HYBRIDT_repository_top_level_path}/bash/logger.bash" "${HYBRIDT_repository_top_level_path}/bash/system_requirements.bash" "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" "${HYBRIDT_tests_folder}/command_line_parser_for_tests.bash" From ddd5820381209c8f0ad4b827e10abfecec36d815 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 23 Jun 2023 18:11:58 +0200 Subject: [PATCH 127/549] Make logger use file descriptor 42 Using the file descriptor number 3 was of course fine, but usually fd 3 is the first free one and it is usually used for temporary redirection of output. Hence, we use now 42 for the logger, so that collisions (and possible confusion) are way less likely. Even if 42 is the default fd one used by the logger when sourced without the --fd option, we still use the option with 42 as value, so that we are unaffected by possible future changes on the logger side. --- bash/source_codebase_files.bash | 2 +- tests/tests_runner | 2 +- tests/unit_tests.bash | 7 ++++--- tests/unit_tests_version.bash | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index cc1c59f..517967c 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -13,7 +13,7 @@ function __static__Source_Codebase_Files() # Source error codes and fail with hard-coded generic error source "${HYBRID_top_level_path}/bash/error_codes.bash" || exit 1 source "${HYBRID_top_level_path}/bash/logger.bash"\ - --fd 3 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} + --fd 42 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} list_of_files=( 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' diff --git a/tests/tests_runner b/tests/tests_runner index 278ab9a..579f494 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -72,7 +72,7 @@ function Source_Needed_Files() { source "${HYBRIDT_repository_top_level_path}/bash/error_codes.bash" || exit 1 source "${HYBRIDT_repository_top_level_path}/bash/logger.bash"\ - --fd 3 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} + --fd 42 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} local -r files_to_be_sourced=( "${HYBRIDT_repository_top_level_path}/bash/system_requirements.bash" "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index e5e672f..0c23e82 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -36,17 +36,18 @@ function Make_Test_Preliminary_Operations() # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 - } &>> "${HYBRIDT_log_file}" 3>&1 # The fd 3 is used by the logger. + } &>> "${HYBRIDT_log_file}" 42>&1 # The fd 42 is used by the logger. } function Run_Test() { - Unit_Test__$1 &>> "${HYBRIDT_log_file}" 3>&1 # The fd 3 is used by the logger. + Unit_Test__$1 &>> "${HYBRIDT_log_file}" 42>&1 # The fd 42 is used by the logger. } function Clean_Tests_Environment_For_Following_Test() { - Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 3>&1 # The fd 3 is used by the logger. + # The fd 42 is used by the logger. + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 42>&1 } #======================================================================================================================= diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash index 50d86eb..1c8ee5c 100644 --- a/tests/unit_tests_version.bash +++ b/tests/unit_tests_version.bash @@ -24,7 +24,7 @@ function Unit_Test__version() return 1 fi if hash git &> /dev/null; then - std_output=$(Print_Software_Version 3>&1) # We want to capture here a logger message that goes to fd 3 + std_output=$(Print_Software_Version 42>&1) # We want to capture here a logger message that goes to fd 42 if [[ $? -ne 0 || $(grep -c "${HYBRID_codebase_version}" <<< "${std_output}") -eq 0 ]] ; then Print_Error "Version printing with git failed." return 1 From c2519bd3228c27444b694c85e4228fc6935cb399 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 23 Jun 2023 19:41:54 +0200 Subject: [PATCH 128/549] Use logger --emph instead of quoting string to highlight --- bash/IC_functionality.bash | 8 +++--- bash/command_line_parsers/helper.bash | 3 +- bash/command_line_parsers/main_parser.bash | 9 +++--- bash/configuration_parser.bash | 6 ++-- bash/sanity_checks.bash | 6 ++-- bash/software_input_functionality.bash | 10 +++---- bash/system_requirements.bash | 18 ++++++------ bash/utility_functions.bash | 33 +++++++++++----------- bash/version.bash | 21 +++++++------- tests/command_line_parser_for_tests.bash | 19 +++++++------ tests/tests_runner | 12 ++++---- tests/unit_tests.bash | 2 +- tests/unit_tests_command_line_parser.bash | 7 +++-- tests/unit_tests_configuration.bash | 8 +++--- tests/unit_tests_help_for_user.bash | 2 +- tests/unit_tests_utility_functions.bash | 16 +++++------ 16 files changed, 93 insertions(+), 87 deletions(-) diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 9c4b631..a3a8c98 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -12,10 +12,10 @@ function Prepare_Software_Input_File_IC() mkdir -p "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} if [[ -f "${HYBRID_software_configuration_file[IC]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - "Configuration file \"${HYBRID_software_configuration_file[IC]}\" is already existing." + 'Configuration file ' --emph "${HYBRID_software_configuration_file[IC]}" ' is already existing.' elif [[ ! -f "${HYBRID_software_base_config_file[IC]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - "Base configuration file \"${HYBRID_software_base_config_file[IC]}\" was not found." + 'Base configuration file ' --emph "${HYBRID_software_base_config_file[IC]}" ' was not found.' fi cp "${HYBRID_software_base_config_file[IC]}"\ "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} @@ -29,11 +29,11 @@ function Ensure_All_Needed_Input_Exists_IC() { if [[ ! -d "${HYBRID_software_output_directory[IC]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - "Folder \"${HYBRID_software_output_directory[IC]}\" does not exist." + 'Folder ' --emph "${HYBRID_software_output_directory[IC]}" ' does not exist.' fi if [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - "The configuration file \"${HYBRID_software_configuration_file[IC]}\" was not found." + 'The configuration file ' --emph "${HYBRID_software_configuration_file[IC]}" ' was not found.' fi } diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index 2718a39..4f06146 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -18,7 +18,8 @@ function Give_Required_Help() ;; * ) Print_Internal_And_Exit\ - 'Unexpected value of HYBRID_execution_mode=${HYBRID_execution_mode} in ${FUNCNAME}' + 'Unexpected value of ' --emph "HYBRID_execution_mode=${HYBRID_execution_mode}"\ + ' in ' --emph "${FUNCNAME}" ;; esac diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 33ef0ac..d1cad8c 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -26,7 +26,8 @@ function Parse_Execution_Mode() ;; * ) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ - "Specified mode '$1' not valid! Run 'Hybrid-handler help' to get further information." + 'Specified mode ' --emph "$1" ' not valid! Run '\ + --emph 'Hybrid-handler help' ' to get further information.' esac shift # Update the global array with remaining options to be parsed @@ -43,7 +44,7 @@ function Parse_Execution_Mode() function Parse_Command_Line_Options() { if [[ ${HYBRID_execution_mode} != 'do' ]]; then - Print_Internal_And_Exit 'Command line options are allowed only in "do" mode for now.' + Print_Internal_And_Exit 'Command line options are allowed only in ' --emph 'do' ' mode for now.' fi set -- "${HYBRID_command_line_options_to_parse[@]}" while [[ $# -gt 0 ]]; do @@ -66,8 +67,8 @@ function Parse_Command_Line_Options() ;; * ) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ - "Invalid option \"$1\" specified in '${HYBRID_execution_mode}' execution mode!"\ - 'Use the "--help" option to get further information.' + 'Invalid option ' --emph "$1" ' specified in ' --emph "${HYBRID_execution_mode}"\ + ' execution mode!' 'Use the ' --emph '--help' ' option to get further information.' ;; esac done diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 6300d46..2c77b1b 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -49,7 +49,7 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() fi done exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ - "Invalid section \"${label}\" found in the handler configuration file." + 'Invalid section ' --emph "${label}" ' found in the handler configuration file.' fi done # Here all given sections are valid. Check possible duplicates/holes and ordering using the stored indices @@ -172,7 +172,7 @@ function __static__YAML_section_must_be_empty() local yaml_section=$1 if [[ "${yaml_section}" != '{}' ]]; then Print_Internal_And_Exit\ - "Not all keys in ${2:-some} section have been parsed. Remaining:"\ - "\n${yaml_section}\n" + 'Not all keys in ' --emph "${2:-some}" ' section have been parsed. Remaining:'\ + --emph "\n${yaml_section}\n" fi } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 5c99f61..7e0e51f 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -27,12 +27,12 @@ function __static__Ensure_Executable_Exists() file_path="${HYBRID_software_executable[${label}]}" if [[ "${file_path}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ - "Software executable for '${label}' run was not specified." + 'Software executable for ' --emph "${label}" ' run was not specified.' elif [[ ! -f "${file_path}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - "The executable file for the '${label}' run was not found." + 'The executable file for the ' --emph "${label}" ' run was not found.' elif [[ ! -x "${file_path}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - "The executable file for the '${label}' run is not executable." + 'The executable file for the ' --emph "${label}" ' run is not executable.' fi } diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 753d8e5..028daf3 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -34,9 +34,9 @@ function __static__Replace_Keys_Into_YAML_File() # Use yq -P to bring all YAML to same format (crucial for later check on number of lines) if ! yq -P --inplace "${base_input_file}" 2> /dev/null; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ - "File \"${base_input_file}\" does not seem to contain valid YAML syntax. Run"\ - " yq -P --inplace \"${base_input_file}\""\ - "to have more information about the problem." + 'File ' --emph "${base_input_file}" ' does not seem to contain valid YAML syntax. Run'\ + --emph " yq -P --inplace \"${base_input_file}\""\ + "\nto have more information about the problem." elif ! keys_to_be_replaced=$(yq -P <(printf "${keys_to_be_replaced}\n") 2> /dev/null); then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ 'Keys to be replaced do not seem to contain valid YAML syntax.' @@ -52,7 +52,7 @@ function __static__Replace_Keys_Into_YAML_File() if [[ $(wc -l < "${base_input_file}") -ne ${initial_number_of_lines} ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'One or more provided software input keys were not found to be replaced'\ - "in the \"${base_input_file}\" file." "Please, check your configuration file." + 'in the ' --emph "${base_input_file}" ' file.' 'Please, check your configuration file.' fi } @@ -62,7 +62,7 @@ function __static__Replace_Keys_Into_Txt_File() # Impose that both file and new keys have two entries per line if ! awk 'NF != 2 { exit 1 }' "${base_input_file}"; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ - "File \"${base_input_file}\" does not seem to contain two columns per line only!" + 'File ' --emph "${base_input_file}" ' does not seem to contain two columns per line only!' elif ! awk 'NF != 2 { exit 1 }' <(printf "${keys_to_be_replaced}\n"); then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ 'Keys to be replaced do not seem to contain valid key-value syntax.' diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index b310744..a591f8b 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -45,8 +45,8 @@ function Check_System_Requirements() __static__Analyze_System_Properties for program in "${HYBRID_gnu_programs_required[@]}"; do if [[ $(cut -d'|' -f2 <<< "${system_information[GNU-${program}]}") = '---' ]]; then - Print_Error "'${program}' either not found or non-GNU version in use."\ - "Please, ensure that '${program}' is installed and in use." + Print_Error --emph "${program}" ' either not found or non-GNU version in use.'\ + 'Please, ensure that ' --emph "${program}" ' is installed and in use.' requirements_present=1 fi done @@ -56,18 +56,20 @@ function Check_System_Requirements() for program in "${!HYBRID_versions_requirements[@]}"; do min_version=${HYBRID_versions_requirements["${program}"]} if [[ $(cut -d'|' -f1 <<< "${system_information[${program}]}") = '---' ]]; then - Print_Error "'${program}' command not found! Minimum version ${min_version} is required." + Print_Error --emph "${program}" ' command not found! Minimum version '\ + --emph "${min_version}" ' is required.' requirements_present=1 continue fi version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") if [[ ${version_found} = '---' ]]; then - Print_Warning "Unable to find version of '${program}', skipping version check!"\ - "Please ensure that current version is at least ${min_version}." + Print_Warning 'Unable to find version of ' --emph "${program}" ', skipping version check!'\ + 'Please ensure that current version is at least ' --emph "${min_version}" '.' continue fi if [[ $(cut -d'|' -f3 <<< "${system_information[${program}]}") = '---' ]]; then - Print_Error "'${program}' version ${version_found} found, but version ${min_version} is required." + Print_Error --emph "${program}" ' version ' --emph "${version_found}"\ + ' found, but version ' --emph "${min_version}" ' is required.' requirements_present=1 fi done @@ -76,8 +78,8 @@ function Check_System_Requirements() fi for name in "${HYBRID_env_variables_required[@]}"; do if [[ ${system_information[${name}]} = '---' ]]; then - Print_Error "'${name}' environment variable either unset or empty."\ - "Please, ensure that '${name}' is properly set." + Print_Error --emph "${name}" ' environment variable either unset or empty.'\ + 'Please, ensure that ' --emph "${name}" ' is properly set.' requirements_present=1 fi done diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 7b216be..49f273e 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -32,11 +32,11 @@ function Has_YAML_String_Given_Key() { local yaml_string section key if [[ $# -lt 2 ]]; then - Print_Internal_And_Exit "Function '${FUNCNAME}' called with less than 2 arguments." + Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with less than 2 arguments.' fi yaml_string=$1; shift if ! yq <<< "${yaml_string}" &> /dev/null; then - Print_Internal_And_Exit "Function '${FUNCNAME}' called with invalid YAML string." + Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with invalid YAML string.' fi section="$(printf '.%s' "${@:1:$#-1}")" # All arguments but last key=${@: -1} # Last argument @@ -54,9 +54,9 @@ function Read_From_YAML_String_Given_Key() { local yaml_string key if [[ $# -lt 2 ]]; then - Print_Internal_And_Exit "Function '${FUNCNAME}' called with less than 2 arguments." + Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with less than 2 arguments.' elif ! Has_YAML_String_Given_Key "$@"; then - Print_Internal_And_Exit "Function '${FUNCNAME}' called with YAML string not containing given key." + Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with YAML string not containing given key.' fi yaml_string=$1; shift key="$(printf '.%s' "$@")" @@ -70,9 +70,9 @@ function Print_YAML_String_Without_Given_Key() { local yaml_string key if [[ $# -lt 2 ]]; then - Print_Internal_And_Exit "Function '${FUNCNAME}' called with less than 2 arguments." + Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with less than 2 arguments.' elif ! Has_YAML_String_Given_Key "$@"; then - Print_Internal_And_Exit "Function '${FUNCNAME}' called with YAML string not containing given key." + Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with YAML string not containing given key.' fi yaml_string=$1; shift key="$(printf '.%s' "$@")" @@ -105,7 +105,7 @@ function Print_Centered_Line() # Determine length of input at net of formatting codes (color, face) real_length=$(printf '%s' "${input_string}" | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]//g" | wc -c) if (( output_total_width - 2 - real_length < 0 )); then - Print_Fatal_And_Exit "Error in \"${FUNCNAME}\": specify larger total width!" + Print_Fatal_And_Exit 'Error in ' --emph "${FUNCNAME}" ': specify larger total width!' fi # In the following we build a very long string of padding characters that # will be later truncated when printing the output string. Very long is @@ -123,12 +123,12 @@ function Print_Centered_Line() function Print_Option_Specification_Error_And_Exit() { exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ - "The value of the option \"$1\" was not correctly specified (either forgotten or invalid)!" + 'The value of the option ' --emph "$1" ' was not correctly specified (either forgotten or invalid)!' } function Print_Not_Implemented_Function_Error() { - Print_Error "Function \"${FUNCNAME[1]}\" not implemented yet, skipping it." + Print_Error 'Function ' --emph "${FUNCNAME[1]}" ' not implemented yet, skipping it.' } function Remove_Comments_In_File() @@ -143,9 +143,9 @@ function Remove_Comments_In_File() comment_character=${2:-#} if [[ ! -f ${filename} ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - "File \"${filename}\" not found." + 'File ' --emph "${filename}" ' not found.' elif [[ ${#comment_character} -ne 1 ]]; then - Print_Internal_And_Exit "Comment character \"${comment_character}\" invalid!" + Print_Internal_And_Exit 'Comment character ' --emph "${comment_character}" ' invalid!' else sed -i '/^[[:blank:]]*'"${comment_character}"'/d;s/[[:blank:]]*'"${comment_character}"'.*//' "${filename}" fi @@ -168,8 +168,8 @@ function Call_Function_If_Existing_Or_Exit() ${name_of_the_function} "$@" || return $? else exit_code=${HYBRID_fatal_missing_feature} Print_Internal_And_Exit\ - "\nFunction \"${name_of_the_function}\" not found!"\ - "Please provide an implementation following the in-code documentation." + '\nFunction ' --emph "${name_of_the_function}" ' not found!'\ + 'Please provide an implementation following the in-code documentation.' fi } @@ -201,7 +201,7 @@ function Ensure_That_Given_Variables_Are_Set() { continue fi Print_Internal_And_Exit\ - "Variable \"${variable_name}\" not set in function \"${FUNCNAME[1]}\"." + 'Variable ' --emph "${variable_name}" ' not set in function ' --emph "${FUNCNAME[1]}" '.' fi done } @@ -228,7 +228,7 @@ function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { fi fi Print_Internal_And_Exit\ - "Variable \"${variable_name}\" unset or empty in function \"${FUNCNAME[1]}\"." + 'Variable ' --emph "${variable_name}" ' unset or empty in function ' --emph "${FUNCNAME[1]}" '.' done } @@ -249,7 +249,8 @@ function Make_Functions_Defined_In_This_File_Readonly() ) if [[ ${#declared_functions[@]} -eq 0 ]]; then Print_Internal_And_Exit\ - "Function \"${FUNCNAME}\" called, but no function found in file\n file \"${BASH_SOURCE[1]}\"" + 'Function ' --emph "${FUNCNAME}" ' called, but no function found in file\n file '\ + --emph "${BASH_SOURCE[1]}" '.' else readonly -f "${declared_functions[@]}" fi diff --git a/bash/version.bash b/bash/version.bash index 49e8e95..0fc6c55 100644 --- a/bash/version.bash +++ b/bash/version.bash @@ -20,14 +20,14 @@ function Print_Software_Version() fi local git_tag_short git_tag_long tag_date if ! git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags 2> /dev/null); then - Print_Warning "It was not possible to obtain the version in use!"\ - "This probably (but not necessarily) means that you are"\ - "behind any release in the Hybrid-handler history.\n" + Print_Warning 'It was not possible to obtain the version in use!'\ + 'This probably (but not necessarily) means that you are'\ + 'behind any release in the Hybrid-handler history.\n' __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" return 0 fi if ! git_tag_short=$(git -C "${HYBRID_top_level_path}" describe --tags --abbr=0 2> /dev/null); then - Print_Internal_And_Exit "Unexpected error in \"${FUNCNAME}\" trying to obtain the closest git tag." + Print_Internal_And_Exit 'Unexpected error in ' --emph "${FUNCNAME}" ' trying to obtain the closest git tag.' fi tag_date=$(date -d "$(git -C "${HYBRID_top_level_path}" log -1 --format=%ai "${git_tag_short}")" +'%d %B %Y') if [[ "${git_tag_short}" != "${git_tag_long}" ]]; then @@ -36,13 +36,12 @@ function Print_Software_Version() else git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags --dirty --broken 2>/dev/null) fi - local last_stable_release_string=$(printf '\e[38;5;202m%s (%s)' "${git_tag_short}" "${tag_date}") - Print_Warning "You are not using an official release of the Hybrid-handler."\ - "Unless you have a reason not to do so, it would be better"\ - "to checkout a stable release. The last stable release behind"\ - "the commit you are using is: ${last_stable_release_string}\n"\ - "The repository state is $(printf '\e[38;5;202m%s\e[93m' "${git_tag_long}")"\ - "(see git-describe documentation for more information)." + Print_Warning 'You are not using an official release of the Hybrid-handler.'\ + 'Unless you have a reason not to do so, it would be better'\ + 'to checkout a stable release. The last stable release behind'\ + 'the commit you are using is: ' --emph "${git_tag_short}"\ + ' (' --emph "${tag_date}" ')\n' 'The repository state is '\ + --emph "${git_tag_long}" '' '(see git-describe documentation for more information).' else __static__Print_Pretty_Version_Line "${git_tag_short}" "${tag_date}" fi diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 08b1b8e..915b891 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -13,13 +13,13 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() suite_name="$1" if [[ ! ${suite_name} =~ ^(functional|unit)$ ]]; then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ - "Invalid tests type \"${suite_name}\". Valid values: \"functional\", \"unit\"."\ - "Use the '--help' option to get more information." + 'Invalid tests type ' --emph "${suite_name}" '. Valid values: ' --emph 'functional'\ + ', ' --emph 'unit' '.' 'Use the ' --emph '--help' ' option to get more information.' fi code_filename="${HYBRIDT_tests_folder}/${suite_name}_tests.bash" if [[ ! -f "${code_filename}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - "File \"${code_filename}\" not found." + 'File ' --emph "${code_filename}" ' not found.' else source "${code_filename}" || exit ${HYBRID_fatal_builtin} fi @@ -62,7 +62,8 @@ function Parse_Tests_Command_Line_Options() shift ;; * ) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ - "Invalid option \"$1\" specified! Use the \"--help\" option to get further information." + 'Invalid option ' --emph "$1" ' specified! Use the '\ + --emph '--help' ' option to get further information.' ;; esac done @@ -128,7 +129,7 @@ function __static__Set_Tests_To_Be_Run_Using_Numbers() /\-/{split($0, res, "-"); for(i=res[1]; i<=res[2]; i++){printf "%d\n", i}; next} {printf "%d\n", $0}' <<< "${selection_string}") ) - Print_Debug "Selected tests indices: ( ${numeric_list[*]} )" + Print_Debug 'Selected tests indices: ' --emph "( ${numeric_list[*]} )" selected_tests=() for number in "${numeric_list[@]}"; do # The user selects human-friendly numbers (1,2,...), here go back to array indices @@ -137,12 +138,12 @@ function __static__Set_Tests_To_Be_Run_Using_Numbers() selected_tests+=( "${HYBRIDT_tests_to_be_run[number]}" ) else exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ - "Some specified test number within \"$1\" is not valid! Use"\ - "the '-t' option without value to get a list of available tests." + 'Some specified test number within ' --emph "$1" ' is not valid! Use'\ + 'the ' --emph '-t' ' option without value to get a list of available tests.' fi done HYBRIDT_tests_to_be_run=( "${selected_tests[@]}" ) - Print_Debug "Selected tests: ( ${HYBRIDT_tests_to_be_run[*]} )" + Print_Debug 'Selected tests: ' --emph "( ${HYBRIDT_tests_to_be_run[*]} )" } function __static__Set_Tests_To_Be_Run_Using_Globbing() @@ -162,7 +163,7 @@ function __static__Set_Tests_To_Be_Run_Using_Globbing() "No test name found matching \"$1\" globbing pattern! Use"\ "the '-t' option without value to get a list of available tests." fi - Print_Debug "Selected tests: ( ${HYBRIDT_tests_to_be_run[*]} )" + Print_Debug 'Selected tests: ' --emph "( ${HYBRIDT_tests_to_be_run[*]} )" } function __static__Print_List_Of_Tests() diff --git a/tests/tests_runner b/tests/tests_runner index 579f494..39b67a2 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -96,7 +96,7 @@ function Prepare_Test_Environment() local postfix postfix=$(date +'%Y-%m-%d_%H%M%S') if [[ -d "${HYBRIDT_folder_to_run_tests}" ]]; then - Print_Warning "Found \"${HYBRIDT_folder_to_run_tests}\", renaming it!\n" + Print_Warning 'Found ' --emph "${HYBRIDT_folder_to_run_tests}" ', renaming it!\n' mv "${HYBRIDT_folder_to_run_tests}"\ "${HYBRIDT_folder_to_run_tests}_${postfix}" || exit ${HYBRID_fatal_builtin} fi @@ -106,7 +106,7 @@ function Prepare_Test_Environment() function Run_Tests() { if [[ ${HYBRIDT_report_level} -eq 3 ]]; then - Print_Info "Running ${#HYBRIDT_tests_to_be_run[@]} test(s):\n" + Print_Info 'Running ' --emph "${#HYBRIDT_tests_to_be_run[@]}" ' test(s):\n' fi local test_name for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do @@ -196,7 +196,7 @@ function Print_Tests_Report() fi fi if [[ ${HYBRIDT_tests_failed} -ne 0 ]]; then - Print_Error "\n${HYBRIDT_tests_failed} failures were detected! Not deleting log file!" + Print_Error '\n' --emph "${HYBRIDT_tests_failed}" ' failures were detected! Not deleting log file!' else Print_Info "\nAll tests passed!" fi @@ -216,10 +216,10 @@ function Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So() elif [[ ! -e "${global_path}" ]]; then continue else - Print_Internal_And_Exit "Error in \"${FUNCNAME}\"."\ - "\"${global_path}\" seems to neither be a file nor directory, leaving it!" + Print_Internal_And_Exit 'Error in ' --emph "${FUNCNAME}" '.'\ + --emph "${global_path}" ' seems to neither be a file nor directory, leaving it!' fi - Print_Info "Removing ${label} \"${global_path}\"" + Print_Info "Removing ${label} " --emph "${global_path}" '.' if [[ -e "${global_path}" ]]; then # Redundant but safer rm -r "${global_path}" fi diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 0c23e82..ea432d4 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -15,7 +15,7 @@ function Define_Available_Tests() "${HYBRIDT_tests_folder}/"unit_tests_*.bash ) for file_to_be_sourced in "${files_to_be_sourced[@]}"; do - Print_Debug "Sourcing ${file_to_be_sourced}" + Print_Debug 'Sourcing ' --emph "${file_to_be_sourced}" source "${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done # Available tests are based on functions in this file whose names begins with "Unit_Test__" diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index 781bca0..e9cedee 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -29,7 +29,7 @@ function __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success( if [[ $? -ne 0 ]] ||\ [[ "${HYBRID_execution_mode}" != "${expected_option}" ]] ||\ [[ ${#HYBRID_command_line_options_to_parse[@]} -ne "${expected_size}" ]]; then - Print_Error "Parsing of valid execution mode '${first_option}' failed." + Print_Error 'Parsing of valid execution mode ' --emph "${first_option}" ' failed.' return 1 fi ) @@ -75,7 +75,8 @@ function __static__Test_CLO_Parsing_Missing_Value() { ( Parse_Command_Line_Options &> /dev/null ) if [[ $? -eq 0 ]]; then - Print_Error "Parsing of CLO '${HYBRID_command_line_options_to_parse[0]}' with missing value succeeded." + Print_Error 'Parsing of CLO ' --emph "${HYBRID_command_line_options_to_parse[0]}"\ + ' with missing value succeeded.' return 1 fi } @@ -84,7 +85,7 @@ function __static__Test_Single_CLO_Parsing_In_Subshell() ( Parse_Command_Line_Options if [[ $? -ne 0 ]] || [[ ${!1} != "$2" ]]; then - Print_Error 'Parsing of '${HYBRID_command_line_options_to_parse[0]}' with valid value failed.' + Print_Error 'Parsing of ' --emph "${HYBRID_command_line_options_to_parse[0]}" ' with valid value failed.' return 1 fi ) diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 1782b3a..848b233 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -179,16 +179,16 @@ function __static__Test_Section_Parsing_In_Subshell() Validate_And_Parse_Configuration_File if [[ "${#HYBRID_given_software_sections[@]}" -ne 1 ]] ||\ [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then - Print_Fatal_And_Exit "Parsing of ${section} section failed (section storing)." + Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (section storing).' fi if [[ ${HYBRID_software_executable[${section}]} != "${executable}" ]]; then - Print_Fatal_And_Exit "Parsing of ${section} section failed (software executable)." + Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (software executable).' fi if [[ ${HYBRID_software_base_config_file[${section}]} != "${input_file}" ]]; then - Print_Fatal_And_Exit 'Parsing of ${section} section failed (input file).' + Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (input file).' fi if [[ ${HYBRID_software_new_input_keys[${section}]} != "${new_keys}" ]]; then - Print_Fatal_And_Exit "Parsing of ${section} section failed (software keys)." + Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (software keys).' fi ) diff --git a/tests/unit_tests_help_for_user.bash b/tests/unit_tests_help_for_user.bash index c12b4a9..f0e1d03 100644 --- a/tests/unit_tests_help_for_user.bash +++ b/tests/unit_tests_help_for_user.bash @@ -18,7 +18,7 @@ function __static__Run_Helper_Expecting_Success() { ( Give_Required_Help ) if [[ $? -ne 0 ]]; then - Print_Error "Providing help in '${HYBRID_execution_mode}' execution mode failed." + Print_Error 'Providing help in ' --emph "${HYBRID_execution_mode}" ' execution mode failed.' return 1 fi } diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 94d0c61..2e66f37 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -23,17 +23,17 @@ function Unit_Test__utility-has-YAML-string-given-key() fi ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c' &> /dev/null ) if [[ $? -ne 0 ]] ; then - Print_Error "Existing key '{a: {b: {c:}}}' not found." + Print_Error 'Existing key ' --emph '{a: {b: {c:}}}' ' not found.' return 1 fi ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' &> /dev/null ) if [[ $? -ne 0 ]] ; then - Print_Error "Existing key '{a: {b:}}' not found." + Print_Error 'Existing key ' --emph '{a: {b:}}' ' not found.' return 1 fi ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' &> /dev/null ) if [[ $? -ne 0 ]] ; then - Print_Error "Existing key '{a:}' not found." + Print_Error 'Existing key ' --emph '{a:}' ' not found.' return 1 fi ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'nope' &> /dev/null ) @@ -123,7 +123,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() printf ' # Comment\n' > "${file_containing_one_commented_line_only}" Remove_Comments_In_File "${file_containing_one_commented_line_only}" if [[ -s "${file_containing_one_commented_line_only}" ]]; then - Print_Error "File \"${file_containing_one_commented_line_only}\" not empty." + Print_Error 'File ' --emph "${file_containing_one_commented_line_only}" ' not empty.' return 1 fi rm "${file_containing_one_commented_line_only}" @@ -133,7 +133,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() number_of_lines=$(wc -l < "${file_containing_no_comments}") Remove_Comments_In_File "${file_containing_no_comments}" if [[ $(wc -l < "${file_containing_no_comments}") -ne ${number_of_lines} ]]; then - Print_Error "Removing comments in \"${file_containing_no_comments}\" file failed." + Print_Error 'Removing comments in ' --emph "${file_containing_no_comments}" ' file failed.' return 1 fi rm "${file_containing_no_comments}" @@ -143,7 +143,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() number_of_lines=$(wc -l < "${file_containing_three_commented_lines}") Remove_Comments_In_File "${file_containing_three_commented_lines}" if (( $(wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3 )); then - Print_Error "Removing comments in \"${file_containing_three_commented_lines}\" file failed." + Print_Error 'Removing comments in ' --emph "${file_containing_three_commented_lines}" ' file failed.' return 1 fi rm "${file_containing_three_commented_lines}" @@ -152,7 +152,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() printf 'Hello %% Comment\n' > "${file_containing_one_line_with_an_inline_comment}" Remove_Comments_In_File "${file_containing_one_line_with_an_inline_comment}" '%' if [[ $(cat "${file_containing_one_line_with_an_inline_comment}") != 'Hello' ]]; then - Print_Error "Removing comments in \"${file_containing_one_line_with_an_inline_comment}\" file failed." + Print_Error 'Removing comments in ' --emph "${file_containing_one_line_with_an_inline_comment}" ' file failed.' return 1 fi rm "${file_containing_one_line_with_an_inline_comment}" @@ -271,7 +271,7 @@ function __static__Test_ANSI_Code_Removal() Ensure_That_Given_Variables_Are_Set_And_Not_Empty input expected_output output=$(Strip_ANSI_Color_Codes_From_String "${input}") if [[ "${output}" != "${expected_output}" ]]; then - Print_Error "Removing format code from '${expected_output}' failed." + Print_Error 'Removing format code from ' --emph "${expected_output}" ' failed.' return 1 fi } From dfe02f2b45f3ba07186120e8e55245b5b888eb09 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 11:22:09 +0200 Subject: [PATCH 129/549] Change logger fd to 9 and do other minor improvements --- bash/logger.bash | 5 +++-- bash/source_codebase_files.bash | 3 ++- tests/tests_runner | 4 ++-- tests/unit_tests.bash | 8 ++++---- tests/unit_tests_version.bash | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/bash/logger.bash b/bash/logger.bash index 6203308..f3b58bd 100644 --- a/bash/logger.bash +++ b/bash/logger.bash @@ -7,8 +7,9 @@ # #=================================================== # -# This file has been taken from the BashLogger project (v0.2) and, as -# requested, its original license header is reported here below. +# This file has been taken from the BashLogger project (v0.2) +# https://github.com/AxelKrypton/BashLogger +# and, as requested, its original license header is reported here below. # #---------------------------------------------------------------------------------------- # diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 517967c..482bc85 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -12,8 +12,9 @@ function __static__Source_Codebase_Files() local list_of_files file_to_be_sourced # Source error codes and fail with hard-coded generic error source "${HYBRID_top_level_path}/bash/error_codes.bash" || exit 1 + # Source logger using fd 9 (not too small and still smaller than 10 as bash manual suggests) source "${HYBRID_top_level_path}/bash/logger.bash"\ - --fd 42 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} + --fd 9 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} list_of_files=( 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' diff --git a/tests/tests_runner b/tests/tests_runner index 39b67a2..c5caca9 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -72,7 +72,7 @@ function Source_Needed_Files() { source "${HYBRIDT_repository_top_level_path}/bash/error_codes.bash" || exit 1 source "${HYBRIDT_repository_top_level_path}/bash/logger.bash"\ - --fd 42 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} + --fd 9 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} local -r files_to_be_sourced=( "${HYBRIDT_repository_top_level_path}/bash/system_requirements.bash" "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" @@ -217,7 +217,7 @@ function Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So() continue else Print_Internal_And_Exit 'Error in ' --emph "${FUNCNAME}" '.'\ - --emph "${global_path}" ' seems to neither be a file nor directory, leaving it!' + --emph "${global_path}" ' seems to neither be a file nor a directory, leaving it!' fi Print_Info "Removing ${label} " --emph "${global_path}" '.' if [[ -e "${global_path}" ]]; then # Redundant but safer diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index ea432d4..8eeabca 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -36,18 +36,18 @@ function Make_Test_Preliminary_Operations() # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 - } &>> "${HYBRIDT_log_file}" 42>&1 # The fd 42 is used by the logger. + } &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger. } function Run_Test() { - Unit_Test__$1 &>> "${HYBRIDT_log_file}" 42>&1 # The fd 42 is used by the logger. + Unit_Test__$1 &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger. } function Clean_Tests_Environment_For_Following_Test() { - # The fd 42 is used by the logger. - Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 42>&1 + # The fd 9 is used by the logger. + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 9>&1 } #======================================================================================================================= diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash index 1c8ee5c..0778682 100644 --- a/tests/unit_tests_version.bash +++ b/tests/unit_tests_version.bash @@ -24,7 +24,7 @@ function Unit_Test__version() return 1 fi if hash git &> /dev/null; then - std_output=$(Print_Software_Version 42>&1) # We want to capture here a logger message that goes to fd 42 + std_output=$(Print_Software_Version 9>&1) # We want to capture here a logger message that goes to fd 9 if [[ $? -ne 0 || $(grep -c "${HYBRID_codebase_version}" <<< "${std_output}") -eq 0 ]] ; then Print_Error "Version printing with git failed." return 1 From b761f93cc149e8ef41c992ccd4ea6f403a39bde5 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 09:50:20 +0200 Subject: [PATCH 130/549] Fix bug in utility function when used in nounset mode --- bash/utility_functions.bash | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 49f273e..c38187b 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -223,9 +223,11 @@ function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { continue fi else + set +u # Here variable_name might be unset! Do not exit if so if [[ "${!variable_name}" != '' ]]; then continue fi + set -u fi Print_Internal_And_Exit\ 'Variable ' --emph "${variable_name}" ' unset or empty in function ' --emph "${FUNCNAME[1]}" '.' From 841459b6177aebb5a2785df772509c4b56633630 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 09:50:53 +0200 Subject: [PATCH 131/549] Make function no-op when either used program isn't available --- bash/utility_functions.bash | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index c38187b..7083e8c 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -236,6 +236,12 @@ function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { function Make_Functions_Defined_In_This_File_Readonly() { + # Make this function a no-op if sed or grep are not available, so that + # the system-requirements check does not weirdly fail in those cases just + # because this function is called when sourcing the files at the end. + if ! hash sed &> /dev/null || ! hash grep &> /dev/null; then + return + fi # Here we assume all functions are defined with the same stile, # including empty parentheses and the braces on new lines! I.e. # From 38732943e2053e7a2a766e5a3681778f14e1dbc7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 09:53:32 +0200 Subject: [PATCH 132/549] Fix bug in system requirements when run in nounset mode The auxiliary system information array uses strings with '|' as field separator and word splitting on this character was used to assign an array with the fields as entries. However, when fields are empty this leads to access later not-existing array entries and this fails when bash is in nounset mode. Now a '---' is used for all previously empty fields. --- bash/system_requirements.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index a591f8b..a0f4f7f 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -138,10 +138,10 @@ function __static__Analyze_System_Properties() if __static__Try_Find_Requirement "${program}"; then system_information[${program}]='found|' else - system_information[${program}]='---||' # Empty following fields + system_information[${program}]='---|---|---' continue fi - if ! __static__Try_Find_Version "${program}"; then + if ! __static__Try_Find_Version "${program}"; then # This writes to system_information continue fi if __static__Check_Version_Suffices "${program}"; then @@ -223,7 +223,7 @@ function __static__Try_Find_Version() if [[ ${found_version} =~ ^${HYBRID_version_regex}$ ]]; then system_information["$1"]+="${found_version}|" else - system_information["$1"]+='---|' + system_information["$1"]+='---|---' return 1 fi } From 34c7d5690166146a99db6c06cbe5733853443798 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 09:56:51 +0200 Subject: [PATCH 133/549] Fix tput usage in system requirements As tput relies on TERM environment variable which might be unset, we decided to explicitly tell tput which terminal is used with the -T option and passing ${TERM:-xterm} to it. However, it was not noticed that the -T option makes tput not deduce the correct size of the terminal and hence we do not have what we wish in the case when the environment is set as it should. A new approach is used now. --- bash/system_requirements.bash | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index a0f4f7f..7332572 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -115,12 +115,13 @@ function Check_System_Requirements_And_Make_Report() )" ) done - # Because of coloured output, we cannot use a tool like 'column' here and - # we manually determine how many columns to use. - local -r single_field_length=15 - # NOTE: tput needs the TERM environment variable to be set. Here, since that is a - # requirement, we use 'xterm' in case that environment variable was not set. - local -r num_cols=$(( $(tput -T ${TERM:-xterm} cols) * 4 / 5 / single_field_length )) + # Because of coloured output, we cannot use a tool like 'column' here to format output + # and we manually determine how many columns to use. Furthermore tput needs the TERM + # environment variable to be set and, as tput is a requirement, we cannot rely on it + # here. Although in some cases this might fail, we refresh and use COLUMNS variable + # here (see https://stackoverflow.com/a/48016366/14967071 for more information). + cat /dev/null # Refresh LINES and COLUMNS + local -r num_cols=$(( COLUMNS / 2 / single_field_length )) local index printf_descriptor printf_descriptor="%${single_field_length}s" # At least one column for ((index=1; index Date: Mon, 26 Jun 2023 09:59:58 +0200 Subject: [PATCH 134/549] Make code more robust for future new requirements The function trying to find a program version needed to be changed for each new possible requirement and, actually, was breaking in a very confusing way when the needed change was forgotten (the global array was not written to before returning). Now we made the default case to try finding the version an internal error so that the developer understands what is missing. --- bash/system_requirements.bash | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 7332572..1e3bc83 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -198,16 +198,13 @@ function __static__Try_Find_Version() Ensure_That_Given_Variables_Are_Set system_information local found_version case "$1" in + awk | sed ) + found_version=$($1 --version | head -n1 | grep -oE "${HYBRID_version_regex}" | head -n1) + ;; bash ) found_version="${BASH_VERSINFO[@]:0:3}" found_version="${found_version// /.}" ;; - awk ) - found_version=$(awk --version | head -n1 | grep -oE "${HYBRID_version_regex}" | head -n1) - ;; - sed ) - found_version=$(sed --version | head -n1 | grep -oE "${HYBRID_version_regex}" | head -n1) - ;; tput ) found_version=$(tput -V | grep -oE "${HYBRID_version_regex}" | cut -d'.' -f1,2) ;; @@ -218,7 +215,7 @@ function __static__Try_Find_Version() grep -oE "${HYBRID_version_regex}") ;; *) - return 1 + Print_Internal_And_Exit 'Version finding for ' --emph "$1" ' to be added!' ;; esac if [[ ${found_version} =~ ^${HYBRID_version_regex}$ ]]; then From 7bb254e4c0d9c4da0de59dee6025025e573d4d89 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 10:09:40 +0200 Subject: [PATCH 135/549] Improve system requirements check --- bash/system_requirements.bash | 45 ++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 1e3bc83..0e8b513 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -13,12 +13,20 @@ function __static__Declare_System_Requirements() if ! declare -p HYBRID_versions_requirements &> /dev/null; then readonly HYBRID_version_regex='[0-9](.[0-9]+)*' declare -rgA HYBRID_versions_requirements=( - [bash]='4.4' [awk]='4.1' + [bash]='4.4' [sed]='4.2.1' [tput]='5.7' [yq]='4.18.1' ) + declare -rga HYBRID_programs_just_required=( + cat + column + cut + grep + head + tail + ) declare -rga HYBRID_gnu_programs_required=( awk sed sort wc ) declare -rga HYBRID_env_variables_required=( TERM ) fi @@ -35,6 +43,9 @@ function Check_System_Requirements() # contains whether the command was found, its version and whether the version # meets the requirement or not. A '|' is used to separate the fields in the # string and '---' is used to indicate a negative outcome in the field. + # The same array is used to store the availability of programs that are just + # required, without any version requirement. For these, only the found field + # is stored (either 'found' or '---'). # The same array is used to store the availability of GNU tools. For these, # the key is prefixed by 'GNU-' and the content has a first "field" that is # 'found' or '---' and a second one that is either 'OK' or '---' to indicate @@ -91,7 +102,7 @@ function Check_System_Requirements() function Check_System_Requirements_And_Make_Report() { __static__Declare_System_Requirements - local program name gnu_env_report=() + local program name system_report=() declare -A system_information __static__Analyze_System_Properties printf "\e[1m System requirements overview:\e[0m\n\n" @@ -99,8 +110,18 @@ function Check_System_Requirements_And_Make_Report() __static__Print_Requirement_Version_Report_Line "${program}" done | sort -b -k3 # the third column is that containing the program name printf '\n' + # This variable is used to prepare the report correctly formatted + local -r single_field_length=15 + for name in "${HYBRID_programs_just_required[@]}"; do + system_report+=( + "$(__static__Get_Single_Tick_Cross_Requirement_Report\ + "PROG ${name}"\ + "${system_information[${name}]}" + )" + ) + done for program in "${HYBRID_gnu_programs_required[@]}"; do - gnu_env_report+=( + system_report+=( "$(__static__Get_Single_Tick_Cross_Requirement_Report\ "GNU ${program}"\ "$(cut -d'|' -f2 <<< "${system_information["GNU-${program}"]}")" @@ -108,7 +129,7 @@ function Check_System_Requirements_And_Make_Report() ) done for name in "${HYBRID_env_variables_required[@]}"; do - gnu_env_report+=( + system_report+=( "$(__static__Get_Single_Tick_Cross_Requirement_Report\ "ENV ${name}"\ "${system_information[${name}]}" @@ -127,7 +148,7 @@ function Check_System_Requirements_And_Make_Report() for ((index=1; index Date: Mon, 26 Jun 2023 10:28:45 +0200 Subject: [PATCH 136/549] Mark all codebase functions as readonly --- bash/Afterburner_functionality.bash | 3 +++ bash/Hydro_functionality.bash | 3 +++ bash/IC_functionality.bash | 3 +++ bash/Sampler_functionality.bash | 3 +++ bash/command_line_parsers/helper.bash | 3 +++ bash/command_line_parsers/main_parser.bash | 3 +++ bash/configuration_parser.bash | 3 +++ bash/dispatch_functions.bash | 3 +++ bash/global_variables.bash | 3 +++ bash/sanity_checks.bash | 3 +++ bash/software_input_functionality.bash | 3 +++ bash/source_codebase_files.bash | 7 ++++--- bash/system_requirements.bash | 3 +++ bash/version.bash | 3 +++ tests/tests_runner | 2 +- 15 files changed, 44 insertions(+), 4 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 7910ab8..92cc897 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -21,3 +21,6 @@ function Run_Software_Afterburner() { Print_Not_Implemented_Function_Error } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 102cd7f..011891c 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -21,3 +21,6 @@ function Run_Software_Hydro() { Print_Not_Implemented_Function_Error } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index a3a8c98..4c64e25 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -46,3 +46,6 @@ function Run_Software_IC() '-n' \ >> "${ic_terminal_output}" } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 3a2c40d..2f4575a 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -21,3 +21,6 @@ function Run_Software_Sampler() { Print_Not_Implemented_Function_Error } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index 4f06146..62385ad 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -117,3 +117,6 @@ function __static__Print_Command_Line_Option_Help() done printf "%${left_column_length}sDefault: ${default_value_color}${default_value}${default_text}\n" '' } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index d1cad8c..4aa4e49 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -73,3 +73,6 @@ function Parse_Command_Line_Options() esac done } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 2c77b1b..610468c 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -176,3 +176,6 @@ function __static__YAML_section_must_be_empty() --emph "\n${yaml_section}\n" fi } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/dispatch_functions.bash b/bash/dispatch_functions.bash index 1884dfa..fffb47b 100644 --- a/bash/dispatch_functions.bash +++ b/bash/dispatch_functions.bash @@ -21,3 +21,6 @@ function Run_Software() { Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/global_variables.bash b/bash/global_variables.bash index ac0db5d..c3a81e7 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -88,3 +88,6 @@ function Define_Further_Global_Variables() [Afterburner]='' ) } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 7e0e51f..84c0b22 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -36,3 +36,6 @@ function __static__Ensure_Executable_Exists() 'The executable file for the ' --emph "${label}" ' run is not executable.' fi } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 028daf3..507bb3a 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -74,3 +74,6 @@ function __static__Replace_Keys_Into_Txt_File() __static__Replace_Keys_Into_YAML_File awk -i inplace 'BEGIN{FS=": "}{printf "%-20s%s\n", $1, $2}' "${base_input_file}" } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 482bc85..1ccc4e6 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -15,6 +15,9 @@ function __static__Source_Codebase_Files() # Source logger using fd 9 (not too small and still smaller than 10 as bash manual suggests) source "${HYBRID_top_level_path}/bash/logger.bash"\ --fd 9 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} + # Source utility functions to be able to mark all functions as readonly + # when sourcing the codebase files (otherwise this would not work) + source "${HYBRID_top_level_path}/bash/utility_functions.bash" || exit ${HYBRID_fatal_builtin} list_of_files=( 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' @@ -23,7 +26,6 @@ function __static__Source_Codebase_Files() 'dispatch_functions.bash' 'global_variables.bash' 'system_requirements.bash' - 'utility_functions.bash' 'version.bash' ) for file_to_be_sourced in "${list_of_files[@]}"; do @@ -34,6 +36,5 @@ function __static__Source_Codebase_Files() # Call the function above and source the codebase files when this script is sourced if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then __static__Source_Codebase_Files + Make_Functions_Defined_In_This_File_Readonly fi - -Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 0e8b513..17d58bb 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -331,3 +331,6 @@ function __static__Get_Single_Tick_Cross_Requirement_Report() fi printf "${line}${default}" } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/version.bash b/bash/version.bash index 0fc6c55..506d699 100644 --- a/bash/version.bash +++ b/bash/version.bash @@ -68,3 +68,6 @@ function __static__Is_Git_Version_Older_Than() return 0 fi } + + +Make_Functions_Defined_In_This_File_Readonly diff --git a/tests/tests_runner b/tests/tests_runner index c5caca9..94948c3 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -73,9 +73,9 @@ function Source_Needed_Files() source "${HYBRIDT_repository_top_level_path}/bash/error_codes.bash" || exit 1 source "${HYBRIDT_repository_top_level_path}/bash/logger.bash"\ --fd 9 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} + source "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" || exit ${HYBRID_fatal_builtin} local -r files_to_be_sourced=( "${HYBRIDT_repository_top_level_path}/bash/system_requirements.bash" - "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" "${HYBRIDT_tests_folder}/command_line_parser_for_tests.bash" ) local file From 7e73af5090648ec8c228925710e8de38592aa641 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 15:23:34 +0200 Subject: [PATCH 137/549] Avoid using system requirements when using them The code became more complicated, but it makes sense to give a clean report to the user and so we should not use system requirements that we check for. Often a bash replacement is easy to implement. However, for grep this might become too long and tricky and we decided for the moment to work around this. --- bash/system_requirements.bash | 134 ++++++++++++++++++---- tests/unit_tests_system_requirements.bash | 2 +- 2 files changed, 112 insertions(+), 24 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 17d58bb..33ee337 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -35,7 +35,7 @@ function __static__Declare_System_Requirements() function Check_System_Requirements() { __static__Declare_System_Requirements - local program requirements_present min_version version_found name + local program requirements_present requirements_present=0 # NOTE: The following associative array will be used to store system information # and since bash does not support arrays entries in associative arrays, then @@ -52,10 +52,15 @@ function Check_System_Requirements() # whether the GNU version of the command is in use or not. # Finally, the same array is used for environment variables and, in this case, # the content is either 'OK' or '---'. + # + # ATTENTION: The code relies on the "fields" in system_information elements not + # containing spaces. This assumption might be dropped, but that array + # is an implementation detail and we have full control on its content. declare -A system_information __static__Analyze_System_Properties for program in "${HYBRID_gnu_programs_required[@]}"; do - if [[ $(cut -d'|' -f2 <<< "${system_information[GNU-${program}]}") = '---' ]]; then + local is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) + if [[ ${is_gnu} = '---' ]]; then Print_Error --emph "${program}" ' either not found or non-GNU version in use.'\ 'Please, ensure that ' --emph "${program}" ' is installed and in use.' requirements_present=1 @@ -65,20 +70,23 @@ function Check_System_Requirements() Print_Fatal_And_Exit 'The GNU version of the above programs is needed.' fi for program in "${!HYBRID_versions_requirements[@]}"; do + local min_version program_found version_found version_ok min_version=${HYBRID_versions_requirements["${program}"]} - if [[ $(cut -d'|' -f1 <<< "${system_information[${program}]}") = '---' ]]; then + program_found=$(__static__Get_Field_In_System_Information_String "${program}" 0) + version_found=$(__static__Get_Field_In_System_Information_String "${program}" 1) + version_ok=$( __static__Get_Field_In_System_Information_String "${program}" 2) + if [[ "${program_found}" = '---' ]]; then Print_Error --emph "${program}" ' command not found! Minimum version '\ --emph "${min_version}" ' is required.' requirements_present=1 continue fi - version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") if [[ ${version_found} = '---' ]]; then Print_Warning 'Unable to find version of ' --emph "${program}" ', skipping version check!'\ 'Please ensure that current version is at least ' --emph "${min_version}" '.' continue fi - if [[ $(cut -d'|' -f3 <<< "${system_information[${program}]}") = '---' ]]; then + if [[ "${version_ok}" = '---' ]]; then Print_Error --emph "${program}" ' version ' --emph "${version_found}"\ ' found, but version ' --emph "${min_version}" ' is required.' requirements_present=1 @@ -87,6 +95,7 @@ function Check_System_Requirements() if [[ ${requirements_present} -ne 0 ]]; then Print_Fatal_And_Exit 'Please install (maybe locally) the required versions of the above programs.' fi + local name for name in "${HYBRID_env_variables_required[@]}"; do if [[ ${system_information[${name}]} = '---' ]]; then Print_Error --emph "${name}" ' environment variable either unset or empty.'\ @@ -102,7 +111,7 @@ function Check_System_Requirements() function Check_System_Requirements_And_Make_Report() { __static__Declare_System_Requirements - local program name system_report=() + local program name is_gnu system_report=() declare -A system_information __static__Analyze_System_Properties printf "\e[1m System requirements overview:\e[0m\n\n" @@ -121,10 +130,11 @@ function Check_System_Requirements_And_Make_Report() ) done for program in "${HYBRID_gnu_programs_required[@]}"; do + is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) system_report+=( "$(__static__Get_Single_Tick_Cross_Requirement_Report\ "GNU ${program}"\ - "$(cut -d'|' -f2 <<< "${system_information["GNU-${program}"]}")" + "${is_gnu}" )" ) done @@ -141,7 +151,8 @@ function Check_System_Requirements_And_Make_Report() # environment variable to be set and, as tput is a requirement, we cannot rely on it # here. Although in some cases this might fail, we refresh and use COLUMNS variable # here (see https://stackoverflow.com/a/48016366/14967071 for more information). - cat /dev/null # Refresh LINES and COLUMNS + shopt -s checkwinsize # Do not assume it is on (as it usually is) + ( : ) # Refresh LINES and COLUMNS, this happens when a child process exits local -r num_cols=$(( COLUMNS / 2 / single_field_length )) local index printf_descriptor printf_descriptor="%${single_field_length}s" # At least one column @@ -186,11 +197,17 @@ function __static__Analyze_System_Properties() system_information["GNU-${program}"]='---|---' continue fi - if __static__Is_Gnu_Version_In_Use "${program}"; then - system_information["GNU-${program}"]+='OK' - else - system_information["GNU-${program}"]+='---' - fi + # Needed handling of command exit code to be done in this form because of errexit mode + local return_code=0 + __static__Is_Gnu_Version_In_Use "${program}" || return_code=$? + case "${return_code}" in + 0) + system_information["GNU-${program}"]+='OK' ;; + 2) + system_information["GNU-${program}"]+='?' ;; + *) + system_information["GNU-${program}"]+='---' ;; + esac done for name in "${HYBRID_env_variables_required[@]}"; do ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty "${name}" &> /dev/null ) @@ -214,7 +231,9 @@ function __static__Try_Find_Requirement() function __static__Is_Gnu_Version_In_Use() { # This follows apparently common sense -> https://stackoverflow.com/a/61767587/14967071 - if [[ $("$1" --version | grep -c 'GNU') -gt 0 ]]; then + if ! hash grep &> /dev/null || ! "$1" --version &> /dev/null; then + return 2 + elif [[ $("$1" --version | grep -c 'GNU') -gt 0 ]]; then return 0 else return 1 @@ -223,18 +242,27 @@ function __static__Is_Gnu_Version_In_Use() function __static__Try_Find_Version() { - Ensure_That_Given_Variables_Are_Set system_information + Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" + if ! hash grep &> /dev/null; then + system_information["$1"]+='?|---' + return 1 + fi local found_version case "$1" in awk | sed ) - found_version=$($1 --version | head -n1 | grep -oE "${HYBRID_version_regex}" | head -n1) + found_version=$($1 --version) + found_version=$(__static__Get_First_Line_From_String "${found_version}") + found_version=$(grep -oE "${HYBRID_version_regex}" <<< "${found_version}") + found_version=$(__static__Get_First_Line_From_String "${found_version}") ;; bash ) found_version="${BASH_VERSINFO[@]:0:3}" found_version="${found_version// /.}" ;; tput ) - found_version=$(tput -V | grep -oE "${HYBRID_version_regex}" | cut -d'.' -f1,2) + found_version=$(tput -V | grep -oE "${HYBRID_version_regex}") + found_version=( ${found_version//./ } ) # Use word split to separate version numbers + found_version="${found_version[0]}.${found_version[1]}" ;; yq ) # Versions before v4.30.3 do not have the 'v' prefix @@ -256,17 +284,17 @@ function __static__Try_Find_Version() function __static__Check_Version_Suffices() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information + Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" # Here we assume that the programs were found and their version, too local program version_required version_found newer_version program=$1 version_required="${HYBRID_versions_requirements[${program}]}" - version_found=$(cut -d'|' -f2 <<< "${system_information[${program}]}") + version_found=$(__static__Get_Field_In_System_Information_String "${program}" 1) # If versions are equal, we're done if [[ "${version_required}" = "${version_found}" ]]; then return 0 fi - newer_version=$(printf '%s\n%s' ${version_required} ${version_found} | sort -V | tail -n 1) + newer_version=$(__static__Get_Larger_Version ${version_required} ${version_found}) if [[ ${newer_version} = ${version_required} ]]; then return 1 else @@ -276,7 +304,7 @@ function __static__Check_Version_Suffices() function __static__Print_Requirement_Version_Report_Line() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information + Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" local -r emph_color='\e[96m'\ red='\e[91m'\ green='\e[92m'\ @@ -298,7 +326,7 @@ function __static__Print_Requirement_Version_Report_Line() "${HYBRID_versions_requirements[${program}]}") if [[ ${found} != '---' ]]; then line+=" ${text_color}System version:${default} " - if [[ ${version_found} = '---' ]]; then + if [[ ${version_found} =~ ^(---|\?)$ ]]; then line+="${yellow}Unable to recover" else if [[ ${version_ok} = '---' ]]; then @@ -315,10 +343,11 @@ function __static__Print_Requirement_Version_Report_Line() function __static__Get_Single_Tick_Cross_Requirement_Report() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_information single_field_length + Ensure_That_Given_Variables_Are_Set_And_Not_Empty single_field_length local -r emph_color='\e[96m'\ red='\e[91m'\ green='\e[92m'\ + yellow='\e[93m'\ text_color='\e[38;5;38m'\ default='\e[0m' local line name="$1" status=$2 name_string @@ -326,11 +355,70 @@ function __static__Get_Single_Tick_Cross_Requirement_Report() printf -v line " %*s${text_color}: ${default}" "${single_field_length}" "${name_string}" if [[ ${status} = '---' ]]; then line+="${red}✘" + elif [[ ${status} = '?' ]]; then + line+="${yellow}\e[1m?" else line+="${green}✔︎" fi printf "${line}${default}" } +function __static__Get_Field_In_System_Information_String() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" + local tmp_array=( ${system_information[$1]//|/ } ) # Unquoted to let word splitting act + printf '%s' "${tmp_array[$2]}" +} + +# This is basically a partial bash implementation of head, which we want avoid +# using in this file as it is a requirement that we want to check +function __static__Get_First_Line_From_String() +{ + while IFS= read -r line; do + printf "${line}" + return + done < <(printf '%s\n' "$1") + # The \n in printf is important to avoid skipping the last line (which might be the only input) +} + +function __static__Get_Larger_Version() +{ + local v1=$1 v2=$2 + if [[ ! "${v1}.${v2}" =~ ${HYBRID_version_regex} ]]; then + Print_Internal_And_Exit 'Wrong arguments passed to ' --emph "${FUNCNAME}" '.' + fi + # Ensure versions are of the same length to make following algorithm work + if [[ ${#v1} -lt ${#v2} ]]; then + declare -n shorter_array=v1 + declare -n longer_array=v2 + else + declare -n shorter_array=v2 + declare -n longer_array=v1 + fi + while [[ ${#v1[@]} -ne ${#v2[@]} ]]; do + shorter_array+='.0' # Add zeroes to shorter string + done + # If versions are equal, we're done + if [[ "${v2}" = "${v1}" ]]; then + printf "${v1}" + fi + # Split version strings into array of numbers replacing '.' by ' ' and let word splitting do the split + local v{1,2}_array index + v1_array=( ${v1//./ } ) + v2_array=( ${v2//./ } ) + # Now version arrays have same number of entries, compare them + for index in ${!v1_array[@]}; do + if [[ "${v1_array[index]}" -eq "${v2_array[index]}" ]]; then + continue + elif [[ "${v1_array[index]}" -lt "${v2_array[index]}" ]]; then + printf "$2" # Print input version, unchanged + return + else + printf "$1" # Print input version, unchanged + return + fi + done +} + Make_Functions_Defined_In_This_File_Readonly diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 7b4762c..2ac2e57 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -67,7 +67,7 @@ function Unit_Test__system-requirements() return 1 fi gnu='BSD' - awk_version=4.0.9 + awk_version=4.1.0 sed_version=4.2.0 tput_version='' yq_version=3.9.98 From 92ea9cfe90ea72d9a3be6929470961b9465395f1 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 17:40:06 +0200 Subject: [PATCH 138/549] Fix usage of sort in main helper testing before using it Both in the sections descriptions and in the requirements report, sort was used to display content in an alphabetical order. Now this is done only if sort is available. --- bash/command_line_parsers/helper.bash | 21 ++++++++++++++++----- bash/system_requirements.bash | 15 ++++++++++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index 62385ad..a68a6dd 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -27,6 +27,8 @@ function Give_Required_Help() function __static__Print_Main_Help_Message() { + # NOTE: This function is thought to be run in ANY user system and it might be + # that handler prerequisites are missing. Hence, possibly only bash should be used. declare -A section_headers auxiliary_modes_description execution_modes_description section_headers=( [auxiliary_modes_description]='Auxiliary modes for help, information or setup' @@ -78,17 +80,26 @@ function __static__Print_Handler_Header_And_Usage_Synopsis() function __static__Print_Modes_Description() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty section_headers "${!section_headers[@]}" - local section mode + local section mode section_string printf '\e[38;5;38m %s\e[0m\n'\ 'Here in the following you find an overview of the existing execution modes.' for section in "${!section_headers[@]}"; do printf "\n \e[93m${section_headers[${section}]}\e[0m\n" declare -n list_of_modes="${section}" + section_string='' for mode in "${!list_of_modes[@]}"; do - printf '\e[38;5;85m%15s \e[96m%s\e[0m\n'\ - "${mode}"\ - "${list_of_modes[${mode}]}" - done | sort --ignore-leading-blanks + # Remember that $(...) strip trailing '\n' -> Add new-line manually to the string + section_string+="$(printf '\e[38;5;85m%15s \e[96m%s\e[0m'\ + "${mode}"\ + "${list_of_modes[${mode}]}")"$'\n' + done + if hash sort &> /dev/null; then + # Remember that the 'here-string' adds a newline to the string when + # feeding it into the command -> get rid of it here + sort --ignore-leading-blanks <<< "${section_string%?}" + else + printf "%s" "${section_string}" + fi done printf '\n\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n\n'\ 'Use' '--help' 'after each non auxiliary mode to get further information about it.' diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 33ee337..04cce6f 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -111,13 +111,22 @@ function Check_System_Requirements() function Check_System_Requirements_And_Make_Report() { __static__Declare_System_Requirements - local program name is_gnu system_report=() + local program report_string name is_gnu system_report=() declare -A system_information __static__Analyze_System_Properties printf "\e[1m System requirements overview:\e[0m\n\n" + # NOTE: sort might not be available, hence put report in string and then optionally sort it + report_string='' for program in "${!HYBRID_versions_requirements[@]}"; do - __static__Print_Requirement_Version_Report_Line "${program}" - done | sort -b -k3 # the third column is that containing the program name + report_string+=$(__static__Print_Requirement_Version_Report_Line "${program}")$'\n' + done + if hash sort &> /dev/null; then + # The third column is that containing the program name; remember that the + # 'here-string' adds a newline to the string when feeding it into the command + sort -b -k3 <<< "${report_string%?}" + else + printf '%s' "${report_string}" + fi printf '\n' # This variable is used to prepare the report correctly formatted local -r single_field_length=15 From d7ea9fb33e15ff6f6d40d67dab24eca82b350794 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 18:35:28 +0200 Subject: [PATCH 139/549] Add refinement when grep is missing plus some debug output --- bash/system_requirements.bash | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 04cce6f..34fdac0 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -114,6 +114,9 @@ function Check_System_Requirements_And_Make_Report() local program report_string name is_gnu system_report=() declare -A system_information __static__Analyze_System_Properties + for program in "${!system_information[@]}"; do + Print_Debug "${program} -> ${system_information[${program}]}" + done printf "\e[1m System requirements overview:\e[0m\n\n" # NOTE: sort might not be available, hence put report in string and then optionally sort it report_string='' @@ -252,7 +255,7 @@ function __static__Is_Gnu_Version_In_Use() function __static__Try_Find_Version() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" - if ! hash grep &> /dev/null; then + if ! hash grep &> /dev/null && [[ $1 != 'bash' ]]; then system_information["$1"]+='?|---' return 1 fi From bda371b65f4f7a509bb6dbb46680d4a7319b192f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 26 Jun 2023 19:37:28 +0200 Subject: [PATCH 140/549] Add warning about system requirements check implementation --- bash/system_requirements.bash | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 34fdac0..c5efc0d 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -7,6 +7,12 @@ # #=================================================== +# ATTENTION: The code in this file might look here and there a bit "strange" and not +# what one would write at first. However, this is due to the fact that +# we want to check the availability of many system requirements without +# using them, otherwise the output for those users missing features would +# be more confusing than helpful. Please, keep this in mind if you are +# going to modify this file and/or change requirements. function __static__Declare_System_Requirements() { From 708038c9724bd4f4ffbed9cad8ee283cb98590bb Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 27 Jun 2023 14:32:15 +0200 Subject: [PATCH 141/549] Remove unused variable and fix check in strict bash mode --- bash/system_requirements.bash | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index c5efc0d..8d22a7f 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -183,9 +183,8 @@ function Check_System_Requirements_And_Make_Report() function __static__Analyze_System_Properties() { Ensure_That_Given_Variables_Are_Set system_information - local program name + local program name return_code for program in "${!HYBRID_versions_requirements[@]}"; do - min_version=${HYBRID_versions_requirements["${program}"]} if __static__Try_Find_Requirement "${program}"; then system_information[${program}]='found|' else @@ -215,8 +214,8 @@ function __static__Analyze_System_Properties() system_information["GNU-${program}"]='---|---' continue fi - # Needed handling of command exit code to be done in this form because of errexit mode - local return_code=0 + # Handling of function exit code must be done in this form because of errexit mode + return_code=0 __static__Is_Gnu_Version_In_Use "${program}" || return_code=$? case "${return_code}" in 0) @@ -228,8 +227,11 @@ function __static__Analyze_System_Properties() esac done for name in "${HYBRID_env_variables_required[@]}"; do - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty "${name}" &> /dev/null ) - if [[ $? -eq 0 ]]; then + # Handling of function exit code must be done in this form because of errexit mode; + # the subshell is needed because the function exits when the variable is unset or empty + return_code=0 + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty "${name}" &> /dev/null ) || return_code=$? + if [[ ${return_code} -eq 0 ]]; then system_information["${name}"]='OK' else system_information["${name}"]='---' From bddd2b6931c02f23ff61bbab1cd55c71986f045e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 27 Jun 2023 15:13:53 +0200 Subject: [PATCH 142/549] Apply IOSP principle to system requirements New functions have been introduced to better structure the file and the auxiliary function have been structured to serve different entry points to the reader. --- bash/system_requirements.bash | 325 ++++++++++++++++++++-------------- 1 file changed, 188 insertions(+), 137 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 8d22a7f..90919fb 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -16,7 +16,7 @@ function __static__Declare_System_Requirements() { - if ! declare -p HYBRID_versions_requirements &> /dev/null; then + if ! declare -p HYBRID_version_regex &> /dev/null; then readonly HYBRID_version_regex='[0-9](.[0-9]+)*' declare -rgA HYBRID_versions_requirements=( [awk]='4.1' @@ -41,8 +41,6 @@ function __static__Declare_System_Requirements() function Check_System_Requirements() { __static__Declare_System_Requirements - local program requirements_present - requirements_present=0 # NOTE: The following associative array will be used to store system information # and since bash does not support arrays entries in associative arrays, then # just strings with information will be used. Each entry of this array @@ -64,19 +62,112 @@ function Check_System_Requirements() # is an implementation detail and we have full control on its content. declare -A system_information __static__Analyze_System_Properties + __static__Exit_If_Some_GNU_Requirement_Is_Missing + __static__Exit_If_Minimum_Versions_Are_Not_Available + __static__Exit_If_Some_Needed_Environment_Variable_Is_Missing +} + +function Check_System_Requirements_And_Make_Report() +{ + __static__Declare_System_Requirements + local system_report=() + local -r single_field_length=15 # This variable is used to prepare the report correctly formatted + declare -A system_information # Same use of this variable as in 'Check_System_Requirements' function + __static__Analyze_System_Properties + __static__Print_Report_Title + __static__Print_Report_Of_Programs_With_Minimum_version + __static__Prepare_Binary_Report_Array + __static__Print_Formatted_Binary_Report +} + +#=================================================================================================== +# First level of Utility functions for functionality above + +function __static__Analyze_System_Properties() +{ + Ensure_That_Given_Variables_Are_Set system_information + local program name return_code + for program in "${!HYBRID_versions_requirements[@]}"; do + if __static__Try_Find_Requirement "${program}"; then + system_information[${program}]='found|' + else + system_information[${program}]='---|---|---' + continue + fi + if ! __static__Try_Find_Version "${program}"; then # This writes to system_information + continue + fi + if __static__Check_Version_Suffices "${program}"; then + system_information[${program}]+='OK' + else + system_information[${program}]+='---' + fi + done + for program in "${HYBRID_programs_just_required[@]}"; do + if __static__Try_Find_Requirement "${program}"; then + system_information["${program}"]='found' + else + system_information["${program}"]='---' + fi + done for program in "${HYBRID_gnu_programs_required[@]}"; do - local is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) + if __static__Try_Find_Requirement "${program}"; then + system_information["GNU-${program}"]='found|' + else + system_information["GNU-${program}"]='---|---' + continue + fi + # Handling of function exit code must be done in this form because of errexit mode + return_code=0 + __static__Is_Gnu_Version_In_Use "${program}" || return_code=$? + case "${return_code}" in + 0) + system_information["GNU-${program}"]+='OK' ;; + 2) + system_information["GNU-${program}"]+='?' ;; + *) + system_information["GNU-${program}"]+='---' ;; + esac + done + for name in "${HYBRID_env_variables_required[@]}"; do + # Handling of function exit code must be done in this form because of errexit mode; + # the subshell is needed because the function exits when the variable is unset or empty + return_code=0 + ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty "${name}" &> /dev/null ) || return_code=$? + if [[ ${return_code} -eq 0 ]]; then + system_information["${name}"]='OK' + else + system_information["${name}"]='---' + fi + done + for program in "${!system_information[@]}"; do + Print_Debug "${program} -> ${system_information[${program}]}" + done +} + +function __static__Exit_If_Some_GNU_Requirement_Is_Missing() +{ + local program errors is_gnu + errors=0 + for program in "${HYBRID_gnu_programs_required[@]}"; do + Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[GNU-${program}]" + is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) if [[ ${is_gnu} = '---' ]]; then Print_Error --emph "${program}" ' either not found or non-GNU version in use.'\ 'Please, ensure that ' --emph "${program}" ' is installed and in use.' - requirements_present=1 + (( errors++ )) || true fi done - if [[ ${requirements_present} -ne 0 ]]; then - Print_Fatal_And_Exit 'The GNU version of the above programs is needed.' + if [[ ${errors} -ne 0 ]]; then + Print_Fatal_And_Exit 'The GNU version of the above program(s) is needed.' fi +} + +function __static__Exit_If_Minimum_Versions_Are_Not_Available() +{ + local program errors min_version program_found version_found version_ok for program in "${!HYBRID_versions_requirements[@]}"; do - local min_version program_found version_found version_ok + Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[${program}]" min_version=${HYBRID_versions_requirements["${program}"]} program_found=$(__static__Get_Field_In_System_Information_String "${program}" 0) version_found=$(__static__Get_Field_In_System_Information_String "${program}" 1) @@ -84,7 +175,7 @@ function Check_System_Requirements() if [[ "${program_found}" = '---' ]]; then Print_Error --emph "${program}" ' command not found! Minimum version '\ --emph "${min_version}" ' is required.' - requirements_present=1 + (( errors++ )) || true continue fi if [[ ${version_found} = '---' ]]; then @@ -95,35 +186,38 @@ function Check_System_Requirements() if [[ "${version_ok}" = '---' ]]; then Print_Error --emph "${program}" ' version ' --emph "${version_found}"\ ' found, but version ' --emph "${min_version}" ' is required.' - requirements_present=1 + (( errors++ )) || true fi done - if [[ ${requirements_present} -ne 0 ]]; then - Print_Fatal_And_Exit 'Please install (maybe locally) the required versions of the above programs.' + if [[ ${errors} -ne 0 ]]; then + Print_Fatal_And_Exit 'Please install (maybe locally) the required versions of the above program(s).' fi - local name +} + +function __static__Exit_If_Some_Needed_Environment_Variable_Is_Missing() +{ + local name errors for name in "${HYBRID_env_variables_required[@]}"; do + Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[${name}]" if [[ ${system_information[${name}]} = '---' ]]; then Print_Error --emph "${name}" ' environment variable either unset or empty.'\ 'Please, ensure that ' --emph "${name}" ' is properly set.' - requirements_present=1 + (( errors++ )) || true fi done - if [[ ${requirements_present} -ne 0 ]]; then - Print_Fatal_And_Exit 'Some needed environment variables are not correctly set.' + if [[ ${errors} -ne 0 ]]; then + Print_Fatal_And_Exit 'Please, set the above environment variable(s) to appropriate value(s).' fi } -function Check_System_Requirements_And_Make_Report() +function __static__Print_Report_Title() { - __static__Declare_System_Requirements - local program report_string name is_gnu system_report=() - declare -A system_information - __static__Analyze_System_Properties - for program in "${!system_information[@]}"; do - Print_Debug "${program} -> ${system_information[${program}]}" - done printf "\e[1m System requirements overview:\e[0m\n\n" +} + +function __static__Print_Report_Of_Programs_With_Minimum_version() +{ + local report_string program # NOTE: sort might not be available, hence put report in string and then optionally sort it report_string='' for program in "${!HYBRID_versions_requirements[@]}"; do @@ -137,8 +231,12 @@ function Check_System_Requirements_And_Make_Report() printf '%s' "${report_string}" fi printf '\n' - # This variable is used to prepare the report correctly formatted - local -r single_field_length=15 +} + +function __static__Prepare_Binary_Report_Array() +{ + Ensure_That_Given_Variables_Are_Set system_report + Ensure_That_Given_Variables_Are_Set_And_Not_Empty single_field_length for name in "${HYBRID_programs_just_required[@]}"; do system_report+=( "$(__static__Get_Single_Tick_Cross_Requirement_Report\ @@ -164,6 +262,11 @@ function Check_System_Requirements_And_Make_Report() )" ) done +} + +function __static__Print_Formatted_Binary_Report() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty system_report # Because of coloured output, we cannot use a tool like 'column' here to format output # and we manually determine how many columns to use. Furthermore tput needs the TERM # environment variable to be set and, as tput is a requirement, we cannot rely on it @@ -180,64 +283,8 @@ function Check_System_Requirements_And_Make_Report() printf "${printf_descriptor}\n" "${system_report[@]}" } -function __static__Analyze_System_Properties() -{ - Ensure_That_Given_Variables_Are_Set system_information - local program name return_code - for program in "${!HYBRID_versions_requirements[@]}"; do - if __static__Try_Find_Requirement "${program}"; then - system_information[${program}]='found|' - else - system_information[${program}]='---|---|---' - continue - fi - if ! __static__Try_Find_Version "${program}"; then # This writes to system_information - continue - fi - if __static__Check_Version_Suffices "${program}"; then - system_information[${program}]+='OK' - else - system_information[${program}]+='---' - fi - done - for program in "${HYBRID_programs_just_required[@]}"; do - if __static__Try_Find_Requirement "${program}"; then - system_information["${program}"]='found' - else - system_information["${program}"]='---' - fi - done - for program in "${HYBRID_gnu_programs_required[@]}"; do - if __static__Try_Find_Requirement "${program}"; then - system_information["GNU-${program}"]='found|' - else - system_information["GNU-${program}"]='---|---' - continue - fi - # Handling of function exit code must be done in this form because of errexit mode - return_code=0 - __static__Is_Gnu_Version_In_Use "${program}" || return_code=$? - case "${return_code}" in - 0) - system_information["GNU-${program}"]+='OK' ;; - 2) - system_information["GNU-${program}"]+='?' ;; - *) - system_information["GNU-${program}"]+='---' ;; - esac - done - for name in "${HYBRID_env_variables_required[@]}"; do - # Handling of function exit code must be done in this form because of errexit mode; - # the subshell is needed because the function exits when the variable is unset or empty - return_code=0 - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty "${name}" &> /dev/null ) || return_code=$? - if [[ ${return_code} -eq 0 ]]; then - system_information["${name}"]='OK' - else - system_information["${name}"]='---' - fi - done -} +#=================================================================================================== +# Second level of Utility functions for functionality above function __static__Try_Find_Requirement() { @@ -248,18 +295,6 @@ function __static__Try_Find_Requirement() fi } -function __static__Is_Gnu_Version_In_Use() -{ - # This follows apparently common sense -> https://stackoverflow.com/a/61767587/14967071 - if ! hash grep &> /dev/null || ! "$1" --version &> /dev/null; then - return 2 - elif [[ $("$1" --version | grep -c 'GNU') -gt 0 ]]; then - return 0 - else - return 1 - fi -} - function __static__Try_Find_Version() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" @@ -322,6 +357,61 @@ function __static__Check_Version_Suffices() fi } +function __static__Is_Gnu_Version_In_Use() +{ + # This follows apparently common sense -> https://stackoverflow.com/a/61767587/14967071 + if ! hash grep &> /dev/null || ! "$1" --version &> /dev/null; then + return 2 + elif [[ $("$1" --version | grep -c 'GNU') -gt 0 ]]; then + return 0 + else + return 1 + fi +} + +#=================================================================================================== +# Third level of Utility functions for functionality above + +function __static__Get_Larger_Version() +{ + local v1=$1 v2=$2 + if [[ ! "${v1}.${v2}" =~ ${HYBRID_version_regex} ]]; then + Print_Internal_And_Exit 'Wrong arguments passed to ' --emph "${FUNCNAME}" '.' + fi + # Ensure versions are of the same length to make following algorithm work + if [[ ${#v1} -lt ${#v2} ]]; then + declare -n shorter_array=v1 + declare -n longer_array=v2 + else + declare -n shorter_array=v2 + declare -n longer_array=v1 + fi + while [[ ${#v1[@]} -ne ${#v2[@]} ]]; do + shorter_array+='.0' # Add zeroes to shorter string + done + # If versions are equal, we're done + if [[ "${v2}" = "${v1}" ]]; then + printf "${v1}" + return + fi + # Split version strings into array of numbers replacing '.' by ' ' and let word splitting do the split + local v{1,2}_array index + v1_array=( ${v1//./ } ) + v2_array=( ${v2//./ } ) + # Now version arrays have same number of entries, compare them + for index in ${!v1_array[@]}; do + if [[ "${v1_array[index]}" -eq "${v2_array[index]}" ]]; then + continue + elif [[ "${v1_array[index]}" -lt "${v2_array[index]}" ]]; then + printf "$2" # Print input version, unchanged + return + else + printf "$1" # Print input version, unchanged + return + fi + done +} + function __static__Print_Requirement_Version_Report_Line() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" @@ -390,8 +480,8 @@ function __static__Get_Field_In_System_Information_String() printf '%s' "${tmp_array[$2]}" } -# This is basically a partial bash implementation of head, which we want avoid -# using in this file as it is a requirement that we want to check +# This is basically a partial bash implementation of head, which we want to +# avoid using in this file as it is a requirement that we want to check function __static__Get_First_Line_From_String() { while IFS= read -r line; do @@ -401,44 +491,5 @@ function __static__Get_First_Line_From_String() # The \n in printf is important to avoid skipping the last line (which might be the only input) } -function __static__Get_Larger_Version() -{ - local v1=$1 v2=$2 - if [[ ! "${v1}.${v2}" =~ ${HYBRID_version_regex} ]]; then - Print_Internal_And_Exit 'Wrong arguments passed to ' --emph "${FUNCNAME}" '.' - fi - # Ensure versions are of the same length to make following algorithm work - if [[ ${#v1} -lt ${#v2} ]]; then - declare -n shorter_array=v1 - declare -n longer_array=v2 - else - declare -n shorter_array=v2 - declare -n longer_array=v1 - fi - while [[ ${#v1[@]} -ne ${#v2[@]} ]]; do - shorter_array+='.0' # Add zeroes to shorter string - done - # If versions are equal, we're done - if [[ "${v2}" = "${v1}" ]]; then - printf "${v1}" - fi - # Split version strings into array of numbers replacing '.' by ' ' and let word splitting do the split - local v{1,2}_array index - v1_array=( ${v1//./ } ) - v2_array=( ${v2//./ } ) - # Now version arrays have same number of entries, compare them - for index in ${!v1_array[@]}; do - if [[ "${v1_array[index]}" -eq "${v2_array[index]}" ]]; then - continue - elif [[ "${v1_array[index]}" -lt "${v2_array[index]}" ]]; then - printf "$2" # Print input version, unchanged - return - else - printf "$1" # Print input version, unchanged - return - fi - done -} - Make_Functions_Defined_In_This_File_Readonly From 31e1b994df5b570c3545e86b4b8e306a53326615 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 28 Jun 2023 08:59:33 +0200 Subject: [PATCH 143/549] Adjust some spacing in system requirements binary report --- bash/system_requirements.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 90919fb..75563a9 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -71,7 +71,7 @@ function Check_System_Requirements_And_Make_Report() { __static__Declare_System_Requirements local system_report=() - local -r single_field_length=15 # This variable is used to prepare the report correctly formatted + local -r single_field_length=16 # This variable is used to prepare the report correctly formatted declare -A system_information # Same use of this variable as in 'Check_System_Requirements' function __static__Analyze_System_Properties __static__Print_Report_Title @@ -462,7 +462,7 @@ function __static__Get_Single_Tick_Cross_Requirement_Report() default='\e[0m' local line name="$1" status=$2 name_string printf -v name_string "%s ${emph_color}%s" "${name% *}" "${name#* }" - printf -v line " %*s${text_color}: ${default}" "${single_field_length}" "${name_string}" + printf -v line " %*s${text_color}: ${default}" "${single_field_length}" "${name_string}" if [[ ${status} = '---' ]]; then line+="${red}✘" elif [[ ${status} = '?' ]]; then From 0e4a804e1508070c898424de77c6af06e90ed45b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 30 Jun 2023 16:34:34 +0200 Subject: [PATCH 144/549] Fix function and some expansions of possibly unset variables --- bash/command_line_parsers/main_parser.bash | 4 ++-- bash/system_requirements.bash | 13 ++++++------- bash/utility_functions.bash | 1 + tests/tests_runner | 8 ++++---- tests/unit_tests_command_line_parser.bash | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 4aa4e49..83824c8 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -50,7 +50,7 @@ function Parse_Command_Line_Options() while [[ $# -gt 0 ]]; do case "$1" in -o | --output-directory ) - if [[ ${2:-} =~ ^(-|$) ]]; then + if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else readonly HYBRID_output_directory=$2 @@ -58,7 +58,7 @@ function Parse_Command_Line_Options() shift 2 ;; -c | --configuration-file ) - if [[ ${2:-} =~ ^(-|$) ]]; then + if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else readonly HYBRID_configuration_file=$2 diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 75563a9..8afc22f 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -147,8 +147,7 @@ function __static__Analyze_System_Properties() function __static__Exit_If_Some_GNU_Requirement_Is_Missing() { - local program errors is_gnu - errors=0 + local program errors=0 is_gnu for program in "${HYBRID_gnu_programs_required[@]}"; do Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[GNU-${program}]" is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) @@ -165,7 +164,7 @@ function __static__Exit_If_Some_GNU_Requirement_Is_Missing() function __static__Exit_If_Minimum_Versions_Are_Not_Available() { - local program errors min_version program_found version_found version_ok + local program errors=0 min_version program_found version_found version_ok for program in "${!HYBRID_versions_requirements[@]}"; do Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[${program}]" min_version=${HYBRID_versions_requirements["${program}"]} @@ -196,7 +195,7 @@ function __static__Exit_If_Minimum_Versions_Are_Not_Available() function __static__Exit_If_Some_Needed_Environment_Variable_Is_Missing() { - local name errors + local name errors=0 for name in "${HYBRID_env_variables_required[@]}"; do Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[${name}]" if [[ ${system_information[${name}]} = '---' ]]; then @@ -274,7 +273,7 @@ function __static__Print_Formatted_Binary_Report() # here (see https://stackoverflow.com/a/48016366/14967071 for more information). shopt -s checkwinsize # Do not assume it is on (as it usually is) ( : ) # Refresh LINES and COLUMNS, this happens when a child process exits - local -r num_cols=$(( COLUMNS / 2 / single_field_length )) + local -r num_cols=$(( ${COLUMNS-80} / 2 / single_field_length )) local index printf_descriptor printf_descriptor="%${single_field_length}s" # At least one column for ((index=1; index Date: Sat, 1 Jul 2023 10:58:00 +0200 Subject: [PATCH 145/549] Fix bug in comparison of version strings The code comapring string was not considering the case the two compared version numbers having different number of digits, which made the algorithm going in an endless loop. This has been fixed now taking the opportunity to rename a variable and fix some indentation. --- bash/system_requirements.bash | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 8afc22f..2ec5e99 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -373,26 +373,31 @@ function __static__Is_Gnu_Version_In_Use() function __static__Get_Larger_Version() { - local v1=$1 v2=$2 + local v1=$1 v2=$2 dots_of_v1 dots_of_v2 if [[ ! "${v1}.${v2}" =~ ${HYBRID_version_regex} ]]; then Print_Internal_And_Exit 'Wrong arguments passed to ' --emph "${FUNCNAME}" '.' fi # Ensure versions are of the same length to make following algorithm work - if [[ ${#v1} -lt ${#v2} ]]; then - declare -n shorter_array=v1 - declare -n longer_array=v2 + dots_of_v1=${v1//[^.]/} + dots_of_v2=${v2//[^.]/} + if [[ ${#dots_of_v1} -lt ${#dots_of_v2} ]]; then + declare -n shorter_version=v1\ + dots_of_shorter_version=dots_of_v1\ + dots_of_longer_version=dots_of_v2 else - declare -n shorter_array=v2 - declare -n longer_array=v1 + declare -n shorter_version=v2\ + dots_of_shorter_version=dots_of_v2\ + dots_of_longer_version=dots_of_v1 fi - while [[ ${#v1} -ne ${#v2} ]]; do - shorter_array+='.0' # Add zeroes to shorter string + while [[ ${#dots_of_shorter_version} -ne ${#dots_of_longer_version} ]]; do + shorter_version+='.0' # Add zeroes to shorter string + dots_of_shorter_version+='.' # Add dot to keep counting accurate done - # If versions are equal, we're done - if [[ "${v2}" = "${v1}" ]]; then - printf "${v1}" - return - fi + # If versions are equal, we're done + if [[ "${v2}" = "${v1}" ]]; then + printf "${v1}" + return + fi # Split version strings into array of numbers replacing '.' by ' ' and let word splitting do the split local v{1,2}_array index v1_array=( ${v1//./ } ) From bd00d767c36a4c7f581abf6869d06d99d466f101 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 30 Jun 2023 16:42:35 +0200 Subject: [PATCH 146/549] Implement mechanism to invoke codebase code in stricter mode The hybrid handler bash code is run with some shell options enabled that changes the shell behavior (nounset, errexit, etc.). It makes definitely sense to run unit tests in the same mode. However, tests makes heavy use of the $? variable to test for success/failure and this is the main reason why in the beginning the shell standard behaviour had been used. Now the same mode used by the handler is enabled in a function that takes care to invoke a codebase function, catch and return its exit code. Such as function will be next used in all unit test to run hybrid handler snippets of code. --- tests/command_line_parser_for_tests.bash | 4 ++-- tests/tests_runner | 12 ++++++++++++ tests/unit_tests.bash | 21 ++++++++++++++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 915b891..af8bb39 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -37,14 +37,14 @@ function Parse_Tests_Command_Line_Options() exit ${HYBRID_success_exit_code} shift ;; -r | --report-level ) - if [[ $2 =~ ^[0-3]$ ]]; then + if [[ ${2-} =~ ^[0-3]$ ]]; then readonly HYBRIDT_report_level=$2 else Print_Option_Specification_Error_And_Exit "$1" fi shift 2 ;; -t | --run-tests ) - if [[ ! $2 =~ ^- && "$2" != '' ]]; then + if [[ ! ${2-} =~ ^- && "${2-}" != '' ]]; then if [[ $2 =~ ^[1-9][0-9]*([,\-][1-9][0-9]*)*$ ]]; then __static__Set_Tests_To_Be_Run_Using_Numbers "$2" elif [[ $2 =~ ^[[:alpha:]*?] ]]; then diff --git a/tests/tests_runner b/tests/tests_runner index 03a7cf6..cacebbc 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -27,6 +27,7 @@ function Main() { Setup_Initial_And_Final_Output_Space Define_Tests_Global_Variables + Enable_Global_Needed_Shell_Options Source_Needed_Files Print_Helper_And_Exit_If_Requested "$@" Parse_Tests_Suite_Parameter_And_Source_Specific_Code "$1" @@ -51,6 +52,17 @@ function Setup_Initial_And_Final_Output_Space() trap 'printf "\n"' EXIT } +function Enable_Global_Needed_Shell_Options() +{ + # See CONTRIBUTING for explanation about why globally + shopt -s extglob + set -o pipefail -o nounset + # NOTE: The behavior about 'errexit' mode here is not the same as in the handler, i.e. we do + # not enable such a mode globally. This is because we want to easily have access to exit + # codes in tests to test success/failure. The errexit mode is enabled only when running + # the codebase functions, see 'Call_Codebase_Function'. +} + function Define_Tests_Global_Variables () { readonly HYBRIDT_repository_top_level_path=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../" &> /dev/null && pwd) diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 8eeabca..3b96bcf 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -36,18 +36,33 @@ function Make_Test_Preliminary_Operations() # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 - } &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger. + } &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } function Run_Test() { - Unit_Test__$1 &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger. + Unit_Test__$1 &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } function Clean_Tests_Environment_For_Following_Test() { - # The fd 9 is used by the logger. + # The fd 9 is used by the logger Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 9>&1 } +function Call_Codebase_Function() +{ + # Set stricter bash mode to run codebase code in the mode it is supposed to be run + set -o errexit + shopt -s inherit_errexit + # NOTE: Call the codebase function in subshell to avoid exiting the test if in the + # codebase function runs an exit command. + local return_code=0 + ( Call_Function_If_Existing_Or_Exit "$@" ) || return_code=$? + # Switch off errexit bash mode to handle errors in a standard way inspecting $? + set +o errexit + shopt -u inherit_errexit + return ${return_code} +} + #======================================================================================================================= From 7d324768e64b48a5919ae94b1b81be28c8558bc2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 30 Jun 2023 18:52:04 +0200 Subject: [PATCH 147/549] Refine mechanism to invoke codebase functions in unit tests It can now be decided whether to invoke codebase functions in subshells or not. Sometimes this is needed e.g. to avoid the test to be exited by an exit command given in the invoked function, but this disable to possibility to inspect changed values of global states (as the change would occur in a subshell). Therefore, a mechanism to invoke codebase functions without using a subshell has been added. --- tests/tests_runner | 2 +- tests/unit_tests.bash | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/tests_runner b/tests/tests_runner index cacebbc..79f48b5 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -60,7 +60,7 @@ function Enable_Global_Needed_Shell_Options() # NOTE: The behavior about 'errexit' mode here is not the same as in the handler, i.e. we do # not enable such a mode globally. This is because we want to easily have access to exit # codes in tests to test success/failure. The errexit mode is enabled only when running - # the codebase functions, see 'Call_Codebase_Function'. + # the codebase functions, see 'Call_Codebase_Function[_In_Subshell]'. } function Define_Tests_Global_Variables () diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 3b96bcf..9af0d3a 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -50,7 +50,19 @@ function Clean_Tests_Environment_For_Following_Test() Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 9>&1 } +function Call_Codebase_Function_In_Subshell() +{ + __static__Call_Codebase_Function_As_Desired 'IN_SUBSHELL' "$@" +} + function Call_Codebase_Function() +{ + __static__Call_Codebase_Function_As_Desired "$@" +} + +#======================================================================================================================= + +function __static__Call_Codebase_Function_As_Desired() { # Set stricter bash mode to run codebase code in the mode it is supposed to be run set -o errexit @@ -58,11 +70,13 @@ function Call_Codebase_Function() # NOTE: Call the codebase function in subshell to avoid exiting the test if in the # codebase function runs an exit command. local return_code=0 - ( Call_Function_If_Existing_Or_Exit "$@" ) || return_code=$? + if [[ ${1-} = 'IN_SUBSHELL' ]]; then + ( Call_Function_If_Existing_Or_Exit "${@:2}" ) || return_code=$? + else + Call_Function_If_Existing_Or_Exit "$@" || return_code=$? + fi # Switch off errexit bash mode to handle errors in a standard way inspecting $? set +o errexit shopt -u inherit_errexit return ${return_code} } - -#======================================================================================================================= From 6d394baacf8325428ab026f3d35586e7769128d1 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 30 Jun 2023 19:03:05 +0200 Subject: [PATCH 148/549] Use bash more efficient command expansion instead of cat --- tests/unit_tests_software_input_functionality.bash | 8 ++++---- tests/unit_tests_utility_functions.bash | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 8dac17d..377de53 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -69,9 +69,9 @@ Foo: BarBar' # yq in v4.30.6 fixed the behavior of keeping leading empty lines # so it is important here to have no leading empty lines, otherwise # this test would succeed/fail depending on yq version available! - if [[ "$(cat "${base_input_file}")" != "${expected_result}" ]]; then + if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then Print_Error "YAML replacement failed!"\ - '---- OBTAINED: ----' "$(cat "${base_input_file}")"\ + '---- OBTAINED: ----' "$(< "${base_input_file}")"\ '---- EXPECTED: ----' "${expected_result}"\ '-------------------' return 1 @@ -119,9 +119,9 @@ function Unit_Test__replace-in-software-input-TXT() printf -v expected_result "%-20s%s\n" 'a' '42' 'b' '0.456' 'c' '77' expected_result=${expected_result%?} # Get rid of trailing endline __static__Replace_Keys_Into_Txt_File - if [[ "$(cat "${base_input_file}")" != "${expected_result}" ]]; then + if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then Print_Error "YAML replacement failed!"\ - "---- OBTAINED: ----\n$(cat "${base_input_file}")"\ + "---- OBTAINED: ----\n$(< "${base_input_file}")"\ "---- EXPECTED: ----\n${expected_result}"\ '-------------------' return 1 diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 2e66f37..447a335 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -151,7 +151,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() local -r file_containing_one_line_with_an_inline_comment=${FUNCNAME}_4.txt printf 'Hello %% Comment\n' > "${file_containing_one_line_with_an_inline_comment}" Remove_Comments_In_File "${file_containing_one_line_with_an_inline_comment}" '%' - if [[ $(cat "${file_containing_one_line_with_an_inline_comment}") != 'Hello' ]]; then + if [[ $(< "${file_containing_one_line_with_an_inline_comment}") != 'Hello' ]]; then Print_Error 'Removing comments in ' --emph "${file_containing_one_line_with_an_inline_comment}" ' file failed.' return 1 fi From ab58132560ea2f0101969c39f7048ea04f74a029 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 30 Jun 2023 19:04:30 +0200 Subject: [PATCH 149/549] Use interface to invoke codebase functions in unit tests --- tests/unit_tests_IC_functionality.bash | 14 ++-- tests/unit_tests_command_line_parser.bash | 12 +-- tests/unit_tests_configuration.bash | 36 +++++---- tests/unit_tests_help_for_user.bash | 2 +- ...it_tests_software_input_functionality.bash | 16 ++-- tests/unit_tests_system_requirements.bash | 8 +- tests/unit_tests_utility_functions.bash | 80 +++++++++---------- tests/unit_tests_version.bash | 5 +- 8 files changed, 89 insertions(+), 84 deletions(-) diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index e8f0804..770a97b 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -30,18 +30,18 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() function Unit_Test__IC-create-input-file() { touch "${HYBRID_software_base_config_file[IC]}" - Prepare_Software_Input_File_IC + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_IC if [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then Print_Error 'The output directory and/or software input file were not properly created.' return 1 fi rm -r "${HYBRID_output_directory}/"* - Prepare_Software_Input_File_IC + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_IC if [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then Print_Error 'The input file was not properly created in the output folder.' return 1 fi - ( Prepare_Software_Input_File_IC &> /dev/null ) + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_IC &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Preparation of input with existent config succeeded.' return 1 @@ -61,19 +61,19 @@ function Make_Test_Preliminary_Operations__IC-check-all-input() function Unit_Test__IC-check-all-input() { - ( Ensure_All_Needed_Input_Exists_IC &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_IC &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing output directory succeeded.' return 1 fi mkdir -p "${HYBRID_software_output_directory[IC]}" - ( Ensure_All_Needed_Input_Exists_IC &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_IC &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing config file succeeded.' return 1 fi touch "${HYBRID_software_configuration_file[IC]}" - ( Ensure_All_Needed_Input_Exists_IC ) + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_IC if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing folder/file failed.' return 1 @@ -98,7 +98,7 @@ function Unit_Test__IC-test-run-software() printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[IC]}" chmod a+x "${HYBRID_software_executable[IC]}" local terminal_output_result correct_result - Run_Software_IC + Call_Codebase_Function_In_Subshell Run_Software_IC if [[ ! -f "${ic_terminal_output}" ]]; then Print_Error 'The terminal output was not created.' return 1 diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index b159199..1a57541 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -25,7 +25,7 @@ function __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success( local expected_option=$1\ expected_size=$2\ first_option="${HYBRID_command_line_options_to_parse[0]-}" - Parse_Execution_Mode + Call_Codebase_Function Parse_Execution_Mode if [[ $? -ne 0 ]] ||\ [[ "${HYBRID_execution_mode}" != "${expected_option}" ]] ||\ [[ ${#HYBRID_command_line_options_to_parse[@]} -ne "${expected_size}" ]]; then @@ -57,7 +57,7 @@ function Unit_Test__parse-execution-mode() HYBRID_command_line_options_to_parse=( 'do' '-o' '/path/to/folder' '--help' ) __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'do-help' 0 || return 1 HYBRID_command_line_options_to_parse=( 'invalid-mode' ) - ( Parse_Execution_Mode &> /dev/null ) + Call_Codebase_Function_In_Subshell Parse_Execution_Mode &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Parsing of invalid execution mode succeeded.' return 1 @@ -73,7 +73,7 @@ function Make_Test_Preliminary_Operations__parse-command-line-options() function __static__Test_CLO_Parsing_Missing_Value() { - ( Parse_Command_Line_Options &> /dev/null ) + Call_Codebase_Function_In_Subshell Parse_Command_Line_Options &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Parsing of CLO ' --emph "${HYBRID_command_line_options_to_parse[0]}"\ ' with missing value succeeded.' @@ -83,7 +83,7 @@ function __static__Test_CLO_Parsing_Missing_Value() function __static__Test_Single_CLO_Parsing_In_Subshell() ( - Parse_Command_Line_Options + Call_Codebase_Function Parse_Command_Line_Options if [[ $? -ne 0 ]] || [[ ${!1} != "$2" ]]; then Print_Error 'Parsing of ' --emph "${HYBRID_command_line_options_to_parse[0]}" ' with valid value failed.' return 1 @@ -92,7 +92,7 @@ function __static__Test_Single_CLO_Parsing_In_Subshell() function Unit_Test__parse-command-line-options() { - ( Parse_Command_Line_Options &> /dev/null ) + Call_Codebase_Function_In_Subshell Parse_Command_Line_Options &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Parsing of CLO in wrong execution mode succeeded.' return 1 @@ -103,7 +103,7 @@ function Unit_Test__parse-command-line-options() HYBRID_command_line_options_to_parse=( --configuration-file ) __static__Test_CLO_Parsing_Missing_Value || return 1 HYBRID_command_line_options_to_parse=() - ( Parse_Command_Line_Options ) + Call_Codebase_Function_In_Subshell Parse_Command_Line_Options if [[ $? -ne 0 ]]; then Print_Error 'Parsing of CLO with no CLO failed.' return 1 diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 848b233..80623b3 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -22,7 +22,7 @@ function Make_Test_Preliminary_Operations__configuration-validate-existence() function Unit_Test__configuration-validate-existence() { - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation not existing file succeeded.' return 1 @@ -40,7 +40,7 @@ function Unit_Test__configuration-validate-YAML() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml printf 'Scalar\nKey: Value\n' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of invalid YAML in configuration file succeeded.' return 1 @@ -59,31 +59,31 @@ function Unit_Test__configuration-validate-section-labels() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml printf 'Invalid: Value\n' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with invalid section succeeded.' return 1 fi printf 'Afterburner: Values\nIC: Values\nHydro: Values\n' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with sections in wrong order succeeded.' return 1 fi printf 'IC: Values\nSampler: Values\nIC: Again\n' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with repeated section succeeded.' return 1 fi printf 'IC: Values\nSampler: Values\n' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with missing sections succeeded.' return 1 fi printf 'Hybrid_handler: Values\n' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with no software section succeeded.' return 1 @@ -117,7 +117,7 @@ function Unit_Test__configuration-validate-all-keys() Nope: 13 Maybe: False ' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with only invalid keys succeeded.' return 1 @@ -128,7 +128,7 @@ function Unit_Test__configuration-validate-all-keys() Executable: /path/to/exec Invalid: 42 ' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File &> /dev/null ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of configuration file with invalid keys succeeded.' return 1 @@ -140,7 +140,7 @@ function Unit_Test__configuration-validate-all-keys() Hydro: Input_file: /path/to/file ' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File if [[ $? -ne 0 ]]; then Print_Error 'Validation of configuration file failed.' return 1 @@ -159,7 +159,7 @@ function Unit_Test__configuration-parse-general-section() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml printf 'Hybrid_handler: {}\nIC:\n Executable: foo\n' > "${HYBRID_configuration_file}" - ( Validate_And_Parse_Configuration_File ) + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File if [[ $? -ne 0 ]]; then Print_Error 'Parsing of general section failed.' return 1 @@ -176,7 +176,7 @@ function __static__Test_Section_Parsing_In_Subshell() executable=$2 input_file=$3 new_keys=$4 - Validate_And_Parse_Configuration_File + Call_Codebase_Function Validate_And_Parse_Configuration_File if [[ "${#HYBRID_given_software_sections[@]}" -ne 1 ]] ||\ [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (section storing).' @@ -208,7 +208,8 @@ function Unit_Test__configuration-parse-IC-section() General: Randomseed: 12345 ' > "${HYBRID_configuration_file}" - __static__Test_Section_Parsing_In_Subshell 'IC' 'foo' 'bar' $'General:\n Randomseed: 12345' + Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ + 'IC' 'foo' 'bar' $'General:\n Randomseed: 12345' if [[ $? -ne 0 ]]; then return 1 fi @@ -232,7 +233,8 @@ function Unit_Test__configuration-parse-Hydro-section() Software_keys: etaS: 0.12345 ' > "${HYBRID_configuration_file}" - __static__Test_Section_Parsing_In_Subshell 'Hydro' 'foo' 'bar' 'etaS: 0.12345' + Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ + 'Hydro' 'foo' 'bar' 'etaS: 0.12345' if [[ $? -ne 0 ]]; then return 1 fi @@ -256,7 +258,8 @@ function Unit_Test__configuration-parse-Sampler-section() Software_keys: shear: 1.2345 ' > "${HYBRID_configuration_file}" - __static__Test_Section_Parsing_In_Subshell 'Sampler' 'foo' 'bar' 'shear: 1.2345' + Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ + 'Sampler' 'foo' 'bar' 'shear: 1.2345' if [[ $? -ne 0 ]]; then return 1 fi @@ -281,7 +284,8 @@ function Unit_Test__configuration-parse-Afterburner-section() General: End_Time: 42000 ' > "${HYBRID_configuration_file}" - __static__Test_Section_Parsing_In_Subshell 'Afterburner' 'foo' 'bar' $'General:\n End_Time: 42000' + Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ + 'Afterburner' 'foo' 'bar' $'General:\n End_Time: 42000' if [[ $? -ne 0 ]]; then return 1 fi diff --git a/tests/unit_tests_help_for_user.bash b/tests/unit_tests_help_for_user.bash index f0e1d03..571874c 100644 --- a/tests/unit_tests_help_for_user.bash +++ b/tests/unit_tests_help_for_user.bash @@ -16,7 +16,7 @@ function Make_Test_Preliminary_Operations__give-requested-help() function __static__Run_Helper_Expecting_Success() { - ( Give_Required_Help ) + Call_Codebase_Function_In_Subshell Give_Required_Help if [[ $? -ne 0 ]]; then Print_Error 'Providing help in ' --emph "${HYBRID_execution_mode}" ' execution mode failed.' return 1 diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 377de53..df90150 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -21,7 +21,7 @@ function Unit_Test__replace-in-software-input-YAML() base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml # Test case 1: printf 'Scalar\nKey: Value\n' > "${base_input_file}" - ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_YAML_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'YAML replacement in invalid file succeeded.' return 1 @@ -29,7 +29,7 @@ function Unit_Test__replace-in-software-input-YAML() # Test case 2: printf 'Key: Value\n' > "${base_input_file}" keys_to_be_replaced=$'Invalid\nyaml: syntax' - ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_YAML_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Invalid YAML replacement in valid file succeeded.' return 1 @@ -37,7 +37,7 @@ function Unit_Test__replace-in-software-input-YAML() # Test case 3: printf 'Key: Value\n' > "${base_input_file}" keys_to_be_replaced='New_key: value' - ( __static__Replace_Keys_Into_YAML_File &> /dev/null ) + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_YAML_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Valid YAML replacement but with non existent key in valid file succeeded.' return 1 @@ -65,7 +65,7 @@ Map: Key_1: Hi Key_2: Bye Foo: BarBar' - __static__Replace_Keys_Into_YAML_File + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_YAML_File # yq in v4.30.6 fixed the behavior of keeping leading empty lines # so it is important here to have no leading empty lines, otherwise # this test would succeed/fail depending on yq version available! @@ -92,7 +92,7 @@ function Unit_Test__replace-in-software-input-TXT() base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml # Test case 1: printf 'Key Value Extra-field\n' > "${base_input_file}" - ( __static__Replace_Keys_Into_Txt_File &> /dev/null ) + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'TXT replacement in invalid file succeeded' return 1 @@ -100,7 +100,7 @@ function Unit_Test__replace-in-software-input-TXT() # Test case 2: printf 'Key: Value\n' > "${base_input_file}" keys_to_be_replaced='Invalid txt syntax' - ( __static__Replace_Keys_Into_Txt_File &> /dev/null ) + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Invalid TXT replacement in valid file succeeded' return 1 @@ -108,7 +108,7 @@ function Unit_Test__replace-in-software-input-TXT() # Test case 3: printf 'Key Value\n' > "${base_input_file}" keys_to_be_replaced='New_key value' - ( __static__Replace_Keys_Into_Txt_File &> /dev/null ) + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Valid TXT replacement but with non existent key in valid file succeeded' return 1 @@ -118,7 +118,7 @@ function Unit_Test__replace-in-software-input-TXT() keys_to_be_replaced=$'a 42\nc 77' printf -v expected_result "%-20s%s\n" 'a' '42' 'b' '0.456' 'c' '77' expected_result=${expected_result%?} # Get rid of trailing endline - __static__Replace_Keys_Into_Txt_File + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then Print_Error "YAML replacement failed!"\ "---- OBTAINED: ----\n$(< "${base_input_file}")"\ diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 2ac2e57..0650318 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -56,12 +56,12 @@ function Unit_Test__system-requirements() sed_version=4.2.1 tput_version=5.9 yq_version=4.18.1 - ( Check_System_Requirements ) + Call_Codebase_Function_In_Subshell Check_System_Requirements if [[ $? -ne 0 ]]; then Print_Error "Check system requirements of good system failed." return 1 fi - ( Check_System_Requirements_And_Make_Report ) + Call_Codebase_Function_In_Subshell Check_System_Requirements_And_Make_Report if [[ $? -ne 0 ]]; then Print_Error "Check system requirements making report of good system failed." return 1 @@ -72,13 +72,13 @@ function Unit_Test__system-requirements() tput_version='' yq_version=3.9.98 printf '\n' - ( Check_System_Requirements &> /dev/null ) + Call_Codebase_Function_In_Subshell Check_System_Requirements &> /dev/null if [[ $? -eq 0 ]]; then Print_Error "Check system requirements of bad system succeeded." return 1 fi printf '\n' - ( unset -v 'TERM'; Check_System_Requirements_And_Make_Report ) + ( unset -v 'TERM'; Call_Codebase_Function_In_Subshell Check_System_Requirements_And_Make_Report ) if [[ $? -ne 0 ]]; then Print_Error "Check system requirements making report of bad system failed." return 1 diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 447a335..3419b82 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -11,32 +11,32 @@ function Unit_Test__utility-has-YAML-string-given-key() { - ( Has_YAML_String_Given_Key &> /dev/null ) + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Wrong call to function succeeded." return 1 fi - ( Has_YAML_String_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null ) + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Function called on invalid YAML succeeded." return 1 fi - ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c' &> /dev/null ) + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c' &> /dev/null if [[ $? -ne 0 ]] ; then Print_Error 'Existing key ' --emph '{a: {b: {c:}}}' ' not found.' return 1 fi - ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' &> /dev/null ) + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' &> /dev/null if [[ $? -ne 0 ]] ; then Print_Error 'Existing key ' --emph '{a: {b:}}' ' not found.' return 1 fi - ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' &> /dev/null ) + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' &> /dev/null if [[ $? -ne 0 ]] ; then Print_Error 'Existing key ' --emph '{a:}' ' not found.' return 1 fi - ( Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'nope' &> /dev/null ) + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'nope' &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Not existing key found." return 1 @@ -45,28 +45,28 @@ function Unit_Test__utility-has-YAML-string-given-key() function Unit_Test__utility-read-from-YAML-string-given-key() { - ( Read_From_YAML_String_Given_Key &> /dev/null ) + Call_Codebase_Function_In_Subshell Read_From_YAML_String_Given_Key &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Wrong call to function succeeded." return 1 fi - ( Read_From_YAML_String_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null ) + Call_Codebase_Function_In_Subshell Read_From_YAML_String_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Function called on invalid YAML succeeded." return 1 fi - ( Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'nope' &> /dev/null ) + Call_Codebase_Function_In_Subshell Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'nope' &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Not existing key successfully read." return 1 fi local result - result=$(Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c') + result=$(Call_Codebase_Function Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c') if [[ ${result} -ne 42 ]] ; then Print_Error "Reading scalar key failed." return 1 fi - result=$(Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b') + result=$(Call_Codebase_Function Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b') if [[ "${result}" != 'c: 42' ]] ; then Print_Error "Reading map key failed." return 1 @@ -75,33 +75,33 @@ function Unit_Test__utility-read-from-YAML-string-given-key() function Unit_Test__utility-print-YAML-string-without-given-key() { - ( Print_YAML_String_Without_Given_Key &> /dev/null ) + Call_Codebase_Function_In_Subshell Print_YAML_String_Without_Given_Key &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Wrong call to function succeeded." return 1 fi - ( Print_YAML_String_Without_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null ) + Call_Codebase_Function_In_Subshell Print_YAML_String_Without_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Function called on invalid YAML succeeded." return 1 fi - ( Print_YAML_String_Without_Given_Key $'a:\n b: 42\n' 'nope' &> /dev/null ) + Call_Codebase_Function_In_Subshell Print_YAML_String_Without_Given_Key $'a:\n b: 42\n' 'nope' &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Not existing key successfully deleted." return 1 fi local result - result=$(Print_YAML_String_Without_Given_Key $'a: 42\nb: 17\n' 'b') + result=$(Call_Codebase_Function Print_YAML_String_Without_Given_Key $'a: 42\nb: 17\n' 'b') if [[ "${result}" != 'a: 42' ]] ; then Print_Error "Deleting scalar key failed." return 1 fi - result=$(Print_YAML_String_Without_Given_Key $'a:\n b:\n c: 17\n' 'a' 'b') + result=$(Call_Codebase_Function Print_YAML_String_Without_Given_Key $'a:\n b:\n c: 17\n' 'a' 'b') if [[ "${result}" != 'a: {}' ]] ; then Print_Error "Deleting map key failed." return 1 fi - result=$(Print_YAML_String_Without_Given_Key $'a: 42\n' 'a') + result=$(Call_Codebase_Function Print_YAML_String_Without_Given_Key $'a: 42\n' 'a') if [[ "${result}" != '{}' ]] ; then Print_Error "Deleting only existing key failed." return 1 @@ -113,7 +113,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() local number_of_lines cd "${HYBRIDT_folder_to_run_tests}" # Test case 0 - ( Remove_Comments_In_Existing_File 'not_existing_file.txt' &> /dev/null ) + Call_Codebase_Function_In_Subshell Remove_Comments_In_Existing_File 'not_existing_file.txt' &> /dev/null if [[ $? -eq 0 ]] ; then Print_Error "Remove comments on not existent file did not fail." return 1 @@ -121,7 +121,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() # Test case 1 local -r file_containing_one_commented_line_only=${FUNCNAME}_1.txt printf ' # Comment\n' > "${file_containing_one_commented_line_only}" - Remove_Comments_In_File "${file_containing_one_commented_line_only}" + Call_Codebase_Function_In_Subshell Remove_Comments_In_File "${file_containing_one_commented_line_only}" if [[ -s "${file_containing_one_commented_line_only}" ]]; then Print_Error 'File ' --emph "${file_containing_one_commented_line_only}" ' not empty.' return 1 @@ -131,7 +131,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() local -r file_containing_no_comments=${FUNCNAME}_2.txt printf $'No comment\nin any\nline\n' > "${file_containing_no_comments}" number_of_lines=$(wc -l < "${file_containing_no_comments}") - Remove_Comments_In_File "${file_containing_no_comments}" + Call_Codebase_Function_In_Subshell Remove_Comments_In_File "${file_containing_no_comments}" if [[ $(wc -l < "${file_containing_no_comments}") -ne ${number_of_lines} ]]; then Print_Error 'Removing comments in ' --emph "${file_containing_no_comments}" ' file failed.' return 1 @@ -141,7 +141,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() local -r file_containing_three_commented_lines=${FUNCNAME}_3.txt printf $'Some\n #comment\ntext\n#comment\namong\n#comment\ncomments\n' > "${file_containing_three_commented_lines}" number_of_lines=$(wc -l < "${file_containing_three_commented_lines}") - Remove_Comments_In_File "${file_containing_three_commented_lines}" + Call_Codebase_Function_In_Subshell Remove_Comments_In_File "${file_containing_three_commented_lines}" if (( $(wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3 )); then Print_Error 'Removing comments in ' --emph "${file_containing_three_commented_lines}" ' file failed.' return 1 @@ -150,7 +150,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() # Test case 4 local -r file_containing_one_line_with_an_inline_comment=${FUNCNAME}_4.txt printf 'Hello %% Comment\n' > "${file_containing_one_line_with_an_inline_comment}" - Remove_Comments_In_File "${file_containing_one_line_with_an_inline_comment}" '%' + Call_Codebase_Function_In_Subshell Remove_Comments_In_File "${file_containing_one_line_with_an_inline_comment}" '%' if [[ $(< "${file_containing_one_line_with_an_inline_comment}") != 'Hello' ]]; then Print_Error 'Removing comments in ' --emph "${file_containing_one_line_with_an_inline_comment}" ' file failed.' return 1 @@ -160,48 +160,48 @@ function Unit_Test__utility-remove-comments-in-existing-file() function Unit_Test__utility-check-shell-variables-set() { - ( Ensure_That_Given_Variables_Are_Set foo &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set foo &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Checking unset variable succeeded.' return 1 fi local foo - ( Ensure_That_Given_Variables_Are_Set foo &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set foo &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Checking set but unassigned variable failed.' return 1 fi foo='' - ( Ensure_That_Given_Variables_Are_Set foo &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set foo &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Checking set but empty variable failed.' return 1 fi foo='bar' - ( Ensure_That_Given_Variables_Are_Set foo ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set foo if [[ $? -ne 0 ]]; then Print_Error 'Checking set and not empty string-variable failed.' return 1 fi foo=() - ( Ensure_That_Given_Variables_Are_Set foo ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set foo if [[ $? -ne 0 ]]; then Print_Error 'Checking empty array variable failed.' return 1 fi foo=( '' ) - ( Ensure_That_Given_Variables_Are_Set foo ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set foo if [[ $? -ne 0 ]]; then Print_Error 'Checking array variable set to one empty entry failed.' return 1 fi declare -A bar=([Hi]='') - ( Ensure_That_Given_Variables_Are_Set bar ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set bar if [[ $? -ne 0 ]]; then Print_Error 'Checking associative array variable set to one empty entry failed.' return 1 fi - ( Ensure_That_Given_Variables_Are_Set bar[Hi] ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set bar[Hi] if [[ $? -ne 0 ]]; then Print_Error 'Checking associative array entry set to one empty entry failed.' return 1 @@ -211,54 +211,54 @@ function Unit_Test__utility-check-shell-variables-set() function Unit_Test__utility-check-shell-variables-set-not-empty() { local foo - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Checking set but unassigned variable succeeded.' return 1 fi foo='' - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Checking set but empty variable succeeded.' return 1 fi foo='bar' - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo if [[ $? -ne 0 ]]; then Print_Error 'Checking set and not empty variable failed.' return 1 fi foo=() - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Checking empty array variable succeeded.' return 1 fi foo=( '' ) - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo if [[ $? -ne 0 ]]; then Print_Error 'Checking array variable set to one empty entry failed.' return 1 fi declare -A bar - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Checking set but unassigned associative array succeeded.' return 1 fi bar['key']='' - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar if [[ $? -ne 0 ]]; then Print_Error 'Checking set associative array failed.' return 1 fi - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar[key] &> /dev/null ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar[key] &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Checking empty associative array entry succeeded.' return 1 fi bar['key']='something' - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar[key] ) + Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty bar[key] if [[ $? -ne 0 ]]; then Print_Error 'Checking set associative array entry failed.' return 1 @@ -269,7 +269,7 @@ function __static__Test_ANSI_Code_Removal() { Ensure_That_Given_Variables_Are_Set output Ensure_That_Given_Variables_Are_Set_And_Not_Empty input expected_output - output=$(Strip_ANSI_Color_Codes_From_String "${input}") + output=$(Call_Codebase_Function Strip_ANSI_Color_Codes_From_String "${input}") if [[ "${output}" != "${expected_output}" ]]; then Print_Error 'Removing format code from ' --emph "${expected_output}" ' failed.' return 1 diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash index 0778682..463c94b 100644 --- a/tests/unit_tests_version.bash +++ b/tests/unit_tests_version.bash @@ -17,14 +17,15 @@ function Unit_Test__version() HYBRID_codebase_version='SMASH-vHLLE-hybrid-1.0' local std_output expected_output # Unsetting PATH in the subshell so that 'git' will not be found - std_output=$(PATH=''; Print_Software_Version) + std_output=$(PATH=''; Call_Codebase_Function Print_Software_Version) if [[ $? -ne 0 ]] ||\ [[ $(Strip_ANSI_Color_Codes_From_String "${std_output}") != "This is ${HYBRID_codebase_version}" ]] ; then Print_Error "Version printing without git available failed." return 1 fi if hash git &> /dev/null; then - std_output=$(Print_Software_Version 9>&1) # We want to capture here a logger message that goes to fd 9 + # We want to capture here a logger message that goes to fd 9 + std_output=$(Call_Codebase_Function Print_Software_Version 9>&1) if [[ $? -ne 0 || $(grep -c "${HYBRID_codebase_version}" <<< "${std_output}") -eq 0 ]] ; then Print_Error "Version printing with git failed." return 1 From 6dcacc975931fbe49662e1baec1f731fed9ec705 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 30 Jun 2023 19:17:01 +0200 Subject: [PATCH 150/549] Mention the new unit test mechanism in CONTRIBUTING --- CONTRIBUTING.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a3d703..0578a31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ Finally, a short remark about `extglob` option. To motivate why we decided to en ## Bash notation in the codebase The general advice is pretty trivial: **Be consistent with what you find**. -Here a list of some aspects worth mentioning: +Here a list of some aspects worth mentioning about the codebase: * indentation is done _exclusively with spaces_ and **no** Tab should be used; * lines of code are split around 100 characters and should never be longer than 120; * bash functions use both the `function` keyword and parenthesis and the enclosing braces are put on separate lines, @@ -83,7 +83,10 @@ Here a list of some aspects worth mentioning: * quotes are correctly used, i.e. everything that _might_ break if unquoted is quoted; * single quotes are used if there is no need of using double or different quotes; * all functions declared in each separate file are marked in the end of the file as `readonly`; -* files are sourced all together by sourcing a single dedicated file (cf. *bash/source_codebase_files.bash* file); +* files are sourced all together by sourcing a single dedicated file (cf. *bash/source_codebase_files.bash* file). + +Here, instead, a list of aspects specific to tests that should be kept in mind: * unit tests must be put in files whose names begin with `unit_tests_` and have the `.bash` extension (this convention allows the runner to source them all automatically) in the ***tests*** folder; * unit tests are automatically recognized by the tests runner as functions having the `Unit_Test__` prefix (and the remaining part of the function will be the unit test name); -* operations to be done before or after a unit test can be put in the `Make_Test_Preliminary_Operations__[test-name]` and `Clean_Tests_Environment_For_Following_Test__[test-name]` functions, respectively (here `[test-name]` must match the string used in the unit test function name). +* operations to be done before or after a unit test can be put in the `Make_Test_Preliminary_Operations__[test-name]` and `Clean_Tests_Environment_For_Following_Test__[test-name]` functions, respectively (here `[test-name]` must match the string used in the unit test function name); +* codebase functions to be invoked in unit tests should be called through the `Call_Codebase_Function` and `Call_Codebase_Function_In_Subshell` interface functions (passing the name of the function to be invoked as first argument and the arguments to be forward afterwards). From 0a12f008f85f80ef277c7e152eb051e838f78afa Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 21 Jun 2023 18:47:11 +0200 Subject: [PATCH 151/549] Avoid using -f option of readlink early in main script --- Hybrid-handler | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Hybrid-handler b/Hybrid-handler index a6c3618..cddbc37 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -51,7 +51,31 @@ function Setup_Initial_And_Final_Output_Space() function Define_Repository_Global_Path() { - readonly HYBRID_top_level_path=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")") + # NOTE: This function would reduce to one line using 'readlink -f'. However this function + # is meant to be called very early and we want to make as few assumptions on the system + # as possible. On macOS the -f option has been added in macOS 12.3 (early 2022) and this + # might be problematic. Hence we basically only use 'readlink' to resolve possible + # symbolic links. Note that we require 'readlink' to exist as this was introduced in + # OpenBSD and GNU around 2000, see comments to https://unix.stackexchange.com/a/136527/370049. + if ! hash readlink &> /dev/null; then + exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + "No 'readlink' command found. Please install it and run the Hybrid-handler again." + fi + local source directory + source=${BASH_SOURCE[0]} + while [[ -L "${source}" ]]; do # resolve ${source} until the file is no longer a symlink + # Find real directory following possible directory symlinks + directory=$(cd -P "$(dirname "${source}")" &> /dev/null && pwd) + source=$(readlink "${source}") + # NOTE: If ${source} was a relative symlink, we need to resolve it relative + # to the path where the symlink file was located. This is needed to ensure + # that readlink still works when the script is invoked from a different + # position from where it is located. + if [[ "${source}" != /* ]]; then + source=${directory}/${source} + fi + done + readonly HYBRID_top_level_path=$(cd -P "$(dirname "${source}")" &> /dev/null && pwd) } function Store_Command_Line_Options_Into_Global_Variable() From 0a2bf63c8c2cb939b2178b037b7b59499d7f6146 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 17 Jul 2023 11:26:24 +0200 Subject: [PATCH 152/549] Extract tests common functionality into utility function --- tests/functional_tests.bash | 5 +--- tests/tests_runner | 3 ++- tests/unit_tests.bash | 16 +----------- tests/utility_functions.bash | 48 ++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 20 deletions(-) create mode 100644 tests/utility_functions.bash diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index a1f9477..22bf06c 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -9,10 +9,7 @@ function Define_Available_Tests() { - HYBRIDT_tests_to_be_run=( - 'help-'{1..3} - 'version-'{1,2} - ) + Define_Available_Tests_For 'functional_tests' } function Make_Test_Preliminary_Operations() diff --git a/tests/tests_runner b/tests/tests_runner index 79f48b5..7920665 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -85,10 +85,11 @@ function Source_Needed_Files() source "${HYBRIDT_repository_top_level_path}/bash/error_codes.bash" || exit 1 source "${HYBRIDT_repository_top_level_path}/bash/logger.bash"\ --fd 9 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} - source "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" || exit ${HYBRID_fatal_builtin} local -r files_to_be_sourced=( + "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" "${HYBRIDT_repository_top_level_path}/bash/system_requirements.bash" "${HYBRIDT_tests_folder}/command_line_parser_for_tests.bash" + "${HYBRIDT_tests_folder}/utility_functions.bash" ) local file for file in "${files_to_be_sourced[@]}"; do diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 9af0d3a..1c13eaf 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -9,21 +9,7 @@ function Define_Available_Tests() { - # Source all unit tests files to also deduce existing tests - local file_to_be_sourced files_to_be_sourced - files_to_be_sourced=( - "${HYBRIDT_tests_folder}/"unit_tests_*.bash - ) - for file_to_be_sourced in "${files_to_be_sourced[@]}"; do - Print_Debug 'Sourcing ' --emph "${file_to_be_sourced}" - source "${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} - done - # Available tests are based on functions in this file whose names begins with "Unit_Test__" - HYBRIDT_tests_to_be_run=( - # Here word splitting can split names, no space allowed in function name! - $(grep -hE '^function[[:space:]]+Unit_Test__[-[:alnum:]_:]+\(\)[[:space:]]*$' "${files_to_be_sourced[@]}" |\ - sed -E 's/^function[[:space:]]+Unit_Test__([^(]+)\(\)[[:space:]]*$/\1/') - ) + Define_Available_Tests_For 'unit_tests' } function Make_Test_Preliminary_Operations() diff --git a/tests/utility_functions.bash b/tests/utility_functions.bash new file mode 100644 index 0000000..3a0204e --- /dev/null +++ b/tests/utility_functions.bash @@ -0,0 +1,48 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Define_Available_Tests_For() +{ + case "$1" in + unit_tests ) + local -r files_prefix='unit_tests_' + local -r functions_prefix='Unit_Test__' + ;; + functional_tests ) + local -r files_prefix='functional_tests_' + local -r functions_prefix='Functional_Test__' + ;; + * ) + Print_Internal_And_Exit 'Wrong call to ' --emph "${FUNCNAME}" ' function.' + ;; + esac + # Source all unit tests files to also deduce existing tests + local string_to_restore_nullglob file_to_be_sourced files_to_be_sourced + string_to_restore_nullglob=$(shopt -p nullglob) + shopt -s nullglob + files_to_be_sourced=( + "${HYBRIDT_tests_folder}/${files_prefix}"*.bash + ) + eval "${string_to_restore_nullglob}" # NOTE: This eval usage is fine + if [[ ${#files_to_be_sourced[@]} -eq 0 ]]; then + return + fi + for file_to_be_sourced in "${files_to_be_sourced[@]}"; do + Print_Debug 'Sourcing ' --emph "${file_to_be_sourced}" + source "${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + # Available tests are based on functions in this file whose names begins with "${functions_prefix}" + local -r grep_regex='^function[[:space:]]+'"${functions_prefix}"'[-[:alnum:]_:]+\(\)[[:space:]]*$'\ + sed_regex='^function[[:space:]]+'"${functions_prefix}"'([^(]+)\(\)[[:space:]]*$' + HYBRIDT_tests_to_be_run=( + # Here word splitting can split names, no space allowed in function name! + $(grep -hE "${grep_regex}" "${files_to_be_sourced[@]}" |\ + sed -E 's/'"${sed_regex}"'/\1/') + ) +} From 04912542800ef6e3fd8eabf6336f6a2ac55ac3e5 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 17 Jul 2023 13:31:39 +0200 Subject: [PATCH 153/549] Use larger default for COLUMNS variable --- bash/system_requirements.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 2ec5e99..44fcc41 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -273,7 +273,7 @@ function __static__Print_Formatted_Binary_Report() # here (see https://stackoverflow.com/a/48016366/14967071 for more information). shopt -s checkwinsize # Do not assume it is on (as it usually is) ( : ) # Refresh LINES and COLUMNS, this happens when a child process exits - local -r num_cols=$(( ${COLUMNS-80} / 2 / single_field_length )) + local -r num_cols=$(( ${COLUMNS-100} / 2 / single_field_length )) local index printf_descriptor printf_descriptor="%${single_field_length}s" # At least one column for ((index=1; index Date: Mon, 17 Jul 2023 13:33:28 +0200 Subject: [PATCH 154/549] Run functional tests as done for units ones --- tests/functional_tests.bash | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index 22bf06c..90f7c56 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -14,16 +14,21 @@ function Define_Available_Tests() function Make_Test_Preliminary_Operations() { - : # No-op for the moment + { + # Write header to the log file to give some structure to it + printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 + } &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } function Run_Test() { local test_name=$1 - return 0 # Success by definition for the moment + Functional_Test__$1 &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } function Clean_Tests_Environment_For_Following_Test() { - : # No-op for the moment + # The fd 9 is used by the logger + Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 9>&1 } From 41b50359d1065942ab969f470df74852459ba8a2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 17 Jul 2023 13:41:25 +0200 Subject: [PATCH 155/549] Add help and version functional tests --- CONTRIBUTING.md | 4 +++- tests/functional_tests.bash | 5 +++++ tests/functional_tests_help.bash | 20 ++++++++++++++++++++ tests/functional_tests_version.bash | 21 +++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 tests/functional_tests_help.bash create mode 100644 tests/functional_tests_version.bash diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0578a31..d150a2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -89,4 +89,6 @@ Here, instead, a list of aspects specific to tests that should be kept in mind: * unit tests must be put in files whose names begin with `unit_tests_` and have the `.bash` extension (this convention allows the runner to source them all automatically) in the ***tests*** folder; * unit tests are automatically recognized by the tests runner as functions having the `Unit_Test__` prefix (and the remaining part of the function will be the unit test name); * operations to be done before or after a unit test can be put in the `Make_Test_Preliminary_Operations__[test-name]` and `Clean_Tests_Environment_For_Following_Test__[test-name]` functions, respectively (here `[test-name]` must match the string used in the unit test function name); -* codebase functions to be invoked in unit tests should be called through the `Call_Codebase_Function` and `Call_Codebase_Function_In_Subshell` interface functions (passing the name of the function to be invoked as first argument and the arguments to be forward afterwards). +* codebase functions to be invoked in unit tests should be called through the `Call_Codebase_Function` and `Call_Codebase_Function_In_Subshell` interface functions (passing the name of the function to be invoked as first argument and the arguments to be forward afterwards); +* functional tests work analogously to unit tests, with the only differences that they have to be put in files with a `functional_tests_` prefix and implemented in bash functions starting by `Functional_Test__`; +* in functional tests you'll probably want to run the hybrid handler with some options and this can be easily achieved by using the `Run_Hybrid_Handler_With_Given_Options_In_Subshell` function. diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index 90f7c56..b8513b7 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -32,3 +32,8 @@ function Clean_Tests_Environment_For_Following_Test() # The fd 9 is used by the logger Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 &>> "${HYBRIDT_log_file}" 9>&1 } + +function Run_Hybrid_Handler_With_Given_Options_In_Subshell() +{ + ( "${HYBRIDT_repository_top_level_path}/Hybrid-handler" "$@" ) +} diff --git a/tests/functional_tests_help.bash b/tests/functional_tests_help.bash new file mode 100644 index 0000000..a07cbcd --- /dev/null +++ b/tests/functional_tests_help.bash @@ -0,0 +1,20 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# NOTE: These functional tests just require code to run and finish with zero exit code. + +function Functional_Test__help-general() +{ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'help' +} + +function Functional_Test__help-do() +{ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '--help' +} diff --git a/tests/functional_tests_version.bash b/tests/functional_tests_version.bash new file mode 100644 index 0000000..71178ac --- /dev/null +++ b/tests/functional_tests_version.bash @@ -0,0 +1,21 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Functional_Test__version() +{ + local version_output git_describe + version_output=$(Run_Hybrid_Handler_With_Given_Options_In_Subshell 'version') + git_describe=$(git -C "${HYBRIDT_repository_top_level_path}" describe --abbrev=0) + printf "${version_output}\n" + if [[ $(grep -c "${git_describe}" <<< "${version_output}") -gt 0 ]]; then + return 0 + else + Print_Error 'Version string is not containing ' --emph "${git_describe}" ' as expected.' + fi +} From 8e857b909a17dc0c7ca9eb0845d40f0eb01e5df3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 17 Jul 2023 15:44:09 +0200 Subject: [PATCH 156/549] Fix bug in tests runner if run without command line options --- tests/command_line_parser_for_tests.bash | 5 +++-- tests/tests_runner | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index af8bb39..4e740d1 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -13,8 +13,9 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() suite_name="$1" if [[ ! ${suite_name} =~ ^(functional|unit)$ ]]; then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ - 'Invalid tests type ' --emph "${suite_name}" '. Valid values: ' --emph 'functional'\ - ', ' --emph 'unit' '.' 'Use the ' --emph '--help' ' option to get more information.' + 'Invalid tests type ' --emph "${suite_name:-}" '. Valid values: '\ + --emph 'unit' ' or ' --emph 'functional' '.'\ + 'Use the ' --emph '--help' ' option to get more information.' fi code_filename="${HYBRIDT_tests_folder}/${suite_name}_tests.bash" if [[ ! -f "${code_filename}" ]]; then diff --git a/tests/tests_runner b/tests/tests_runner index 7920665..7b59f46 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -30,7 +30,7 @@ function Main() Enable_Global_Needed_Shell_Options Source_Needed_Files Print_Helper_And_Exit_If_Requested "$@" - Parse_Tests_Suite_Parameter_And_Source_Specific_Code "$1" + Parse_Tests_Suite_Parameter_And_Source_Specific_Code "${1-}" Check_System_Requirements Call_through_interface 'Define_Available_Tests' Parse_Tests_Command_Line_Options "${@:2}" From 133dceba9e2befb519ce8ebee3d4003acd6bc7fd Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 17 Jul 2023 16:01:55 +0200 Subject: [PATCH 157/549] Fix minor bug in version execution mode --- bash/version.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/version.bash b/bash/version.bash index 506d699..3e412f3 100644 --- a/bash/version.bash +++ b/bash/version.bash @@ -13,8 +13,8 @@ function Print_Software_Version() # First handle cases where git is not available, or codebase downloaded as archive and not cloned # NOTE: Git introduced -C option in version 1.8.5 if ! hash git &> /dev/null ||\ - ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null ||\ - __static__Is_Git_Version_Older_Than '1.8.3'; then + __static__Is_Git_Version_Older_Than '1.8.3' ||\ + ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null; then __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" return 0 fi From 95706adecb533f8e67deabf5e4a9f10adff5873d Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 17 Jul 2023 16:02:39 +0200 Subject: [PATCH 158/549] Add type of test in tests log-file output --- tests/functional_tests.bash | 2 +- tests/unit_tests.bash | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index b8513b7..7de9f7e 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -16,7 +16,7 @@ function Make_Test_Preliminary_Operations() { { # Write header to the log file to give some structure to it - printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" + printf "\n[$(date)]\nRunning functional test \"%s\"\n\n" "${test_name}" Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 } &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 1c13eaf..7218924 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -20,7 +20,7 @@ function Make_Test_Preliminary_Operations() # and since it is likely that most unit tests need it, let's always define it readonly HYBRID_top_level_path="${HYBRIDT_repository_top_level_path}" # Write header to the log file to give some structure to it - printf "\n[$(date)]\nRunning test \"%s\"\n\n" "${test_name}" + printf "\n[$(date)]\nRunning unit test \"%s\"\n\n" "${test_name}" Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 } &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } From 1d5855aee8aacd667d6c5563700cd208d2002628 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 17 Jul 2023 16:03:03 +0200 Subject: [PATCH 159/549] Avoid running version functional test if git is not found --- tests/functional_tests_version.bash | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/functional_tests_version.bash b/tests/functional_tests_version.bash index 71178ac..ca32f87 100644 --- a/tests/functional_tests_version.bash +++ b/tests/functional_tests_version.bash @@ -11,11 +11,19 @@ function Functional_Test__version() { local version_output git_describe version_output=$(Run_Hybrid_Handler_With_Given_Options_In_Subshell 'version') - git_describe=$(git -C "${HYBRIDT_repository_top_level_path}" describe --abbrev=0) - printf "${version_output}\n" - if [[ $(grep -c "${git_describe}" <<< "${version_output}") -gt 0 ]]; then - return 0 + if [[ $? -ne 0 ]]; then + Print_Fatal_And_Exit 'Execution of version mode unexpectedly failed.' + fi + if ! hash git &> /dev/null; then + Print_Warning 'Command ' --emph 'git' ' not available, part of test cannot be run.' else - Print_Error 'Version string is not containing ' --emph "${git_describe}" ' as expected.' + git_describe=$(cd "${HYBRIDT_repository_top_level_path}"; git describe --abbrev=0) + printf "${version_output}\n" + if [[ $(grep -c "${git_describe}" <<< "${version_output}") -gt 0 ]]; then + return 0 + else + Print_Error 'Version string is not containing ' --emph "${git_describe}" ' as expected.' + return 1 + fi fi } From 7078a4433a52e4bd10509c79a6998a96010d4d17 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 18 Jul 2023 13:31:26 +0200 Subject: [PATCH 160/549] Source missing files in codebase --- bash/source_codebase_files.bash | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 1ccc4e6..85251ee 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -19,12 +19,18 @@ function __static__Source_Codebase_Files() # when sourcing the codebase files (otherwise this would not work) source "${HYBRID_top_level_path}/bash/utility_functions.bash" || exit ${HYBRID_fatal_builtin} list_of_files=( + 'Afterburner_functionality.bash' 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' 'command_line_parsers/sub_parser.bash' 'configuration_parser.bash' 'dispatch_functions.bash' 'global_variables.bash' + 'Hydro_functionality.bash' + 'IC_functionality.bash' + 'Sampler_functionality.bash' + 'sanity_checks.bash' + 'software_input_functionality.bash' 'system_requirements.bash' 'version.bash' ) From 149ca30a8c9b2fd9e5e7d53918a0e239c59a3536 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 18 Jul 2023 13:32:51 +0200 Subject: [PATCH 161/549] Consider executable name as global path or command --- bash/IC_functionality.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 4c64e25..48597f5 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -40,7 +40,7 @@ function Ensure_All_Needed_Input_Exists_IC() function Run_Software_IC() { local ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" - ./"${HYBRID_software_executable[IC]}" \ + "${HYBRID_software_executable[IC]}" \ '-i' "${HYBRID_software_configuration_file[IC]}" \ '-o' "${HYBRID_software_output_directory[IC]}" \ '-n' \ From a9b93e6fb054d9bd30270cc82bd220b3cd69bd68 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 18 Jul 2023 14:28:05 +0200 Subject: [PATCH 162/549] Use environment variable to toggle IC black-box failures As the IC software is at the moment run with fixed command line options, we cannot use command line options to let the black-box mock software fail. Environment variables are better suited in this case. --- tests/mocks/smash_IC_black-box.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/mocks/smash_IC_black-box.py b/tests/mocks/smash_IC_black-box.py index aee4270..d716d3d 100755 --- a/tests/mocks/smash_IC_black-box.py +++ b/tests/mocks/smash_IC_black-box.py @@ -3,16 +3,17 @@ import argparse import os import sys +import textwrap import time def check_config(valid_config): if args.i is None: args.i = "./config.yaml" if not os.path.exists(args.i): - print(fatal_error+"The configuration file was expected at './config.yaml', but the file does not exist.") + print(fatal_error + "The configuration file was expected at '" + args.i + "', but the file does not exist.") sys.exit(1) if not valid_config: - print(fatal_error+"Validation of SMASH input failed.") + print(fatal_error + "Validation of SMASH input failed.") sys.exit(1) return @@ -90,28 +91,29 @@ def finish(): else: print("somehow the output file (.dat) was not properly written") sys.exit(1) - + # remove smash.lock file os.remove(args.o+file_name_is_running) return if __name__ == '__main__': - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, + epilog=textwrap.dedent(''' + Use the BLACK_BOX_FAIL environment variable set to either "invalid_config" + or to "smash_crashes" to mimic a particular failure in the black box. + ''')) parser.add_argument("-i", required=False, help="File to the config.yaml") parser.add_argument("-o", required=False, help="Path to the output folder") parser.add_argument("-c", required=False, help="Make changes to config.yaml (this is not tested here)") - parser.add_argument("--fail_with", required=False, - default=None, - choices=["invalid_config", "smash_crashes"], - help="Choose a place where SMASH should fail") - + parser.add_argument("-n", required=False, default=False, action='store_true', + help="As SMASH -n (this is not affecting any behavior here)") args = parser.parse_args() - config_is_valid = args.fail_with != "invalid_config" - smash_finishes = args.fail_with != "smash_crashes" + config_is_valid = os.environ.get('BLACK_BOX_FAIL') != "invalid_config" + smash_finishes = os.environ.get('BLACK_BOX_FAIL') != "smash_crashes" fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " file_name_is_running = "smash.lock" @@ -130,4 +132,4 @@ def finish(): # smash is now ready to run print_terminal_start() - run_smash(smash_finishes) \ No newline at end of file + run_smash(smash_finishes) From 7c2cf6a06a12fa8f2c54fdc3a97910f7445c136e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 18 Jul 2023 14:32:10 +0200 Subject: [PATCH 163/549] Implement functional test for IC only run --- tests/functional_tests_IC_only.bash | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/functional_tests_IC_only.bash diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash new file mode 100644 index 0000000..dc11441 --- /dev/null +++ b/tests/functional_tests_IC_only.bash @@ -0,0 +1,70 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# NOTE: These functional tests just require code to run and finish with zero exit code. + +function Functional_Test__do-IC-only() +{ + shopt -s nullglob + local -r config_filename='IC_config.yaml' + local unfinished_files output_files terminal_output_file failure_message + printf ' + IC: + Executable: %s/tests/mocks/smash_IC_black-box.py + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + # Expect success and test absence of "SMASH" unfinished file + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + unfinished_files=( IC/*.unfinished ) + output_files=( IC/* ) + if [[ ${#unfinished_files[@]} -lt 0 ]]; then + Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' + return 1 + elif [[ ${#output_files[@]} -ne 5 ]]; then + Print_Error 'Expected ' --emph '5' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'IC' 'IC-success' + # Expect failure and test "SMASH" message + Print_Info 'Running Hybrid-handler expecting invalid IC input file failure' + terminal_output_file='IC/Terminal_Output.txt' + BLACK_BOX_FAIL='invalid_config'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input.' + return 1 + elif [[ ! -f "${terminal_output_file}" ]]; then + Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' + return 1 + fi + failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') + if [[ "${failure_message}" != 'Validation of SMASH input failed.' ]]; then + Print_Error 'Unexpected failure message: ' --emph "${failure_message}" + return 1 + fi + mv 'IC' 'IC-invalid-config' + # Expect failure and test "SMASH" unfinished/lock files + Print_Info 'Running Hybrid-handler expecting crash in IC software' + BLACK_BOX_FAIL='smash_crashes'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with IC software crashing.' + return 1 + fi + unfinished_files=( IC/*.{unfinished,lock} ) + if [[ ${#unfinished_files[@]} -ne 3 ]]; then + Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." + return 1 + fi + mv 'IC' 'IC-software-crash' +} From 93c25e2262c81f8ad949cc5d055d788a0b86cf32 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 18 Jul 2023 14:36:47 +0200 Subject: [PATCH 164/549] Fix bug in sanity checks due to hard-coded string --- bash/sanity_checks.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 84c0b22..901f12b 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -12,10 +12,10 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var local key base_file for key in "${HYBRID_valid_software_configuration_sections[@]}"; do if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then - __static__Ensure_Executable_Exists 'IC' - HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/${key}" + __static__Ensure_Executable_Exists "${key}" + HYBRID_software_output_directory[${key}]="${HYBRID_output_directory}/${key}" base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") - HYBRID_software_configuration_file[IC]="${HYBRID_software_output_directory[${key}]}/${base_file}" + HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" fi done readonly HYBRID_software_output_directory HYBRID_software_configuration_file From 58d5a4a77008472b876c9f86bbc3638f4508ce79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Thu, 26 Oct 2023 13:20:35 +0200 Subject: [PATCH 165/549] Add sampler blackbox --- tests/mocks/sampler_black_box.py | 172 +++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 tests/mocks/sampler_black_box.py diff --git a/tests/mocks/sampler_black_box.py b/tests/mocks/sampler_black_box.py new file mode 100644 index 0000000..66c3c59 --- /dev/null +++ b/tests/mocks/sampler_black_box.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +# README: +# The sampler black box is called like: +# +# ./sampler_black_box events NUM PATH_TO_CONFIG_FILE +# +# with: +# - events: This is not a variable. This must be the string 'events' +# - NUM: random number set by the user. In hybrid NUM=1 +# - PATH_TO_CONFIG_FILE: Path to the sampler configuration + +# How it works: +# The sampler black box starts by checking if the sampler config exists. +# If it is the case, it gets the path to the freezeout hypersurface and the output +# directory from the config and checks that also the freezeout surface exists. +# +# Use the BLACK_BOX_FAIL environment variable set to "fail" +# to mimic a particular failure in the black box. +# +# If it got everything correctly, it will produce a dummy terminal output and +# a) particle_lists.oscar in the output directory if the optional argument is not given +# b) nothing if the BLACK_BOX_FAIL environment variable is set to "fail" + +import sys +import os.path +import numpy as np +import time +import random + +def check_input_arguments(): + calling_instruction = 'Call the sampler black box by:\n\n' +\ + './sampler_black_box.py events NUM PATH_TO_CONFIG_FILE \n\n' +\ + '- events: This is not a variable. This must be the string "events"\n' +\ + '- NUM: random number set by the user. In hybrid NUM=1\n' +\ + '- PATH_TO_CONFIG_FILE: Path to the sampler configuration\n' + + if len(sys.argv) < 4 or len(sys.argv) > 5: + err_msg = 'Invalid number of arguments!\n' + calling_instruction + raise IndexError(err_msg) + elif not sys.argv[1] == 'events': + err_msg = 'Invalid first argument passed!\n' +\ + 'The first argument must be the string "events"\n' + calling_instruction + raise NameError(err_msg) + + +def check_if_file_exists(PATH): + if os.path.isfile(PATH): + pass + else: + err_msg = 'Input file not found at given path: ' + PATH + raise RuntimeError(err_msg) + +def check_if_directory_exists(PATH): + if os.path.isdir(PATH): + pass + else: + err_msg = PATH + ' is not a valid directory or does not exist!' + raise RuntimeError(err_msg) + +def split_line_in_config_and_remove_spaces(line_in_config): + line_in_config = line_in_config.replace('\n','').split(' ') + splitted_line_in_config = list(filter(None, line_in_config)) + splitted_line_in_config[0]=str(splitted_line_in_config[0]) + splitted_line_in_config[1]=str(splitted_line_in_config[1]) + return splitted_line_in_config + +def get_value_as_string_from_config_by_keyword(PATH_TO_CONFIG, keyword): + #Valid keywords: + #surface, spectra_dir, number_of_events, weakContribution, shear, ecrit + file = open(PATH_TO_CONFIG) + + while True: + line_in_config = file.readline() + if not line_in_config: + exception_message = 'Keyword '+str(keyword)+' is not contained in the config file!' + raise Exception(exception_message) + else: + splitted_line_in_config = split_line_in_config_and_remove_spaces(line_in_config) + key_in_config_line = splitted_line_in_config[0] + value_of_key_in_config_line = splitted_line_in_config[1] + + if key_in_config_line == str(keyword): + return value_of_key_in_config_line + + +def write_terminal_output(): + header ="\n"+\ + " _____ __ __ _____ _ ______ _____ ____ _ _____ _ __ ____ ______ __\n"+\ + " / ____| /\ | \/ | __ \| | | ____| __ \ | _ \| | /\ / ____| |/ / | _ \ / __ \ \ / /\n"+\ + " | (___ / \ | \ / | |__) | | | |__ | |__) | | |_) | | / \ | | | ' / | |_) | | | \ V / \n"+\ + " \___ \ / /\ \ | |\/| | ___/| | | __| | _ / | _ <| | / /\ \| | | < | _ <| | | |> < \n"+\ + " ____) / ____ \| | | | | | |____| |____| | \ \ | |_) | |____ / ____ \ |____| . \ | |_) | |__| / . \ \n"+\ + " |_____/_/ \_\_| |_|_| |______|______|_| \_\ |____/|______/_/ \_\_____|_|\_\ |____/ \____/_/ \_\ \n"+\ + " | | | | / _| | | | | | | \n"+\ + " | |_| |__ ___ | |_ _ _| |_ _ _ _ __ ___ ___| |_ __ _ _ __| |_ ___ _ __ _____ __ \n"+\ + " | __| '_ \ / _ \ | _| | | | __| | | | '__/ _ \ / __| __/ _` | '__| __/ __| | '_ \ / _ \ \ /\ / / \n"+\ + " | |_| | | | __/ | | | |_| | |_| |_| | | | __/ \__ \ || (_| | | | |_\__ \ | | | | (_) \ V V / \n"+\ + " \__|_| |_|\___| |_| \__,_|\__|\__,_|_| \___| |___/\__\__,_|_| \__|___/ |_| |_|\___/ \_/\_/ \n" + + print(header) + for i in range(11): + print('Fake computational progress: ', int(10*i), '%') + time.sleep(0.1) + + +def create_output_file(OUTPUT_DIR): + header = '#!OSCAR2013 particle_lists t x y z mass p0 px py pz pdg ID charge\n' +\ + '# Units: fm fm fm fm GeV GeV GeV GeV GeV none none e\n' +\ + '# SMASH-3.0-1-g0985d6b3\n' + format_oscar2013 = '%g %g %g %g %g %g %g %g %g %d %d %d' + + output_file = OUTPUT_DIR+'/particle_lists_test.oscar' + + file = open(output_file, 'w') + file.write(header) + file.close() + + with open(output_file, "a") as f_out: + for counter_event in range(4): + num_output_of_event = random.randint(10, 20) + output_event = [] + + for counter_output in range(num_output_of_event): + t = 200.0 + x = 0.1*random.randint(-1000, 1000) + y = 0.1*random.randint(-1000, 1000) + z = 0.1*random.randint(-1000, 1000) + mass = 0.1*random.randint(0, 100) + p0 = 0.001*random.randint(0, 10000) + px = 0.001*random.randint(-10000, 10000) + py = 0.001*random.randint(-100, 10000) + pz = 0.001*random.randint(-10000, 10000) + pdg = random.randint(0, 10000) + ID = random.randint(0, 100) + charge = random.randint(-2, 2) + + output_line = [t, x, y, z, mass, p0, px, py, pz, pdg, ID, charge] + output_event.append(output_line) + + output_event = np.asarray(output_event) + + + f_out.write('# event '+ str(counter_event)+' out '+ str(num_output_of_event)+'\n') + np.savetxt(f_out, output_event, delimiter=' ', newline='\n', fmt=format_oscar2013) + f_out.write('# event '+ str(counter_event)+' end 0 impact 0.000 scattering_projectile_target yes\n') + + +#################### End Definitions #################### + +if __name__=='__main__': + + sampler_finishes = os.environ.get('BLACK_BOX_FAIL') != 'fail' + + check_input_arguments() + + string_events = sys.argv[1] + random_number = sys.argv[2] + PATH_TO_CONFIG = sys.argv[3] + + PATH_TO_FREEZEOUT = get_value_as_string_from_config_by_keyword(PATH_TO_CONFIG, 'surface') + OUTPUT_DIR = get_value_as_string_from_config_by_keyword(PATH_TO_CONFIG, 'spectra_dir') + + check_if_file_exists(PATH_TO_CONFIG) + check_if_file_exists(PATH_TO_FREEZEOUT) + check_if_directory_exists(OUTPUT_DIR) + + if sampler_finishes: + write_terminal_output() + create_output_file(OUTPUT_DIR) + else: + exit(1) From 06dbd2bb7d5edffecf9ae9a28c4bcf96e9a4be07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Thu, 26 Oct 2023 13:25:16 +0200 Subject: [PATCH 166/549] Add Nils to AUTHORS list --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index c5a7d37..cafd8a4 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -5,6 +5,7 @@ | Alessandro Sciarra | asciarra@fias.uni-frankfurt.de | | Jan Hammelmann | hammelmann@fias.uni-frankfurt.de | | Niklas Götz | goetz@fias.uni-frankfurt.de | +| Nils Saß | nsass@fias.uni-frankfurt.de | | Renan Hirayama | hirayama@fias.uni-frankfurt.de | | Zuzana Paulinyova | zuzana.paulinyova@upjs.sk | From b321493e24ff254850e914e287b064d249226ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Mon, 30 Oct 2023 15:08:28 +0100 Subject: [PATCH 167/549] Improve black box code and make it executable --- tests/mocks/sampler_black_box.py | 79 ++++++++++++++++---------------- 1 file changed, 40 insertions(+), 39 deletions(-) mode change 100644 => 100755 tests/mocks/sampler_black_box.py diff --git a/tests/mocks/sampler_black_box.py b/tests/mocks/sampler_black_box.py old mode 100644 new mode 100755 index 66c3c59..b011e98 --- a/tests/mocks/sampler_black_box.py +++ b/tests/mocks/sampler_black_box.py @@ -3,12 +3,12 @@ # README: # The sampler black box is called like: # -# ./sampler_black_box events NUM PATH_TO_CONFIG_FILE +# ./sampler_black_box events NUM path_to_config_FILE # # with: # - events: This is not a variable. This must be the string 'events' # - NUM: random number set by the user. In hybrid NUM=1 -# - PATH_TO_CONFIG_FILE: Path to the sampler configuration +# - path_to_config_FILE: Path to the sampler configuration # How it works: # The sampler black box starts by checking if the sampler config exists. @@ -30,53 +30,54 @@ def check_input_arguments(): calling_instruction = 'Call the sampler black box by:\n\n' +\ - './sampler_black_box.py events NUM PATH_TO_CONFIG_FILE \n\n' +\ + './sampler_black_box.py events num path_to_config_file \n\n' +\ '- events: This is not a variable. This must be the string "events"\n' +\ '- NUM: random number set by the user. In hybrid NUM=1\n' +\ - '- PATH_TO_CONFIG_FILE: Path to the sampler configuration\n' + '- path_to_config_file: Path to the sampler configuration\n' if len(sys.argv) < 4 or len(sys.argv) > 5: err_msg = 'Invalid number of arguments!\n' + calling_instruction - raise IndexError(err_msg) + print(err_msg) + sys.exit(1) elif not sys.argv[1] == 'events': err_msg = 'Invalid first argument passed!\n' +\ 'The first argument must be the string "events"\n' + calling_instruction - raise NameError(err_msg) + print(err_msg) + sys.exit(1) -def check_if_file_exists(PATH): - if os.path.isfile(PATH): - pass - else: - err_msg = 'Input file not found at given path: ' + PATH - raise RuntimeError(err_msg) +def check_if_file_exists(path): + if not os.path.isfile(path): + err_msg = 'File not found at given path: ' + path + print(err_msg) + sys.exit(1) -def check_if_directory_exists(PATH): - if os.path.isdir(PATH): - pass - else: - err_msg = PATH + ' is not a valid directory or does not exist!' - raise RuntimeError(err_msg) +def check_if_directory_exists(path): + if not os.path.isdir(path): + err_msg = path + ' is not a valid directory or does not exist!' + print(err_msg) + sys.exit(1) -def split_line_in_config_and_remove_spaces(line_in_config): - line_in_config = line_in_config.replace('\n','').split(' ') +def get_first_two_fields_in_line(line_in_config): + line_in_config = line_in_config[:-1].split(' ') splitted_line_in_config = list(filter(None, line_in_config)) splitted_line_in_config[0]=str(splitted_line_in_config[0]) splitted_line_in_config[1]=str(splitted_line_in_config[1]) return splitted_line_in_config -def get_value_as_string_from_config_by_keyword(PATH_TO_CONFIG, keyword): +def get_value_as_string_from_config_by_keyword(path_to_config, keyword): #Valid keywords: #surface, spectra_dir, number_of_events, weakContribution, shear, ecrit - file = open(PATH_TO_CONFIG) + file = open(path_to_config) while True: line_in_config = file.readline() if not line_in_config: exception_message = 'Keyword '+str(keyword)+' is not contained in the config file!' - raise Exception(exception_message) + print(exception_message) + sys.exit(1) else: - splitted_line_in_config = split_line_in_config_and_remove_spaces(line_in_config) + splitted_line_in_config = get_first_two_fields_in_line(line_in_config) key_in_config_line = splitted_line_in_config[0] value_of_key_in_config_line = splitted_line_in_config[1] @@ -84,7 +85,7 @@ def get_value_as_string_from_config_by_keyword(PATH_TO_CONFIG, keyword): return value_of_key_in_config_line -def write_terminal_output(): +def make_fake_run(): header ="\n"+\ " _____ __ __ _____ _ ______ _____ ____ _ _____ _ __ ____ ______ __\n"+\ " / ____| /\ | \/ | __ \| | | ____| __ \ | _ \| | /\ / ____| |/ / | _ \ / __ \ \ / /\n"+\ @@ -104,13 +105,13 @@ def write_terminal_output(): time.sleep(0.1) -def create_output_file(OUTPUT_DIR): +def create_output_file(output_dir): header = '#!OSCAR2013 particle_lists t x y z mass p0 px py pz pdg ID charge\n' +\ '# Units: fm fm fm fm GeV GeV GeV GeV GeV none none e\n' +\ '# SMASH-3.0-1-g0985d6b3\n' format_oscar2013 = '%g %g %g %g %g %g %g %g %g %d %d %d' - output_file = OUTPUT_DIR+'/particle_lists_test.oscar' + output_file = output_dir+'/particle_lists_test.oscar' file = open(output_file, 'w') file.write(header) @@ -150,23 +151,23 @@ def create_output_file(OUTPUT_DIR): if __name__=='__main__': - sampler_finishes = os.environ.get('BLACK_BOX_FAIL') != 'fail' + sampler_finishes = os.environ.get('BLACK_BOX_FAIL') != 'true' check_input_arguments() - string_events = sys.argv[1] - random_number = sys.argv[2] - PATH_TO_CONFIG = sys.argv[3] + path_to_config = sys.argv[3] + + check_if_file_exists(path_to_config) - PATH_TO_FREEZEOUT = get_value_as_string_from_config_by_keyword(PATH_TO_CONFIG, 'surface') - OUTPUT_DIR = get_value_as_string_from_config_by_keyword(PATH_TO_CONFIG, 'spectra_dir') + path_to_freezeout = get_value_as_string_from_config_by_keyword(path_to_config, 'surface') + output_dir = get_value_as_string_from_config_by_keyword(path_to_config, 'spectra_dir') - check_if_file_exists(PATH_TO_CONFIG) - check_if_file_exists(PATH_TO_FREEZEOUT) - check_if_directory_exists(OUTPUT_DIR) + check_if_file_exists(path_to_freezeout) + check_if_directory_exists(output_dir) if sampler_finishes: - write_terminal_output() - create_output_file(OUTPUT_DIR) + make_fake_run() + create_output_file(output_dir) else: - exit(1) + print("Sampler black-box crashed!") + sys.exit(1) From 35c51e1b1f9c98bb6d2630c20613e71823d002f6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 31 Oct 2023 15:33:48 +0100 Subject: [PATCH 168/549] Fix wrong comparison operator in IC functional test --- tests/functional_tests_IC_only.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index dc11441..24d9ca4 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -27,7 +27,7 @@ function Functional_Test__do-IC-only() fi unfinished_files=( IC/*.unfinished ) output_files=( IC/* ) - if [[ ${#unfinished_files[@]} -lt 0 ]]; then + if [[ ${#unfinished_files[@]} -gt 0 ]]; then Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' return 1 elif [[ ${#output_files[@]} -ne 5 ]]; then From 013d70d4eb0de356cec0baece6513cabaa3f47da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Tue, 31 Oct 2023 16:20:43 +0100 Subject: [PATCH 169/549] Improve code documentation --- tests/mocks/sampler_black_box.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/mocks/sampler_black_box.py b/tests/mocks/sampler_black_box.py index b011e98..c9b2cf4 100755 --- a/tests/mocks/sampler_black_box.py +++ b/tests/mocks/sampler_black_box.py @@ -3,24 +3,24 @@ # README: # The sampler black box is called like: # -# ./sampler_black_box events NUM path_to_config_FILE +# ./sampler_black_box events num path_to_config_file # # with: # - events: This is not a variable. This must be the string 'events' -# - NUM: random number set by the user. In hybrid NUM=1 -# - path_to_config_FILE: Path to the sampler configuration +# - num: random number set by the user. In hybrid NUM=1 +# - path_to_config_file: Path to the sampler configuration # How it works: # The sampler black box starts by checking if the sampler config exists. # If it is the case, it gets the path to the freezeout hypersurface and the output # directory from the config and checks that also the freezeout surface exists. # -# Use the BLACK_BOX_FAIL environment variable set to "fail" +# Use the BLACK_BOX_FAIL environment variable set to "true" # to mimic a particular failure in the black box. # # If it got everything correctly, it will produce a dummy terminal output and # a) particle_lists.oscar in the output directory if the optional argument is not given -# b) nothing if the BLACK_BOX_FAIL environment variable is set to "fail" +# b) nothing if the BLACK_BOX_FAIL environment variable is set to "true" import sys import os.path @@ -32,7 +32,7 @@ def check_input_arguments(): calling_instruction = 'Call the sampler black box by:\n\n' +\ './sampler_black_box.py events num path_to_config_file \n\n' +\ '- events: This is not a variable. This must be the string "events"\n' +\ - '- NUM: random number set by the user. In hybrid NUM=1\n' +\ + '- num: random number set by the user. In hybrid num=1\n' +\ '- path_to_config_file: Path to the sampler configuration\n' if len(sys.argv) < 4 or len(sys.argv) > 5: @@ -59,6 +59,7 @@ def check_if_directory_exists(path): sys.exit(1) def get_first_two_fields_in_line(line_in_config): + #deleting the last character to omit the newline character '\n' line_in_config = line_in_config[:-1].split(' ') splitted_line_in_config = list(filter(None, line_in_config)) splitted_line_in_config[0]=str(splitted_line_in_config[0]) From a834a7e2dc5338dc6e989f65726d94dd1b0bfb45 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 1 Nov 2023 15:30:44 +0100 Subject: [PATCH 170/549] Fix too strict requirement in input keys TXT replacement --- bash/software_input_functionality.bash | 18 ++++++++-- ...it_tests_software_input_functionality.bash | 33 +++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 507bb3a..1fe54c4 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -67,12 +67,24 @@ function __static__Replace_Keys_Into_Txt_File() exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ 'Keys to be replaced do not seem to contain valid key-value syntax.' fi + local number_of_fields_per_line=( $(awk 'BEGIN{FS=":"}{print NF}' <(printf "${keys_to_be_replaced}\n") | sort -u) ) + if [[ ${#number_of_fields_per_line[@]} -gt 1 ]]; then + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + 'Keys to be replaced do not have consistent colon-terminated keys syntax.' + elif [[ ${number_of_fields_per_line[0]} -gt 2 ]]; then + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + 'Keys to be replaced seem to use more than a colon after key(s).' + fi # NOTE: Since the YAML implementation is very general, here we can take advantage of it - # on constraint of inserting and then removing a ':' after the "key". + # on constraint of inserting (if needed) and then removing a ':' after the "key". awk -i inplace 'BEGIN{OFS=": "}{print $1, $2}' "${base_input_file}" - keys_to_be_replaced=$(awk 'BEGIN{OFS=": "}{print $1, $2}' <<< "${keys_to_be_replaced}") + if [[ ${number_of_fields_per_line[0]} -eq 1 ]]; then + keys_to_be_replaced=$(awk 'BEGIN{OFS=": "}{print $1, $2}' <<< "${keys_to_be_replaced}") + fi __static__Replace_Keys_Into_YAML_File - awk -i inplace 'BEGIN{FS=": "}{printf "%-20s%s\n", $1, $2}' "${base_input_file}" + # NOTE: Using ':' as field separator, spaces after it will be preserved, hence there + # is no worry about having potentially the two fields merged into one in printf. + awk -i inplace 'BEGIN{FS=":"}{printf("%-20s%s\n", $1, $2)}' "${base_input_file}" } diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index df90150..6b2f464 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -113,10 +113,39 @@ function Unit_Test__replace-in-software-input-TXT() Print_Error 'Valid TXT replacement but with non existent key in valid file succeeded' return 1 fi - # Test case 4: + # Test case 5: + printf 'Key Value\n' > "${base_input_file}" + keys_to_be_replaced=$'New_key: value\nOther_key value' + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Invalid TXT replacement with inconsistent colons succeeded' + return 1 + fi + # Test case 6: + printf 'Key Value\n' > "${base_input_file}" + keys_to_be_replaced=$'New_key:: value\nOther_key:: value' + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Invalid TXT replacement with too many colons succeeded' + return 1 + fi + # Test case 7: printf 'a 0.123\nb 0.456\nc 0.789\n' > "${base_input_file}" keys_to_be_replaced=$'a 42\nc 77' - printf -v expected_result "%-20s%s\n" 'a' '42' 'b' '0.456' 'c' '77' + printf -v expected_result "%-20s %s\n" 'a' '42' 'b' '0.456' 'c' '77' + expected_result=${expected_result%?} # Get rid of trailing endline + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File + if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then + Print_Error "YAML replacement failed!"\ + "---- OBTAINED: ----\n$(< "${base_input_file}")"\ + "---- EXPECTED: ----\n${expected_result}"\ + '-------------------' + return 1 + fi + # Test case 8: + printf 'a 0.123\nb 0.456\nvery_very_very_long_key 0.789\n' > "${base_input_file}" + keys_to_be_replaced=$'a: 42\nvery_very_very_long_key: 77' + printf -v expected_result "%-20s %s\n" 'a' '42' 'b' '0.456' 'very_very_very_long_key' '77' expected_result=${expected_result%?} # Get rid of trailing endline Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then From 010826db877f03b72b39762f61ea2822472b7cac Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 2 Nov 2023 10:07:52 +0100 Subject: [PATCH 171/549] Make funtional tester create a folder per functional test --- tests/functional_tests.bash | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index 7de9f7e..1878741 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -17,6 +17,8 @@ function Make_Test_Preliminary_Operations() { # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning functional test \"%s\"\n\n" "${test_name}" + mkdir "$1" || exit ${HYBRID_fatal_builtin} + cd "$1" || exit ${HYBRID_fatal_builtin} Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 } &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } From e343de0c5a3e7fbe7e3ef1338e9127579f7e06e6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 1 Nov 2023 12:34:19 +0100 Subject: [PATCH 172/549] Store output directory given on command line as global path --- bash/command_line_parsers/main_parser.bash | 6 +++++- bash/global_variables.bash | 2 +- bash/system_requirements.bash | 3 ++- tests/unit_tests_command_line_parser.bash | 5 +++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 83824c8..022d59d 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -53,7 +53,11 @@ function Parse_Command_Line_Options() if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else - readonly HYBRID_output_directory=$2 + if ! realpath "$2" &> /dev/null; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Specified output directory ' --emph "$2" ' not found' + fi + readonly HYBRID_output_directory="$(realpath "$2")" fi shift 2 ;; diff --git a/bash/global_variables.bash b/bash/global_variables.bash index c3a81e7..4e1e5b3 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -53,7 +53,7 @@ function Define_Further_Global_Variables() # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' - HYBRID_output_directory='.' + HYBRID_output_directory="$(realpath .)" # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_given_software_sections=() declare -gA HYBRID_software_executable=( diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 44fcc41..9d5d764 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -31,6 +31,7 @@ function __static__Declare_System_Requirements() cut grep head + realpath tail ) declare -rga HYBRID_gnu_programs_required=( awk sed sort wc ) @@ -71,7 +72,7 @@ function Check_System_Requirements_And_Make_Report() { __static__Declare_System_Requirements local system_report=() - local -r single_field_length=16 # This variable is used to prepare the report correctly formatted + local -r single_field_length=18 # This variable is used to prepare the report correctly formatted declare -A system_information # Same use of this variable as in 'Check_System_Requirements' function __static__Analyze_System_Properties __static__Print_Report_Title diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index 1a57541..d676a2f 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -85,6 +85,7 @@ function __static__Test_Single_CLO_Parsing_In_Subshell() ( Call_Codebase_Function Parse_Command_Line_Options if [[ $? -ne 0 ]] || [[ ${!1} != "$2" ]]; then + Print_Debug --emph "${!1}" ' != ' --emph "$2" Print_Error 'Parsing of ' --emph "${HYBRID_command_line_options_to_parse[0]}" ' with valid value failed.' return 1 fi @@ -108,8 +109,8 @@ function Unit_Test__parse-command-line-options() Print_Error 'Parsing of CLO with no CLO failed.' return 1 fi - HYBRID_command_line_options_to_parse=( -o /path/to/dir ) - __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_output_directory '/path/to/dir' || return 1 + HYBRID_command_line_options_to_parse=( -o "${HOME}" ) + __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_output_directory "${HOME}" || return 1 HYBRID_command_line_options_to_parse=( -c /path/to/file ) __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_configuration_file '/path/to/file' || return 1 } From 7236a72fe27008fb420db8e5babec9a29f6f1e5f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 2 Nov 2023 10:15:57 +0100 Subject: [PATCH 173/549] Fix bug about not always setting all software output folders --- bash/sanity_checks.bash | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 901f12b..0517c1c 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -11,9 +11,11 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var { local key base_file for key in "${HYBRID_valid_software_configuration_sections[@]}"; do + # The software output directories are always ALL set, even if not all software is run. This + # is important as some software might rely on files in directories of other workflow blocks. + HYBRID_software_output_directory[${key}]="${HYBRID_output_directory}/${key}" if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then __static__Ensure_Executable_Exists "${key}" - HYBRID_software_output_directory[${key}]="${HYBRID_output_directory}/${key}" base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" fi From cc81173b0397ee3ca9d914c81c0c2991969f37c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 30 May 2023 16:31:21 +0200 Subject: [PATCH 174/549] Initialise Hydro functionality and tests --- bash/Hydro_functionality.bash | 39 +++++++++++++++++++++-- bash/global_variables.bash | 15 ++++++++- tests/unit_tests_Hydro_functionality.bash | 20 ++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 tests/unit_tests_Hydro_functionality.bash diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 011891c..7a71f8f 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -9,17 +9,50 @@ function Prepare_Software_Input_File_Hydro() { - Print_Not_Implemented_Function_Error + # check if output-dir is there and create + local Hydro_output_directory="${HYBRID_output_directory}/Hydro" # this could also be a global variable + if [[ ! -d "${Hydro_output_directory}" ]]; then + mkdir "${Hydro_output_directory}" + fi + # check if config already exists in the output directory, exit if yes + local Hydro_input_file_path="${Hydro_output_directory}/vhlle_config" + if [[ ! -f "${Hydro_input_file_path}" ]]; then + cp "${HYBRID_software_base_config_file['Hydro']}" "${Hydro_input_file_path}" || exit ${HYBRID_fatal_builtin} + else + exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + "File \"${Hydro_input_file_path}\" is already there." + fi + + # replace all fields with input configs + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + 'TXT' "${Hydro_input_file_path}" "${HYBRID_software_input['Hydro']}" } function Ensure_All_Needed_Input_Exists_Hydro() { - Print_Not_Implemented_Function_Error + #check that input lists exists + local IC_output_directory="${HYBRID_output_directory}/IC" # this could also be a global variable + if [[ ! -d "${IC_output_directory}" ]]; then + mkdir "${IC_output_directory}" + fi + # check if input exists + local IC_output_file_path="${IC_output_directory}/SMASH_IC.dat" + if [[ ! -f "${IC_output_file_path}" ]]; then + exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + "File \"${IC_output_file_path}\" is not there." + fi + } function Run_Software_Hydro() { - Print_Not_Implemented_Function_Error + local Hydro_input_file_path="${Hydro_output_directory}/${HYBRID_software_base_config_file['Hydro']}" + local IC_output_file_path="${IC_output_directory}/SMASH_IC.dat" + local Hydro_output_directory="${HYBRID_output_directory}/Hydro" + ./"${HYBRID_hydro_software_executable}" "-params" "${Hydro_input_file_path}" + "-ISinput" "${IC_output_file_path}" + "-outputDir" "${Hydro_output_directory}" + ">" "${Hydro_output_directory}/Terminal_Output.txt" } diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 4e1e5b3..a46ef8d 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -51,8 +51,17 @@ function Define_Further_Global_Variables() [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' ) # Variables to be set (and possibly made readonly) from command line + readonly HYBRID_default_configurations_folder="${HYBRID_repository_global_path}/configs" + declare -gAr HYBRID_default_software_input_files=( + ['IC']="${HYBRID_default_configurations_folder}/smash_initial_conditions_custom.yaml" + ['Hydro']="${HYBRID_default_configurations_folder}/vhlle_config" + ['Sampler']='' + ['Afterburner']='' + ) + # Variables to be set from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' +<<<<<<< HEAD HYBRID_output_directory="$(realpath .)" # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_given_software_sections=() @@ -62,9 +71,13 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) +======= + HYBRID_output_directory='.' + +>>>>>>> Initialise Hydro functionality and tests declare -gA HYBRID_software_base_config_file=( [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" - [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" + [Hydro]="${HYBRID_default_configurations_folder}/vhlle_config" [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" ) diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash new file mode 100644 index 0000000..1dd853a --- /dev/null +++ b/tests/unit_tests_Hydro_functionality.bash @@ -0,0 +1,20 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Test_Preliminary_Operations_Hydro_test() +{ + source "${HYBRIDT_repository_top_level_path}"/bash/Hydro_functionality.bash\ + || exit "${HYBRID_fatal_builtin}" +} + +function Unit_Test__Prepare-Software-Input-File-Hydro() +{ + Make_Test_Preliminary_Operations_Hydro_test + Prepare_Software_Input_File_Hydro +} \ No newline at end of file From f3664d075e44d30fa08a4dd1c24180e33260da8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 17 Jul 2023 12:07:49 +0200 Subject: [PATCH 175/549] Implement hydro functionality --- bash/Hydro_functionality.bash | 39 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 7a71f8f..9b4c37c 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -9,10 +9,13 @@ function Prepare_Software_Input_File_Hydro() { - # check if output-dir is there and create - local Hydro_output_directory="${HYBRID_output_directory}/Hydro" # this could also be a global variable - if [[ ! -d "${Hydro_output_directory}" ]]; then - mkdir "${Hydro_output_directory}" + mkdir -p "${HYBRID_software_output_directory[Hydro]}" || exit ${HYBRID_fatal_builtin} + if [[ -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' is already existing.' + elif [[ ! -f "${HYBRID_software_base_config_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Hydro]}" ' was not found.' fi # check if config already exists in the output directory, exit if yes local Hydro_input_file_path="${Hydro_output_directory}/vhlle_config" @@ -22,26 +25,18 @@ function Prepare_Software_Input_File_Hydro() exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ "File \"${Hydro_input_file_path}\" is already there." fi - - # replace all fields with input configs - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ - 'TXT' "${Hydro_input_file_path}" "${HYBRID_software_input['Hydro']}" } function Ensure_All_Needed_Input_Exists_Hydro() { - #check that input lists exists - local IC_output_directory="${HYBRID_output_directory}/IC" # this could also be a global variable - if [[ ! -d "${IC_output_directory}" ]]; then - mkdir "${IC_output_directory}" + if [[ ! -d "${HYBRID_software_output_directory[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Folder ' --emph "${HYBRID_software_output_directory[Hydro]}" ' does not exist.' fi - # check if input exists - local IC_output_file_path="${IC_output_directory}/SMASH_IC.dat" - if [[ ! -f "${IC_output_file_path}" ]]; then - exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ - "File \"${IC_output_file_path}\" is not there." + if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi - } function Run_Software_Hydro() @@ -51,9 +46,13 @@ function Run_Software_Hydro() local Hydro_output_directory="${HYBRID_output_directory}/Hydro" ./"${HYBRID_hydro_software_executable}" "-params" "${Hydro_input_file_path}" "-ISinput" "${IC_output_file_path}" - "-outputDir" "${Hydro_output_directory}" - ">" "${Hydro_output_directory}/Terminal_Output.txt" + "-outputDir" "${HYBRID_software_output_directory[Hydro]}" + ">" "${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" } +<<<<<<< HEAD Make_Functions_Defined_In_This_File_Readonly +======= +Make_Functions_Defined_In_This_File_Readonly +>>>>>>> Implement hydro functionality From 381bec5fb575f1aff0c3c15db34531d2c7b7e18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 17 Jul 2023 16:35:48 +0200 Subject: [PATCH 176/549] Work on tests --- bash/Hydro_functionality.bash | 8 +- bash/sanity_checks.bash | 8 +- tests/unit_tests_Hydro_functionality.bash | 123 ++++++++++++++++++++-- 3 files changed, 127 insertions(+), 12 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 9b4c37c..4fbc0d2 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -37,6 +37,10 @@ function Ensure_All_Needed_Input_Exists_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi + if [[ ! -f "${HYBRID_software_input_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The input file ' --emph "${HYBRID_software_input_file[Hydro]}" ' was not found.' + fi } function Run_Software_Hydro() @@ -50,9 +54,5 @@ function Run_Software_Hydro() ">" "${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" } -<<<<<<< HEAD Make_Functions_Defined_In_This_File_Readonly -======= -Make_Functions_Defined_In_This_File_Readonly ->>>>>>> Implement hydro functionality diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 0517c1c..a0fc424 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -20,7 +20,13 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" fi done - readonly HYBRID_software_output_directory HYBRID_software_configuration_file + declare -gA HYBRID_software_input_file=( + [Hydro]="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" + [Sampler]="${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + [Afterburner]="${HYBRID_software_output_directory[Sampler]}/sampling0" + ) + #TODO not fix IC input, read from config + readonly HYBRID_software_output_directory HYBRID_software_configuration_file HYBRID_software_input_file } function __static__Ensure_Executable_Exists() diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 1dd853a..5014f72 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -7,14 +7,123 @@ # #=================================================== -function Make_Test_Preliminary_Operations_Hydro_test() +#TODO IC ALREADY GIVEN + + +function Make_Test_Preliminary_Operations__Hydro-create-input-file() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'Hydro_functionality.bash' + 'global_variables.bash' + 'software_input_functionality.bash' + 'sanity_checks.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables + HYBRID_output_directory="./test_dir_Hydro" + HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' + HYBRID_given_software_sections=( 'IC' 'Hydro' ) + HYBRID_software_executable[IC]=$(which ls) # Use command as fake executable + HYBRID_software_executable[Hydro]=$(which ls) # Use command as fake executable + Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables + +} + +function Unit_Test__Hydro-create-input-file() +{ + + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro + touch "${HYBRID_software_input_file[Hydro]}" + if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then + Print_Error 'The output directory and/or software input file were not properly created.' + return 1 + fi + rm -r "${HYBRID_output_directory}/"* + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro + if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then + Print_Error 'The input file was not properly created in the output folder.' + return 1 + fi + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation of input with existent config succeeded.' + return 1 + fi +} + +function Clean_Tests_Environment_For_Following_Test__Hydro-create-input-file() +{ + rm "${HYBRID_software_base_config_file[Hydro]}" + rm -r "${HYBRID_output_directory}" +} + +function Make_Test_Preliminary_Operations__Hydro-check-all-input() +{ + Make_Test_Preliminary_Operations__Hydro-create-input-file +} + +function Unit_Test__Hydro-check-all-input() +{ + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing output directory succeeded.' + return 1 + fi + mkdir -p "${HYBRID_software_output_directory[Hydro]}" + mkdir -p "${HYBRID_software_output_directory[IC]}" + touch "${HYBRID_software_base_config_file[Hydro]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing config file succeeded.' + return 1 + fi + touch "${HYBRID_software_configuration_file[Hydro]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro + if [[ $? -ne 0 ]]; then + Print_Error 'Ensuring existence of existing folder/file failed.' + return 1 + fi +} + +function Clean_Tests_Environment_For_Following_Test__Hydro-check-all-input() +{ + rm -r "${HYBRID_output_directory}" +} + +function Make_Test_Preliminary_Operations__Hydro-test-run-software() +{ + Make_Test_Preliminary_Operations__Hydro-create-input-file + #Make_Test_Preliminary_Operations__IC-create-input-file +} + +function Unit_Test__Hydro-test-run-software() { - source "${HYBRIDT_repository_top_level_path}"/bash/Hydro_functionality.bash\ - || exit "${HYBRID_fatal_builtin}" + HYBRID_software_executable[Hydro]="${HYBRID_output_directory}/dummy_exec_Hydro.bash" + echo "${HYBRID_software_output_directory[Hydro]}" + local -r hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" + echo "${hydro_terminal_output}" + mkdir -p "${HYBRID_software_output_directory[Hydro]}" + printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Hydro]}" + echo "${HYBRID_software_executable[Hydro]}" + chmod a+x "${HYBRID_software_executable[Hydro]}" + local terminal_output_result correct_result + Call_Codebase_Function_In_Subshell Run_Software_Hydro + if [[ ! -f "${hydro_terminal_output}" ]]; then + Print_Error 'The terminal output was not created.' + return 1 + fi + terminal_output_result=$(< "${hydro_terminal_output}") + correct_result="-i ${HYBRID_software_configuration_file[Hydro]} -o ${HYBRID_software_output_directory[Hydro]} -n" + if [[ "${terminal_output_result}" != "${correct_result}" ]]; then + Print_Error 'The terminal output has not the expected content.' + return 1 + fi } -function Unit_Test__Prepare-Software-Input-File-Hydro() +function Clean_Tests_Environment_For_Following_Test__Hydro-test-run-software() { - Make_Test_Preliminary_Operations_Hydro_test - Prepare_Software_Input_File_Hydro -} \ No newline at end of file + Clean_Tests_Environment_For_Following_Test__Hydro-check-all-input +} From dce3aea7ec98391eb01771a3bf7de13b2ac05fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 19 Sep 2023 20:40:11 +0200 Subject: [PATCH 177/549] Finalise functionality and test --- bash/Hydro_functionality.bash | 21 ++++++++++++--- tests/unit_tests_Hydro_functionality.bash | 32 ++++++++++++++--------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 4fbc0d2..ac3b96e 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -17,6 +17,7 @@ function Prepare_Software_Input_File_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Hydro]}" ' was not found.' fi +<<<<<<< HEAD # check if config already exists in the output directory, exit if yes local Hydro_input_file_path="${Hydro_output_directory}/vhlle_config" if [[ ! -f "${Hydro_input_file_path}" ]]; then @@ -24,6 +25,13 @@ function Prepare_Software_Input_File_Hydro() else exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ "File \"${Hydro_input_file_path}\" is already there." +======= + cp "${HYBRID_software_base_config_file[Hydro]}"\ + "${HYBRID_software_configuration_file[Hydro]}" || exit ${HYBRID_fatal_builtin} + if [[ "${HYBRID_software_new_input_keys[Hydro]}" != '' ]]; then + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + 'YAML' "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_new_input_keys[Hydro]}" +>>>>>>> Finalise functionality and test fi } @@ -37,14 +45,15 @@ function Ensure_All_Needed_Input_Exists_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi - if [[ ! -f "${HYBRID_software_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_input_file[Hydro]}" ' was not found.' + if [[ ! -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'IC output file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" ' does not exist.' fi } function Run_Software_Hydro() { +<<<<<<< HEAD local Hydro_input_file_path="${Hydro_output_directory}/${HYBRID_software_base_config_file['Hydro']}" local IC_output_file_path="${IC_output_directory}/SMASH_IC.dat" local Hydro_output_directory="${HYBRID_output_directory}/Hydro" @@ -52,6 +61,12 @@ function Run_Software_Hydro() "-ISinput" "${IC_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" ">" "${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" +======= + local Hydro_input_file_path="${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_input_file[Hydro]}" + local IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + local hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" + ./"${HYBRID_software_executable[Hydro]}" "-params" "${Hydro_input_file_path}" "-ISinput" "${IC_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" +>>>>>>> Finalise functionality and test } diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 5014f72..c16faa0 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -25,8 +25,8 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() Define_Further_Global_Variables HYBRID_output_directory="./test_dir_Hydro" HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' - HYBRID_given_software_sections=( 'IC' 'Hydro' ) - HYBRID_software_executable[IC]=$(which ls) # Use command as fake executable + HYBRID_given_software_sections=('Hydro' ) + HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" HYBRID_software_executable[Hydro]=$(which ls) # Use command as fake executable Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables @@ -34,7 +34,10 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() function Unit_Test__Hydro-create-input-file() { - + touch "${HYBRID_software_base_config_file[Hydro]}" + mkdir -p "${HYBRID_software_output_directory[IC]}" + local plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + touch "${plist_IC}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro touch "${HYBRID_software_input_file[Hydro]}" if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then @@ -42,6 +45,9 @@ function Unit_Test__Hydro-create-input-file() return 1 fi rm -r "${HYBRID_output_directory}/"* + mkdir -p "${HYBRID_software_output_directory[IC]}" + local plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + touch "${plist_IC}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then Print_Error 'The input file was not properly created in the output folder.' @@ -52,6 +58,7 @@ function Unit_Test__Hydro-create-input-file() Print_Error 'Preparation of input with existent config succeeded.' return 1 fi + rm -r "${HYBRID_output_directory}/"* } function Clean_Tests_Environment_For_Following_Test__Hydro-create-input-file() @@ -69,19 +76,19 @@ function Unit_Test__Hydro-check-all-input() { Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of not-existing output directory succeeded.' + Print_Error 'Ensuring existence of not-existing output directory succeeded, although failure was expected.' return 1 fi mkdir -p "${HYBRID_software_output_directory[Hydro]}" mkdir -p "${HYBRID_software_output_directory[IC]}" - touch "${HYBRID_software_base_config_file[Hydro]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of not-existing config file succeeded.' + Print_Error 'Ensuring existence of not-existing config file succeeded, although failure was expected.' return 1 fi touch "${HYBRID_software_configuration_file[Hydro]}" - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro + touch "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing folder/file failed.' return 1 @@ -96,18 +103,16 @@ function Clean_Tests_Environment_For_Following_Test__Hydro-check-all-input() function Make_Test_Preliminary_Operations__Hydro-test-run-software() { Make_Test_Preliminary_Operations__Hydro-create-input-file - #Make_Test_Preliminary_Operations__IC-create-input-file } function Unit_Test__Hydro-test-run-software() { HYBRID_software_executable[Hydro]="${HYBRID_output_directory}/dummy_exec_Hydro.bash" - echo "${HYBRID_software_output_directory[Hydro]}" - local -r hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" - echo "${hydro_terminal_output}" mkdir -p "${HYBRID_software_output_directory[Hydro]}" + local -r hydro_terminal_output="${HYBRID_output_directory}/Hydro/Terminal_Output.txt" + local Hydro_input_file_path="${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_input_file[Hydro]}" + local IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Hydro]}" - echo "${HYBRID_software_executable[Hydro]}" chmod a+x "${HYBRID_software_executable[Hydro]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro @@ -116,11 +121,12 @@ function Unit_Test__Hydro-test-run-software() return 1 fi terminal_output_result=$(< "${hydro_terminal_output}") - correct_result="-i ${HYBRID_software_configuration_file[Hydro]} -o ${HYBRID_software_output_directory[Hydro]} -n" + correct_result="-params ${Hydro_input_file_path} -ISinput ${IC_output_file_path} -outputDir ${HYBRID_software_output_directory[Hydro]}" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then Print_Error 'The terminal output has not the expected content.' return 1 fi + } function Clean_Tests_Environment_For_Following_Test__Hydro-test-run-software() From eaf59e87b4c3fa662800665081ae1a7b1a350c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 26 Oct 2023 10:27:18 +0200 Subject: [PATCH 178/549] Cleanup after rebase --- bash/Hydro_functionality.bash | 32 +++++++------------------------- bash/global_variables.bash | 14 +------------- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index ac3b96e..973ae4b 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -17,21 +17,11 @@ function Prepare_Software_Input_File_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Hydro]}" ' was not found.' fi -<<<<<<< HEAD - # check if config already exists in the output directory, exit if yes - local Hydro_input_file_path="${Hydro_output_directory}/vhlle_config" - if [[ ! -f "${Hydro_input_file_path}" ]]; then - cp "${HYBRID_software_base_config_file['Hydro']}" "${Hydro_input_file_path}" || exit ${HYBRID_fatal_builtin} - else - exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ - "File \"${Hydro_input_file_path}\" is already there." -======= cp "${HYBRID_software_base_config_file[Hydro]}"\ "${HYBRID_software_configuration_file[Hydro]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Hydro]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ - 'YAML' "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_new_input_keys[Hydro]}" ->>>>>>> Finalise functionality and test + 'TXT' "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_new_input_keys[Hydro]}" fi } @@ -53,20 +43,12 @@ function Ensure_All_Needed_Input_Exists_Hydro() function Run_Software_Hydro() { -<<<<<<< HEAD - local Hydro_input_file_path="${Hydro_output_directory}/${HYBRID_software_base_config_file['Hydro']}" - local IC_output_file_path="${IC_output_directory}/SMASH_IC.dat" - local Hydro_output_directory="${HYBRID_output_directory}/Hydro" - ./"${HYBRID_hydro_software_executable}" "-params" "${Hydro_input_file_path}" - "-ISinput" "${IC_output_file_path}" - "-outputDir" "${HYBRID_software_output_directory[Hydro]}" - ">" "${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" -======= - local Hydro_input_file_path="${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_input_file[Hydro]}" - local IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" - local hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" - ./"${HYBRID_software_executable[Hydro]}" "-params" "${Hydro_input_file_path}" "-ISinput" "${IC_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" ->>>>>>> Finalise functionality and test + local -r\ + hydro_input_file_path="${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_input_file[Hydro]}"\ + ic_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ + hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" + ./"${HYBRID_software_executable[Hydro]}" "-params" "${Hydro_input_file_path}"\ + "-ISinput" "${IC_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" } diff --git a/bash/global_variables.bash b/bash/global_variables.bash index a46ef8d..bb22aeb 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -50,18 +50,10 @@ function Define_Further_Global_Variables() [Input_file]='HYBRID_software_base_config_file[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' ) - # Variables to be set (and possibly made readonly) from command line - readonly HYBRID_default_configurations_folder="${HYBRID_repository_global_path}/configs" - declare -gAr HYBRID_default_software_input_files=( - ['IC']="${HYBRID_default_configurations_folder}/smash_initial_conditions_custom.yaml" - ['Hydro']="${HYBRID_default_configurations_folder}/vhlle_config" - ['Sampler']='' - ['Afterburner']='' - ) + # Variables to be set from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' -<<<<<<< HEAD HYBRID_output_directory="$(realpath .)" # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_given_software_sections=() @@ -71,10 +63,6 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) -======= - HYBRID_output_directory='.' - ->>>>>>> Initialise Hydro functionality and tests declare -gA HYBRID_software_base_config_file=( [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" [Hydro]="${HYBRID_default_configurations_folder}/vhlle_config" From 8f87bad433920b01b74322fcde656444489746c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 26 Oct 2023 13:41:59 +0200 Subject: [PATCH 179/549] Improve unit tests --- tests/unit_tests_Hydro_functionality.bash | 44 ++++++++--------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index c16faa0..a06b41a 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -7,9 +7,6 @@ # #=================================================== -#TODO IC ALREADY GIVEN - - function Make_Test_Preliminary_Operations__Hydro-create-input-file() { local file_to_be_sourced list_of_files @@ -27,30 +24,23 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' HYBRID_given_software_sections=('Hydro' ) HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" - HYBRID_software_executable[Hydro]=$(which ls) # Use command as fake executable + HYBRID_software_executable[Hydro]="${HYBRID_output_directory}/dummy_exec_Hydro.bash" + mkdir -p ${HYBRID_output_directory} + printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Hydro]}" + chmod a+x "${HYBRID_software_executable[Hydro]}" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } - +#is the second touch in the function here wrong? function Unit_Test__Hydro-create-input-file() { touch "${HYBRID_software_base_config_file[Hydro]}" mkdir -p "${HYBRID_software_output_directory[IC]}" - local plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" - touch "${plist_IC}" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro - touch "${HYBRID_software_input_file[Hydro]}" - if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then - Print_Error 'The output directory and/or software input file were not properly created.' - return 1 - fi - rm -r "${HYBRID_output_directory}/"* - mkdir -p "${HYBRID_software_output_directory[IC]}" - local plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" - touch "${plist_IC}" + local -r plist_ic="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + touch "${plist_ic}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then - Print_Error 'The input file was not properly created in the output folder.' + Print_Error 'The config was not properly created in the output folder.' return 1 fi Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null @@ -79,15 +69,14 @@ function Unit_Test__Hydro-check-all-input() Print_Error 'Ensuring existence of not-existing output directory succeeded, although failure was expected.' return 1 fi - mkdir -p "${HYBRID_software_output_directory[Hydro]}" - mkdir -p "${HYBRID_software_output_directory[IC]}" + mkdir -p "${HYBRID_software_output_directory[Hydro]}"\ + "${HYBRID_software_output_directory[IC]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing config file succeeded, although failure was expected.' return 1 fi - touch "${HYBRID_software_configuration_file[Hydro]}" - touch "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + touch "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing folder/file failed.' @@ -107,14 +96,11 @@ function Make_Test_Preliminary_Operations__Hydro-test-run-software() function Unit_Test__Hydro-test-run-software() { - HYBRID_software_executable[Hydro]="${HYBRID_output_directory}/dummy_exec_Hydro.bash" mkdir -p "${HYBRID_software_output_directory[Hydro]}" - local -r hydro_terminal_output="${HYBRID_output_directory}/Hydro/Terminal_Output.txt" - local Hydro_input_file_path="${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_input_file[Hydro]}" - local IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" - printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Hydro]}" - chmod a+x "${HYBRID_software_executable[Hydro]}" - local terminal_output_result correct_result + local -r hydro_terminal_output="${HYBRID_output_directory}/Hydro/Terminal_Output.txt"\ + Hydro_input_file_path="${HYBRID_software_configuration_file[Hydro]}" + IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro if [[ ! -f "${hydro_terminal_output}" ]]; then Print_Error 'The terminal output was not created.' From 49bd6ac559682829b0670e4ea8cf7ceec811c179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 26 Oct 2023 16:25:59 +0200 Subject: [PATCH 180/549] Create first functional tests for hydro, fix output file name of vhlle black box --- bash/Hydro_functionality.bash | 6 +++--- tests/mocks/vhlle_black-box.py | 2 +- tests/unit_tests_Hydro_functionality.bash | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 973ae4b..a581194 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -44,11 +44,11 @@ function Ensure_All_Needed_Input_Exists_Hydro() function Run_Software_Hydro() { local -r\ - hydro_input_file_path="${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_input_file[Hydro]}"\ + hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ ic_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" - ./"${HYBRID_software_executable[Hydro]}" "-params" "${Hydro_input_file_path}"\ - "-ISinput" "${IC_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" + ./"${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}"\ + "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" } diff --git a/tests/mocks/vhlle_black-box.py b/tests/mocks/vhlle_black-box.py index 26e5e3c..9a131ea 100755 --- a/tests/mocks/vhlle_black-box.py +++ b/tests/mocks/vhlle_black-box.py @@ -186,7 +186,7 @@ def print_timestep(timestep): def run_hydro(outputDirSpecified): # create freezout hypersurface file # only if output directory is specified - if outputDirSpecified: freezeout = open(args.outputDir+"hypersurface.dat", "w") + if outputDirSpecified: freezeout = open(args.outputDir+"freezeout.dat", "w") variableList = ["tau", "E", "Efull", "Nb", "Sfull", "EtotSurf", "elements", "susp.", "%cut"] # run the black box print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*variableList)) diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index a06b41a..358950a 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -31,7 +31,7 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } -#is the second touch in the function here wrong? + function Unit_Test__Hydro-create-input-file() { touch "${HYBRID_software_base_config_file[Hydro]}" @@ -98,7 +98,7 @@ function Unit_Test__Hydro-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" local -r hydro_terminal_output="${HYBRID_output_directory}/Hydro/Terminal_Output.txt"\ - Hydro_input_file_path="${HYBRID_software_configuration_file[Hydro]}" + Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro @@ -107,7 +107,7 @@ function Unit_Test__Hydro-test-run-software() return 1 fi terminal_output_result=$(< "${hydro_terminal_output}") - correct_result="-params ${Hydro_input_file_path} -ISinput ${IC_output_file_path} -outputDir ${HYBRID_software_output_directory[Hydro]}" + correct_result="-params ${Hydro_config_file_path} -ISinput ${IC_output_file_path} -outputDir ${HYBRID_software_output_directory[Hydro]}" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then Print_Error 'The terminal output has not the expected content.' return 1 From f107f0281082731d57c175af8822acf20541125b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 26 Oct 2023 17:10:53 +0200 Subject: [PATCH 181/549] Restart functional tests --- bash/global_variables.bash | 4 +- .../functional_tests_Hydro_functionality.bash | 125 ++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 tests/functional_tests_Hydro_functionality.bash diff --git a/bash/global_variables.bash b/bash/global_variables.bash index bb22aeb..8adfd36 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -65,7 +65,7 @@ function Define_Further_Global_Variables() ) declare -gA HYBRID_software_base_config_file=( [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" - [Hydro]="${HYBRID_default_configurations_folder}/vhlle_config" + [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" ) @@ -77,7 +77,7 @@ function Define_Further_Global_Variables() ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( - [IC]='' + [IC]='IC' [Hydro]='' [Sampler]='' [Afterburner]='' diff --git a/tests/functional_tests_Hydro_functionality.bash b/tests/functional_tests_Hydro_functionality.bash new file mode 100644 index 0000000..9c40fbe --- /dev/null +++ b/tests/functional_tests_Hydro_functionality.bash @@ -0,0 +1,125 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# function Make_Test_Preliminary_Operations__Hydro-create-input() +# { +# local file_to_be_sourced list_of_files +# list_of_files=( +# 'Hydro_functionality.bash' +# 'global_variables.bash' +# 'software_input_functionality.bash' +# 'sanity_checks.bash' +# ) +# for file_to_be_sourced in "${list_of_files[@]}"; do +# source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} +# done +# Define_Further_Global_Variables +# HYBRID_output_directory="./test_dir_Hydro" +# HYBRID_software_base_config_file[Hydro]="${HYBRID_output_directory}/vhlle_config_cool" +# HYBRID_given_software_sections=('Hydro' ) +# HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" +# HYBRID_software_executable[Hydro]="../mocks/vhlle_black-box.py" +# mkdir -p ${HYBRID_output_directory} ${HYBRID_software_output_directory[IC]} +# touch "${HYBRID_software_base_config_file[Hydro]}" +# touch "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" +# Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables + +# } + +# function Make_Test_Preliminary_Operations__Hydro-execute() +# { +# Make_Test_Preliminary_Operations__Hydro-create-input +# } + +# function Functional_Test__Hydro-execute() +# { +# #cd "${HYBRID_output_directory}" +# Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro +# Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro +# Call_Codebase_Function_In_Subshell Run_Software_Hydro +# terminal_output=$(tail -n1 ${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt) +# if [[ "$terminal_output" != *'-nan'* ]] +# then +# Print_Error 'Hydro terminal output contains an error!' +# return 1 +# fi +# if [[ ! -f "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" ]] +# then +# Print_Error 'Hydro output was not created!' +# return 1 +# fi + +# return 1 +# } + +function Functional_Test__do-Hydro-only() +{ + #how to define the output directory + shopt -s nullglob + local -r config_filename='vhlle_hydro' + local -r input_filename='SMASH_IC.dat' + local unfinished_files output_files terminal_output_file failure_message + printf ' + Hydro: + Executable: %s/tests/mocks/vhlle_black-box.py + Output_directory: ../IC/ + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + # Expect success and test presence of freezeout + mkdir -p './IC' + touch './IC/SMASH_IC.dat' + ls + cd IC + ls + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + + output_files=( Hydro/* ) + if [[ ${#output_files[@]} -ne 3 ]]; then + Print_Error 'Expected ' --emph '3' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'Hydro' 'Hydro-success' + # Expect failure + Print_Info 'Running Hybrid-handler expecting invalid Hydro input file failure' + terminal_output_file='Hydro/Terminal_Output.txt' + #we have some bad input here + # BLACK_BOX_FAIL='invalid_config'\ + # Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + # if [[ $? -eq 0 ]]; then + # Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input.' + # return 1 + # elif [[ ! -f "${terminal_output_file}" ]]; then + # Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' + # return 1 + # fi + # failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') + # if [[ "${failure_message}" != 'Validation of SMASH input failed.' ]]; then + # Print_Error 'Unexpected failure message: ' --emph "${failure_message}" + # return 1 + # fi + # mv 'IC' 'IC-invalid-config' + # # Expect failure and test "SMASH" unfinished/lock files + # Print_Info 'Running Hybrid-handler expecting crash in IC software' + # BLACK_BOX_FAIL='smash_crashes'\ + # Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + # if [[ $? -eq 0 ]]; then + # Print_Error 'Hybrid-handler unexpectedly succeeded with IC software crashing.' + # return 1 + # fi + # unfinished_files=( IC/*.{unfinished,lock} ) + # if [[ ${#unfinished_files[@]} -ne 3 ]]; then + # Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." + # return 1 + # fi + # mv 'IC' 'IC-software-crash' +} \ No newline at end of file From 6107797b96150768ea8d95ffa5e5e2c72086d968 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Mon, 30 Oct 2023 15:08:52 +0100 Subject: [PATCH 182/549] Finalise first draft for functional Hydro tests --- bash/Hydro_functionality.bash | 2 +- .../functional_tests_Hydro_functionality.bash | 125 ------------------ tests/functional_tests_Hydro_only.bash | 85 ++++++++++++ tests/mocks/vhlle_black-box.py | 12 +- 4 files changed, 95 insertions(+), 129 deletions(-) delete mode 100644 tests/functional_tests_Hydro_functionality.bash create mode 100644 tests/functional_tests_Hydro_only.bash diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index a581194..707ecad 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -47,7 +47,7 @@ function Run_Software_Hydro() hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ ic_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" - ./"${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}"\ + "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}"\ "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" } diff --git a/tests/functional_tests_Hydro_functionality.bash b/tests/functional_tests_Hydro_functionality.bash deleted file mode 100644 index 9c40fbe..0000000 --- a/tests/functional_tests_Hydro_functionality.bash +++ /dev/null @@ -1,125 +0,0 @@ -#=================================================== -# -# Copyright (c) 2023 -# SMASH Hybrid Team -# -# GNU General Public License (GPLv3 or later) -# -#=================================================== - -# function Make_Test_Preliminary_Operations__Hydro-create-input() -# { -# local file_to_be_sourced list_of_files -# list_of_files=( -# 'Hydro_functionality.bash' -# 'global_variables.bash' -# 'software_input_functionality.bash' -# 'sanity_checks.bash' -# ) -# for file_to_be_sourced in "${list_of_files[@]}"; do -# source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} -# done -# Define_Further_Global_Variables -# HYBRID_output_directory="./test_dir_Hydro" -# HYBRID_software_base_config_file[Hydro]="${HYBRID_output_directory}/vhlle_config_cool" -# HYBRID_given_software_sections=('Hydro' ) -# HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" -# HYBRID_software_executable[Hydro]="../mocks/vhlle_black-box.py" -# mkdir -p ${HYBRID_output_directory} ${HYBRID_software_output_directory[IC]} -# touch "${HYBRID_software_base_config_file[Hydro]}" -# touch "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" -# Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables - -# } - -# function Make_Test_Preliminary_Operations__Hydro-execute() -# { -# Make_Test_Preliminary_Operations__Hydro-create-input -# } - -# function Functional_Test__Hydro-execute() -# { -# #cd "${HYBRID_output_directory}" -# Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro -# Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro -# Call_Codebase_Function_In_Subshell Run_Software_Hydro -# terminal_output=$(tail -n1 ${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt) -# if [[ "$terminal_output" != *'-nan'* ]] -# then -# Print_Error 'Hydro terminal output contains an error!' -# return 1 -# fi -# if [[ ! -f "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" ]] -# then -# Print_Error 'Hydro output was not created!' -# return 1 -# fi - -# return 1 -# } - -function Functional_Test__do-Hydro-only() -{ - #how to define the output directory - shopt -s nullglob - local -r config_filename='vhlle_hydro' - local -r input_filename='SMASH_IC.dat' - local unfinished_files output_files terminal_output_file failure_message - printf ' - Hydro: - Executable: %s/tests/mocks/vhlle_black-box.py - Output_directory: ../IC/ - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" - # Expect success and test presence of freezeout - mkdir -p './IC' - touch './IC/SMASH_IC.dat' - ls - cd IC - ls - Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -ne 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly failed.' - return 1 - fi - - output_files=( Hydro/* ) - if [[ ${#output_files[@]} -ne 3 ]]; then - Print_Error 'Expected ' --emph '3' " output files, but ${#output_files[@]} found." - return 1 - fi - mv 'Hydro' 'Hydro-success' - # Expect failure - Print_Info 'Running Hybrid-handler expecting invalid Hydro input file failure' - terminal_output_file='Hydro/Terminal_Output.txt' - #we have some bad input here - # BLACK_BOX_FAIL='invalid_config'\ - # Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - # if [[ $? -eq 0 ]]; then - # Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input.' - # return 1 - # elif [[ ! -f "${terminal_output_file}" ]]; then - # Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' - # return 1 - # fi - # failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') - # if [[ "${failure_message}" != 'Validation of SMASH input failed.' ]]; then - # Print_Error 'Unexpected failure message: ' --emph "${failure_message}" - # return 1 - # fi - # mv 'IC' 'IC-invalid-config' - # # Expect failure and test "SMASH" unfinished/lock files - # Print_Info 'Running Hybrid-handler expecting crash in IC software' - # BLACK_BOX_FAIL='smash_crashes'\ - # Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - # if [[ $? -eq 0 ]]; then - # Print_Error 'Hybrid-handler unexpectedly succeeded with IC software crashing.' - # return 1 - # fi - # unfinished_files=( IC/*.{unfinished,lock} ) - # if [[ ${#unfinished_files[@]} -ne 3 ]]; then - # Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." - # return 1 - # fi - # mv 'IC' 'IC-software-crash' -} \ No newline at end of file diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash new file mode 100644 index 0000000..812f8d6 --- /dev/null +++ b/tests/functional_tests_Hydro_only.bash @@ -0,0 +1,85 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Functional_Test__do-Hydro-only() +{ + #how to define the output directory + shopt -s nullglob + local -r config_filename='vhlle_hydro' + local output_files terminal_output_file failure_message + printf ' + Hydro: + Executable: %s/tests/mocks/vhlle_black-box.py + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + # Expect success and test presence of freezeout + mkdir -p './IC' + touch './IC/SMASH_IC.dat' + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + output_files=( Hydro/* ) + if [[ ${#output_files[@]} -ne 3 ]]; then + Print_Error 'Expected ' --emph '3' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'Hydro' 'Hydro-success' + # Expect failure + Print_Info 'Running Hybrid-handler expecting invalid IC argument' + terminal_output_file='Hydro/Terminal_Output.txt' + BLACK_BOX_FAIL='invalid_input'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input for Hydro.' + return 1 + elif [[ ! -f "${terminal_output_file}" ]]; then + Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' + return 1 + fi + failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') + if [[ "${failure_message}" != 'I/O error with '* ]]; then + Print_Error 'Unexpected failure message: ' --emph "${failure_message}" + return 1 + fi + mv 'Hydro' 'Hydro-invalid-input' + # Expect failure + Print_Info 'Running Hybrid-handler expecting invalid config argument' + terminal_output_file='Hydro/Terminal_Output.txt' + BLACK_BOX_FAIL='invalid_config'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Hydro.' + return 1 + elif [[ ! -f "${terminal_output_file}" ]]; then + Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' + return 1 + fi + failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') + if [[ "${failure_message}" != 'cannot open parameters file '* ]]; then + Print_Error 'Unexpected failure message: ' --emph "${failure_message}" + return 1 + fi + mv 'Hydro' 'Hydro-invalid-config' + # Expect failure and test terminal output + Print_Info 'Running Hybrid-handler expecting crash in Hydro' + BLACK_BOX_FAIL='crash'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with Hydro crashing.' + return 1 + fi + failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') + if [[ "${failure_message}" == *'-nan'* ]]; then + Print_Error 'Hydro finished although crash was expected.' + return 1 + fi + mv 'Hydro' 'Hydro-crash' +} \ No newline at end of file diff --git a/tests/mocks/vhlle_black-box.py b/tests/mocks/vhlle_black-box.py index 9a131ea..8775d39 100755 --- a/tests/mocks/vhlle_black-box.py +++ b/tests/mocks/vhlle_black-box.py @@ -120,7 +120,7 @@ def check_command_line(): sys.exit(1) # check if config file exists if not args.params == "": - if not os.path.isfile(args.params): + if not os.path.isfile(args.params) or not config_is_valid: print("cannot open parameters file ", args.params) sys.exit(1) return @@ -166,7 +166,7 @@ def read_initial_state(): Init time = 9 [sec]""" print("fluid allocation done") - if os.path.exists(args.ISinput): + if os.path.exists(args.ISinput) and input_is_valid: print(messageExample) else: print("I/O error with",args.ISinput) @@ -177,7 +177,7 @@ def print_timestep(timestep): for i in range(1, 10): number = round(random.random(), 2) randomList.append(str(number)) - if timestep > 10: + if timestep > 10 and not crash: randomList[8] = "-nan" print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*randomList)) return @@ -190,6 +190,8 @@ def run_hydro(outputDirSpecified): variableList = ["tau", "E", "Efull", "Nb", "Sfull", "EtotSurf", "elements", "susp.", "%cut"] # run the black box print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*variableList)) + if crash: + sys.exit(1) for ts in range(1,13): print_timestep(ts) time.sleep(0.1) @@ -210,6 +212,10 @@ def run_hydro(outputDirSpecified): parser.add_argument("-outputDir", required=False, help="Path to the output folder", default="") + + input_is_valid = os.environ.get('BLACK_BOX_FAIL') != "invalid_input" + config_is_valid = os.environ.get('BLACK_BOX_FAIL') != "invalid_config" + crash = os.environ.get('BLACK_BOX_FAIL') == "crash" args = parser.parse_args() outputDirGiven = not args.outputDir == "" From 0944d2cf63e0a6080e9d7463a731c20a0c78cc2b Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Mon, 30 Oct 2023 17:14:14 +0100 Subject: [PATCH 183/549] Fix line break in unit test --- tests/unit_tests_Hydro_functionality.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 358950a..9b10f48 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -98,8 +98,8 @@ function Unit_Test__Hydro-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" local -r hydro_terminal_output="${HYBRID_output_directory}/Hydro/Terminal_Output.txt"\ - Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" - IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ + IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro if [[ ! -f "${hydro_terminal_output}" ]]; then From ec593d3f777beed1cf150dcb29e139c13ddc1867 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Mon, 30 Oct 2023 17:18:42 +0100 Subject: [PATCH 184/549] Make terminal_output_result modfiable --- tests/unit_tests_Hydro_functionality.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 9b10f48..a19b60b 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -99,8 +99,8 @@ function Unit_Test__Hydro-test-run-software() mkdir -p "${HYBRID_software_output_directory[Hydro]}" local -r hydro_terminal_output="${HYBRID_output_directory}/Hydro/Terminal_Output.txt"\ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ - terminal_output_result correct_result + IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro if [[ ! -f "${hydro_terminal_output}" ]]; then Print_Error 'The terminal output was not created.' From ef0b1e3203c2bc5de165630b9c34e86b050146d3 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Wed, 1 Nov 2023 12:00:57 +0100 Subject: [PATCH 185/549] Correct treatment of output folders, improve hydro blackbox --- bash/global_variables.bash | 5 ++--- bash/sanity_checks.bash | 6 ------ tests/functional_tests_Hydro_only.bash | 17 ++++++++--------- tests/mocks/vhlle_black-box.py | 9 ++++++++- tests/unit_tests_Hydro_functionality.bash | 6 +----- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 8adfd36..4e1e5b3 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -50,8 +50,7 @@ function Define_Further_Global_Variables() [Input_file]='HYBRID_software_base_config_file[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' ) - - # Variables to be set from command line + # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' HYBRID_output_directory="$(realpath .)" @@ -77,7 +76,7 @@ function Define_Further_Global_Variables() ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( - [IC]='IC' + [IC]='' [Hydro]='' [Sampler]='' [Afterburner]='' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index a0fc424..b20b521 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -20,12 +20,6 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" fi done - declare -gA HYBRID_software_input_file=( - [Hydro]="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" - [Sampler]="${HYBRID_software_output_directory[Hydro]}/freezeout.dat" - [Afterburner]="${HYBRID_software_output_directory[Sampler]}/sampling0" - ) - #TODO not fix IC input, read from config readonly HYBRID_software_output_directory HYBRID_software_configuration_file HYBRID_software_input_file } diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 812f8d6..fec0027 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -9,7 +9,6 @@ function Functional_Test__do-Hydro-only() { - #how to define the output directory shopt -s nullglob local -r config_filename='vhlle_hydro' local output_files terminal_output_file failure_message @@ -17,9 +16,9 @@ function Functional_Test__do-Hydro-only() Hydro: Executable: %s/tests/mocks/vhlle_black-box.py ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" - # Expect success and test presence of freezeout - mkdir -p './IC' - touch './IC/SMASH_IC.dat' + # Run the hydro stage and check if freezeout is successfully generated + mkdir -p 'IC' + touch 'IC/SMASH_IC.dat' Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 0 ]]; then @@ -32,7 +31,7 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-success' - # Expect failure + # Expect failure when giving an invalid IC output Print_Info 'Running Hybrid-handler expecting invalid IC argument' terminal_output_file='Hydro/Terminal_Output.txt' BLACK_BOX_FAIL='invalid_input'\ @@ -50,7 +49,7 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-invalid-input' - # Expect failure + # Expect failure when an invalid config was supplied Print_Info 'Running Hybrid-handler expecting invalid config argument' terminal_output_file='Hydro/Terminal_Output.txt' BLACK_BOX_FAIL='invalid_config'\ @@ -68,7 +67,7 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-invalid-config' - # Expect failure and test terminal output + # Expect failure and test terminal output in the case of a crash of vHLLE Print_Info 'Running Hybrid-handler expecting crash in Hydro' BLACK_BOX_FAIL='crash'\ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -77,9 +76,9 @@ function Functional_Test__do-Hydro-only() return 1 fi failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') - if [[ "${failure_message}" == *'-nan'* ]]; then + if [[ "${failure_message}" != *'Crash happened in vHLLE'* ]]; then Print_Error 'Hydro finished although crash was expected.' return 1 fi mv 'Hydro' 'Hydro-crash' -} \ No newline at end of file +} diff --git a/tests/mocks/vhlle_black-box.py b/tests/mocks/vhlle_black-box.py index 8775d39..8801812 100755 --- a/tests/mocks/vhlle_black-box.py +++ b/tests/mocks/vhlle_black-box.py @@ -6,6 +6,7 @@ import random import time import datetime +import textwrap def print_terminal_start(): # generated with https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 @@ -191,6 +192,7 @@ def run_hydro(outputDirSpecified): # run the black box print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*variableList)) if crash: + print("Crash happened in vHLLE") sys.exit(1) for ts in range(1,13): print_timestep(ts) @@ -202,7 +204,12 @@ def run_hydro(outputDirSpecified): if __name__ == '__main__': - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, + epilog=textwrap.dedent(''' + Use the BLACK_BOX_FAIL environment variable set to either "invalid_config", + "invalid_input" or to "smash_crashes" to mimic a particular failure in the + black box. + ''')) parser.add_argument("-params", required=False, help="Path to vhlle_config", default="") diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index a19b60b..6fa6115 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -24,10 +24,7 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' HYBRID_given_software_sections=('Hydro' ) HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" - HYBRID_software_executable[Hydro]="${HYBRID_output_directory}/dummy_exec_Hydro.bash" - mkdir -p ${HYBRID_output_directory} - printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Hydro]}" - chmod a+x "${HYBRID_software_executable[Hydro]}" + HYBRID_software_executable[Hydro]="$(which echo)" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } @@ -48,7 +45,6 @@ function Unit_Test__Hydro-create-input-file() Print_Error 'Preparation of input with existent config succeeded.' return 1 fi - rm -r "${HYBRID_output_directory}/"* } function Clean_Tests_Environment_For_Following_Test__Hydro-create-input-file() From c73b0c8e41f734626b7df2541d21f7304701c9c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 2 Nov 2023 11:05:27 +0100 Subject: [PATCH 186/549] Remove unused variable and make Hydro crash test stricter --- bash/sanity_checks.bash | 2 +- tests/functional_tests_Hydro_only.bash | 2 +- tests/mocks/vhlle_black-box.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index b20b521..a6c4d23 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -20,7 +20,7 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" fi done - readonly HYBRID_software_output_directory HYBRID_software_configuration_file HYBRID_software_input_file + readonly HYBRID_software_output_directory HYBRID_software_configuration_file } function __static__Ensure_Executable_Exists() diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index fec0027..1683a91 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -76,7 +76,7 @@ function Functional_Test__do-Hydro-only() return 1 fi failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') - if [[ "${failure_message}" != *'Crash happened in vHLLE'* ]]; then + if [[ "${failure_message}" != 'Crash happened in vHLLE' ]]; then Print_Error 'Hydro finished although crash was expected.' return 1 fi diff --git a/tests/mocks/vhlle_black-box.py b/tests/mocks/vhlle_black-box.py index 8801812..d9c561d 100755 --- a/tests/mocks/vhlle_black-box.py +++ b/tests/mocks/vhlle_black-box.py @@ -207,7 +207,7 @@ def run_hydro(outputDirSpecified): parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, epilog=textwrap.dedent(''' Use the BLACK_BOX_FAIL environment variable set to either "invalid_config", - "invalid_input" or to "smash_crashes" to mimic a particular failure in the + "invalid_input" or to "crash" to mimic a particular failure in the black box. ''')) parser.add_argument("-params", required=False, From 18f6e71e29a39162bc47ab6e3407ece4b7ddbd8e Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 10 Aug 2023 13:45:28 +0200 Subject: [PATCH 187/549] Add first draft of functionality plus tests --- bash/Afterburner_functionality.bash | 63 +++++++++++++++++++++++++++-- bash/global_variables.bash | 8 ++++ python/add_spectators.py | 2 + 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 92cc897..dd23017 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -9,18 +9,73 @@ function Prepare_Software_Input_File_Afterburner() { - Print_Not_Implemented_Function_Error + mkdir -p "${HYBRID_software_output_directory[Afterburner]}" || exit ${HYBRID_fatal_builtin} + if [[ -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" ' is already existing.' + elif [[ ! -f "${HYBRID_software_base_config_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Afterburner]}" ' was not found.' + fi + cp "${HYBRID_software_base_config_file[Afterburner]}"\ + "${HYBRID_software_output_directory[Afterburner]}" || exit ${HYBRID_fatal_builtin} + if [[ "${HYBRID_software_new_input_keys[Afterburner]}" != '' ]]; then + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" + fi + + if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ' does not exist.' + fi + + if [[ "${HYBRID_optional_feature[Add_Spectators]}" = true ]]; then + if [[ -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0" ' already exists.' + elif [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Initial condition configuration file ' --emph "${HYBRID_software_configuration_file[IC]}" ' does not exist which is needed to check number of initial nucleons.' + elif [[ ! -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ' does not exist.' + fi + + "${HYBRID_external_python_scripts[Add_Spectators]}" \ + '--sampled_particle_list' "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" + '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" + '--output_file' "${HYBRID_software_output_directory[Afterburner]}/sampling0" + '--smash_config' "${HYBRID_software_configuration_file[IC]}" + else + # rename? check if necessary + mv "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" "${HYBRID_software_output_directory[Sampler]}/sampling0" + fi } function Ensure_All_Needed_Input_Exists_Afterburner() { - Print_Not_Implemented_Function_Error + if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0" ' does not exist.' + fi + if [[ ! -d "${HYBRID_software_output_directory[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Folder ' --emph "${HYBRID_software_output_directory[Afterburner]}" ' does not exist.' + fi + if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" ' was not found.' + fi } function Run_Software_Afterburner() { - Print_Not_Implemented_Function_Error + local afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" + "${HYBRID_software_executable[Afterburner]}" \ + '-i' "${HYBRID_software_configuration_file[Afterburner]}" \ + '-o' "${HYBRID_software_output_directory[Afterburner]}" \ + '-n' \ + >> "${afterburner_terminal_output}" } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 4e1e5b3..2df7b6d 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -27,6 +27,7 @@ function Define_Further_Global_Variables() 'Hybrid_handler' ) readonly HYBRID_default_configurations_folder="${HYBRID_top_level_path}/configs" + readonly HYBRID_python_folder="${HYBRID_top_level_path}/python" # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=() @@ -87,6 +88,13 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) + declare -gA HYBRID_optional_feature=( + [Add_Spectators]='' + ) + + declare -gA HYBRID_external_python_scripts=( + [Add_Spectators]='${HYBRID_python_folder}/add_spectators.py' + ) } diff --git a/python/add_spectators.py b/python/add_spectators.py index 50a68b1..bccdb96 100644 --- a/python/add_spectators.py +++ b/python/add_spectators.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import numpy as np import argparse import os From ba63ab51307928e0ff5c1065e2d6de8383d3f292 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 15 Aug 2023 14:53:00 +0200 Subject: [PATCH 188/549] Working Afterburner functionality + tests implementation --- bash/Afterburner_functionality.bash | 31 ++- bash/global_variables.bash | 17 +- python/add_spectators.py | 0 .../unit_tests_Afterburner_functionality.bash | 187 ++++++++++++++++++ 4 files changed, 212 insertions(+), 23 deletions(-) mode change 100644 => 100755 python/add_spectators.py create mode 100644 tests/unit_tests_Afterburner_functionality.bash diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index dd23017..311771a 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -28,43 +28,40 @@ function Prepare_Software_Input_File_Afterburner() exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ' does not exist.' fi - if [[ "${HYBRID_optional_feature[Add_Spectators]}" = true ]]; then if [[ -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0" ' already exists.' - elif [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then + elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Initial condition configuration file ' --emph "${HYBRID_software_configuration_file[IC]}" ' does not exist which is needed to check number of initial nucleons.' + 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml" ' does not exist which is needed to check number of initial nucleons.' elif [[ ! -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ' does not exist.' - fi - - "${HYBRID_external_python_scripts[Add_Spectators]}" \ - '--sampled_particle_list' "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" - '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" - '--output_file' "${HYBRID_software_output_directory[Afterburner]}/sampling0" - '--smash_config' "${HYBRID_software_configuration_file[IC]}" + fi + "${HYBRID_external_python_scripts[Add_Spectators]}"\ + '--sampled_particle_list' "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ + '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ + '--output_file' "${HYBRID_software_output_directory[Sampler]}/sampling0"\ + '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" else - # rename? check if necessary - mv "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" "${HYBRID_software_output_directory[Sampler]}/sampling0" + ln -s "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" "${HYBRID_software_output_directory[Sampler]}/sampling0" fi } function Ensure_All_Needed_Input_Exists_Afterburner() { - if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0" ' does not exist.' - fi if [[ ! -d "${HYBRID_software_output_directory[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'Folder ' --emph "${HYBRID_software_output_directory[Afterburner]}" ' does not exist.' fi if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" ' was not found.' + 'Folder ' --emph "${HYBRID_software_output_directory[Afterburner]}" ' does not exist.' + fi + if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The input file ' --emph "${HYBRID_software_configuration_file[Sampler]}/sampling0" ' s not found.' fi } diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 2df7b6d..7deccf7 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -63,11 +63,17 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) + declare -gA HYBRID_software_base_config_file_names=( + [IC]="smash_initial_conditions_AuAu.yaml" + [Hydro]="vhlle_hydro" + [Sampler]="hadron_sampler" + [Afterburner]="smash_afterburner.yaml" + ) declare -gA HYBRID_software_base_config_file=( - [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" - [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" - [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" - [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" + [IC]="${HYBRID_default_configurations_folder}/${HYBRID_software_base_config_file_names[IC]}" + [Hydro]="${HYBRID_default_configurations_folder}/${HYBRID_software_base_config_file_names[Hydro]}" + [Sampler]="${HYBRID_default_configurations_folder}/${HYBRID_software_base_config_file_names[Sampler]}" + [Afterburner]="${HYBRID_default_configurations_folder}/${HYBRID_software_base_config_file_names[Afterburner]}" ) declare -gA HYBRID_software_new_input_keys=( [IC]='' @@ -91,9 +97,8 @@ function Define_Further_Global_Variables() declare -gA HYBRID_optional_feature=( [Add_Spectators]='' ) - declare -gA HYBRID_external_python_scripts=( - [Add_Spectators]='${HYBRID_python_folder}/add_spectators.py' + [Add_Spectators]="${HYBRID_python_folder}/add_spectators.py" ) } diff --git a/python/add_spectators.py b/python/add_spectators.py old mode 100644 new mode 100755 diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash new file mode 100644 index 0000000..1167b02 --- /dev/null +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -0,0 +1,187 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Test_Preliminary_Operations__Afterburner-create-input-file() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'Afterburner_functionality.bash' + 'global_variables.bash' + 'software_input_functionality.bash' + 'sanity_checks.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables + HYBRID_output_directory="${HYBRIDT_tests_folder}/test_dir_Afterburner" + HYBRID_software_base_config_file[Afterburner]='my_cool_conf.yaml' + HYBRID_given_software_sections=( 'Afterburner' ) + HYBRID_software_executable[Afterburner]=$(which ls) # Use command as fake executable + HYBRID_software_output_directory[Sampler]="${HYBRID_output_directory}/Sampler" + HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" + Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables +} + +function Unit_Test__Afterburner-create-input-file() +{ + touch "${HYBRID_software_base_config_file[Afterburner]}" + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + local plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" + local plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" + local plist_Final="${HYBRID_software_output_directory[Sampler]}/sampling0" + touch "${plist_Sampler}" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner + if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then + Print_Error 'The output directory and/or software input file were not properly created.' + return 1 + fi + rm -r "${HYBRID_output_directory}/"* + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + touch "${plist_Sampler}" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner + if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then + Print_Error 'The input file was not properly created in the output folder.' + return 1 + fi + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation of input with existent config succeeded.' + return 1 + fi + rm -r "${HYBRID_output_directory}/"* + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation of input succeeded even though the particle_list.oscar does not exist.' + return 1 + fi + rm -r "${HYBRID_output_directory}/"* + HYBRID_optional_feature[Add_Spectators]=false + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + touch "${plist_Sampler}" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner + if [[ ! -f "${plist_Final}" ]]; then + Print_Error 'The final input file was not properly created in the output folder.' + return 1 + fi + HYBRID_optional_feature[Add_Spectators]=true + rm -r "${HYBRID_output_directory}/"* + mkdir -p "${HYBRID_software_output_directory[IC]}" + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + touch "${plist_Sampler}" + touch "${plist_Final}" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preperation succeeded even though the final particle list already exists.' + return 1 + fi + rm "${plist_Final}" + rm "${HYBRID_output_directory}/Afterburner/"* + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preperation succeeded even though the config.yaml of the IC doesnt exist.' + return 1 + fi + rm "${HYBRID_output_directory}/Afterburner/"* + touch "${HYBRID_software_output_directory[IC]}/config.yaml" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preperation succeeded even though the SMASH_IC.oscar doesnt exist.' + return 1 + fi + rm -r "${HYBRID_output_directory}/"* + mkdir -p "${HYBRID_software_output_directory[IC]}" + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + mkdir -p "${HYBRID_software_output_directory[Afterburner]}" + cp "${HYBRID_software_base_config_file[IC]}" "${HYBRID_software_output_directory[IC]}" + mv "${HYBRID_software_output_directory[IC]}/${HYBRID_software_base_config_file_names[IC]}" "${HYBRID_software_output_directory[IC]}/config.yaml" + touch "${plist_Sampler}" + touch "${plist_IC}" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner + if [[ ! -f "${plist_Final}" ]]; then + Print_Error 'The final input file was not properly created in the output folder.' + return 1 + fi +} + +function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file() +{ + rm "${HYBRID_software_base_config_file[Afterburner]}" + rm -r "${HYBRID_output_directory}" +} + +function Make_Test_Preliminary_Operations__Afterburner-check-all-input() +{ + Make_Test_Preliminary_Operations__Afterburner-create-input-file +} + +function Unit_Test__Afterburner-check-all-input() +{ + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing output directory succeeded.' + return 1 + fi + mkdir -p "${HYBRID_software_output_directory[Afterburner]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing config file succeeded.' + return 1 + fi + touch "${HYBRID_software_configuration_file[Afterburner]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of input file failed.' + return 1 + fi + touch "${HYBRID_software_configuration_file[Afterburner]}" + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + touch "${HYBRID_software_output_directory[Sampler]}/sampling0" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner + if [[ $? -ne 0 ]]; then + Print_Error 'Ensuring existence of existing folder/file failed.' + return 1 + fi +} + +function Clean_Tests_Environment_For_Following_Test__Afterburner-check-all-input() +{ + rm -r "${HYBRID_output_directory}" +} + +function Make_Test_Preliminary_Operations__Afterburner-test-run-software() +{ + Make_Test_Preliminary_Operations__Afterburner-create-input-file +} + +function Unit_Test__Afterburner-test-run-software() +{ + HYBRID_software_executable[Afterburner]="${HYBRID_output_directory}/dummy_exec_IC.bash" + mkdir -p "${HYBRID_software_output_directory[Afterburner]}" + local -r afterburner_terminal_output="${HYBRID_output_directory}/Afterburner/Terminal_Output.txt" + printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Afterburner]}" + chmod a+x "${HYBRID_software_executable[Afterburner]}" + local terminal_output_result correct_result + Call_Codebase_Function_In_Subshell Run_Software_Afterburner + if [[ ! -f "${afterburner_terminal_output}" ]]; then + Print_Error 'The terminal output was not created.' + return 1 + fi + terminal_output_result=$(< "${afterburner_terminal_output}") + correct_result="-i ${HYBRID_software_configuration_file[Afterburner]} -o ${HYBRID_software_output_directory[Afterburner]} -n" + if [[ "${terminal_output_result}" != "${correct_result}" ]]; then + Print_Error 'The terminal output has not the expected content.' + return 1 + fi +} + +function Clean_Tests_Environment_For_Following_Test__Afterburner-test-run-software() +{ + Clean_Tests_Environment_For_Following_Test__Afterburner-check-all-input +} From 5ca2b50e1f85af1515b03e8f86a9ccf834c6a252 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 15 Aug 2023 14:58:49 +0200 Subject: [PATCH 189/549] Make afterburner black box fail with a environment variable instead of argument --- tests/mocks/smash_afterburner_black-box.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 2bbe48e..9469d69 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -4,6 +4,7 @@ import os import sys import re +import textwrap import time def check_config(valid_config): @@ -173,7 +174,11 @@ def parse_command_line_config_options(): if __name__ == '__main__': - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, + epilog=textwrap.dedent(''' + Use the BLACK_BOX_FAIL environment variable set to either "invalid_config" + or to "smash_crashes" to mimic a particular failure in the black box. + ''')) parser.add_argument("-i", required=False, help="File to the config.yaml") parser.add_argument("-o", required=False, @@ -190,6 +195,9 @@ def parse_command_line_config_options(): args = parser.parse_args() + config_is_valid = os.environ.get('BLACK_BOX_FAIL') != "invalid_config" + smash_finishes = os.environ.get('BLACK_BOX_FAIL') != "smash_crashes" + smash_finishes = args.fail_with != "smash_crashes" fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " file_name_is_running = "smash.lock" From 7ba281c149b2ec203ef124cbd280bac7aef15f55 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 28 Aug 2023 11:33:27 +0200 Subject: [PATCH 190/549] Add python setup to GitHub actions The latest minor release of Python 3 has been used. The explicit installation of numpy package has been added, too. --- .../workflows/github-actions-on-github-servers.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index cfc3493..da5e72d 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -35,7 +35,19 @@ jobs: steps: # this is an action provided by GitHub to checkout the repository - uses: actions/checkout@v3 - # we set the name of the step, collecting all the tests here except those about formatting + # taken from https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + # pick the latest minor release of Python 3 + python-version: '3.x' + # print the picked Python version + - name: Display Python version + run: python -c "import sys; print(sys.version)" + # install needed non-standard packages + - name: Install Python dependencies + run: python -m pip install --upgrade pip numpy + # now everything should be ready to setup bash and run codebase tests - name: code_check # we set some environment variables which depend on the specific matrix element env: From 44fe18934f8c798b794be47c37d88ed4fd996b3e Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 30 Oct 2023 10:03:21 +0100 Subject: [PATCH 191/549] Use basename and add sanity check for external python scripts --- bash/global_variables.bash | 16 +++++----------- bash/sanity_checks.bash | 9 +++++++++ tests/unit_tests_Afterburner_functionality.bash | 4 +++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 7deccf7..d31900b 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -63,17 +63,11 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) - declare -gA HYBRID_software_base_config_file_names=( - [IC]="smash_initial_conditions_AuAu.yaml" - [Hydro]="vhlle_hydro" - [Sampler]="hadron_sampler" - [Afterburner]="smash_afterburner.yaml" - ) declare -gA HYBRID_software_base_config_file=( - [IC]="${HYBRID_default_configurations_folder}/${HYBRID_software_base_config_file_names[IC]}" - [Hydro]="${HYBRID_default_configurations_folder}/${HYBRID_software_base_config_file_names[Hydro]}" - [Sampler]="${HYBRID_default_configurations_folder}/${HYBRID_software_base_config_file_names[Sampler]}" - [Afterburner]="${HYBRID_default_configurations_folder}/${HYBRID_software_base_config_file_names[Afterburner]}" + [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" + [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" + [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" + [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" ) declare -gA HYBRID_software_new_input_keys=( [IC]='' @@ -97,7 +91,7 @@ function Define_Further_Global_Variables() declare -gA HYBRID_optional_feature=( [Add_Spectators]='' ) - declare -gA HYBRID_external_python_scripts=( + declare -rgA HYBRID_external_python_scripts=( [Add_Spectators]="${HYBRID_python_folder}/add_spectators.py" ) } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index a6c4d23..3fc32cb 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -23,6 +23,15 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var readonly HYBRID_software_output_directory HYBRID_software_configuration_file } +function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() +{ + for external_file in "${HYBRID_external_python_scripts[@]}"; do + if [[ ! -f "${external_file}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The external script ' --emph "${external_file}" ' was not found.' + done +} + function __static__Ensure_Executable_Exists() { local label=$1 file_path diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 1167b02..00a6b86 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -27,6 +27,7 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() HYBRID_software_output_directory[Sampler]="${HYBRID_output_directory}/Sampler" HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables + Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts } function Unit_Test__Afterburner-create-input-file() @@ -100,7 +101,8 @@ function Unit_Test__Afterburner-create-input-file() mkdir -p "${HYBRID_software_output_directory[Sampler]}" mkdir -p "${HYBRID_software_output_directory[Afterburner]}" cp "${HYBRID_software_base_config_file[IC]}" "${HYBRID_software_output_directory[IC]}" - mv "${HYBRID_software_output_directory[IC]}/${HYBRID_software_base_config_file_names[IC]}" "${HYBRID_software_output_directory[IC]}/config.yaml" + base_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") + mv "${HYBRID_software_output_directory[IC]}/${base_config_name}" "${HYBRID_software_output_directory[IC]}/config.yaml" touch "${plist_Sampler}" touch "${plist_IC}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner From d8d2dbb2a565edef2451064f5ebc7089c5c67aee Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 30 Oct 2023 10:17:21 +0100 Subject: [PATCH 192/549] fix afterburner blackbox logic --- tests/mocks/smash_afterburner_black-box.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 9469d69..ced77c7 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -188,17 +188,11 @@ def parse_command_line_config_options(): " -c 'Modi: { List: { File_Directory: } }'" " -c 'General: { Nevents: }'") - parser.add_argument("--fail_with", required=False, - default=None, - choices=["invalid_config", "smash_crashes"], - help="Choose a place where SMASH should fail") - args = parser.parse_args() config_is_valid = os.environ.get('BLACK_BOX_FAIL') != "invalid_config" smash_finishes = os.environ.get('BLACK_BOX_FAIL') != "smash_crashes" - smash_finishes = args.fail_with != "smash_crashes" fatal_error = "FATAL Main : SMASH failed with the following error:\n\t\t\t " file_name_is_running = "smash.lock" name_unfinished = ".unfinished" @@ -208,7 +202,7 @@ def parse_command_line_config_options(): sampler_dir=parse_command_line_config_options() # initialize the system - check_config(args.fail_with != "invalid_config") + check_config(config_is_valid) create_folders_structure() ensure_no_output_is_overwritten() From b14b7df22f6970f1df6e3372e9a9c82811807728 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 30 Oct 2023 10:22:05 +0100 Subject: [PATCH 193/549] Clean up functional tests --- tests/unit_tests_Afterburner_functionality.bash | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 00a6b86..3405fca 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -73,17 +73,14 @@ function Unit_Test__Afterburner-create-input-file() fi HYBRID_optional_feature[Add_Spectators]=true rm -r "${HYBRID_output_directory}/"* - mkdir -p "${HYBRID_software_output_directory[IC]}" - mkdir -p "${HYBRID_software_output_directory[Sampler]}" - touch "${plist_Sampler}" - touch "${plist_Final}" + mkdir -p "${HYBRID_software_output_directory[IC]}" "${HYBRID_software_output_directory[Sampler]}" + touch "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Preperation succeeded even though the final particle list already exists.' return 1 fi - rm "${plist_Final}" - rm "${HYBRID_output_directory}/Afterburner/"* + rm "${plist_Final}" "${HYBRID_output_directory}/Afterburner/"* Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Preperation succeeded even though the config.yaml of the IC doesnt exist.' @@ -97,14 +94,11 @@ function Unit_Test__Afterburner-create-input-file() return 1 fi rm -r "${HYBRID_output_directory}/"* - mkdir -p "${HYBRID_software_output_directory[IC]}" - mkdir -p "${HYBRID_software_output_directory[Sampler]}" - mkdir -p "${HYBRID_software_output_directory[Afterburner]}" + mkdir -p "${HYBRID_software_output_directory[IC]}" "${HYBRID_software_output_directory[Sampler]}" "${HYBRID_software_output_directory[Afterburner]}" cp "${HYBRID_software_base_config_file[IC]}" "${HYBRID_software_output_directory[IC]}" base_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") mv "${HYBRID_software_output_directory[IC]}/${base_config_name}" "${HYBRID_software_output_directory[IC]}/config.yaml" - touch "${plist_Sampler}" - touch "${plist_IC}" + touch "${plist_Sampler}" "${plist_IC}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${plist_Final}" ]]; then Print_Error 'The final input file was not properly created in the output folder.' From 9e04b9485ccd87ab69f8c57c4062cd44a6c9f7da Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 30 Oct 2023 11:57:49 +0100 Subject: [PATCH 194/549] Clean up functions 2 --- bash/Afterburner_functionality.bash | 8 ++++---- bash/IC_functionality.bash | 2 +- bash/sanity_checks.bash | 1 + .../unit_tests_Afterburner_functionality.bash | 19 ++++++++----------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 311771a..785abe4 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -18,7 +18,7 @@ function Prepare_Software_Input_File_Afterburner() 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Afterburner]}" ' was not found.' fi cp "${HYBRID_software_base_config_file[Afterburner]}"\ - "${HYBRID_software_output_directory[Afterburner]}" || exit ${HYBRID_fatal_builtin} + "${HYBRID_software_configuration_file[Afterburner]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Afterburner]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" @@ -28,7 +28,7 @@ function Prepare_Software_Input_File_Afterburner() exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ' does not exist.' fi - if [[ "${HYBRID_optional_feature[Add_Spectators]}" = true ]]; then + if [[ "${HYBRID_optional_feature[Add_Spectators]}" = 'true' ]]; then if [[ -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0" ' already exists.' @@ -61,13 +61,13 @@ function Ensure_All_Needed_Input_Exists_Afterburner() fi if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_configuration_file[Sampler]}/sampling0" ' s not found.' + 'The input file ' --emph "${HYBRID_software_configuration_file[Sampler]}/sampling0" ' was not found.' fi } function Run_Software_Afterburner() { - local afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" + local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" "${HYBRID_software_executable[Afterburner]}" \ '-i' "${HYBRID_software_configuration_file[Afterburner]}" \ '-o' "${HYBRID_software_output_directory[Afterburner]}" \ diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 48597f5..8d8c237 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -18,7 +18,7 @@ function Prepare_Software_Input_File_IC() 'Base configuration file ' --emph "${HYBRID_software_base_config_file[IC]}" ' was not found.' fi cp "${HYBRID_software_base_config_file[IC]}"\ - "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} + "${HYBRID_software_configuration_file[IC]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[IC]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[IC]}" "${HYBRID_software_new_input_keys[IC]}" diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 3fc32cb..b18bbfb 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -29,6 +29,7 @@ function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() if [[ ! -f "${external_file}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The external script ' --emph "${external_file}" ' was not found.' + fi done } diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 3405fca..ac113d0 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -34,9 +34,10 @@ function Unit_Test__Afterburner-create-input-file() { touch "${HYBRID_software_base_config_file[Afterburner]}" mkdir -p "${HYBRID_software_output_directory[Sampler]}" - local plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" - local plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" - local plist_Final="${HYBRID_software_output_directory[Sampler]}/sampling0" + local -r\ + plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ + plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ + plist_Final="${HYBRID_software_output_directory[Sampler]}/sampling0" touch "${plist_Sampler}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then @@ -77,28 +78,25 @@ function Unit_Test__Afterburner-create-input-file() touch "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Preperation succeeded even though the final particle list already exists.' + Print_Error 'Preparation succeeded even though the final particle list already exists.' return 1 fi rm "${plist_Final}" "${HYBRID_output_directory}/Afterburner/"* Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Preperation succeeded even though the config.yaml of the IC doesnt exist.' + Print_Error 'Preparation succeeded even though the config.yaml of the IC does not exist.' return 1 fi rm "${HYBRID_output_directory}/Afterburner/"* touch "${HYBRID_software_output_directory[IC]}/config.yaml" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Preperation succeeded even though the SMASH_IC.oscar doesnt exist.' + Print_Error 'Preparation succeeded even though the SMASH_IC.oscar does not exist.' return 1 fi rm -r "${HYBRID_output_directory}/"* mkdir -p "${HYBRID_software_output_directory[IC]}" "${HYBRID_software_output_directory[Sampler]}" "${HYBRID_software_output_directory[Afterburner]}" - cp "${HYBRID_software_base_config_file[IC]}" "${HYBRID_software_output_directory[IC]}" - base_config_name=$(basename "${HYBRID_software_base_config_file[IC]}") - mv "${HYBRID_software_output_directory[IC]}/${base_config_name}" "${HYBRID_software_output_directory[IC]}/config.yaml" - touch "${plist_Sampler}" "${plist_IC}" + touch "${HYBRID_software_output_directory[IC]}/config.yaml" "${plist_Sampler}" "${plist_IC}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${plist_Final}" ]]; then Print_Error 'The final input file was not properly created in the output folder.' @@ -136,7 +134,6 @@ function Unit_Test__Afterburner-check-all-input() Print_Error 'Ensuring existence of input file failed.' return 1 fi - touch "${HYBRID_software_configuration_file[Afterburner]}" mkdir -p "${HYBRID_software_output_directory[Sampler]}" touch "${HYBRID_software_output_directory[Sampler]}/sampling0" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner From 3f0d3bfed96fd818615e4cc94a11ac2e4d36b051 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 30 Oct 2023 14:22:00 +0100 Subject: [PATCH 195/549] Split create-input-file test into two, with/without spectators --- .../unit_tests_Afterburner_functionality.bash | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index ac113d0..f9340bf 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -64,15 +64,29 @@ function Unit_Test__Afterburner-create-input-file() return 1 fi rm -r "${HYBRID_output_directory}/"* - HYBRID_optional_feature[Add_Spectators]=false +} + +function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file() +{ + rm "${HYBRID_software_base_config_file[Afterburner]}" + rm -r "${HYBRID_output_directory}" +} + +function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-spectators() +{ + Make_Test_Preliminary_Operations__Afterburner-create-input-file +} + +function Unit_Test__Afterburner-create-input-file-with-spectators() +{ + HYBRID_optional_feature[Add_Spectators]=true + touch "${HYBRID_software_base_config_file[Afterburner]}" mkdir -p "${HYBRID_software_output_directory[Sampler]}" + local -r\ + plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ + plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ + plist_Final="${HYBRID_software_output_directory[Sampler]}/sampling0" touch "${plist_Sampler}" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner - if [[ ! -f "${plist_Final}" ]]; then - Print_Error 'The final input file was not properly created in the output folder.' - return 1 - fi - HYBRID_optional_feature[Add_Spectators]=true rm -r "${HYBRID_output_directory}/"* mkdir -p "${HYBRID_software_output_directory[IC]}" "${HYBRID_software_output_directory[Sampler]}" touch "${plist_Sampler}" "${plist_Final}" @@ -104,7 +118,7 @@ function Unit_Test__Afterburner-create-input-file() fi } -function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file() +function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file-with-spectators() { rm "${HYBRID_software_base_config_file[Afterburner]}" rm -r "${HYBRID_output_directory}" From 1a0d5079d15ddcd1dd0a01a202bc69ca0fc0aa8b Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 31 Oct 2023 11:15:34 +0100 Subject: [PATCH 196/549] Make Add_Spectators_From_IC false as default and add it to the YAML options. --- bash/Afterburner_functionality.bash | 4 ++-- bash/global_variables.bash | 5 +++-- bash/sanity_checks.bash | 12 ++++++++++-- tests/unit_tests_Afterburner_functionality.bash | 3 ++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 785abe4..d47b600 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -28,7 +28,7 @@ function Prepare_Software_Input_File_Afterburner() exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ' does not exist.' fi - if [[ "${HYBRID_optional_feature[Add_Spectators]}" = 'true' ]]; then + if [[ "${HYBRID_optional_feature[Add_Spectators_From_IC]}" = 'TRUE' ]]; then if [[ -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0" ' already exists.' @@ -39,7 +39,7 @@ function Prepare_Software_Input_File_Afterburner() exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ' does not exist.' fi - "${HYBRID_external_python_scripts[Add_Spectators]}"\ + "${HYBRID_external_python_scripts[Add_Spectators_From_IC]}"\ '--sampled_particle_list' "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ '--output_file' "${HYBRID_software_output_directory[Sampler]}/sampling0"\ diff --git a/bash/global_variables.bash b/bash/global_variables.bash index d31900b..aa71de5 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -50,6 +50,7 @@ function Define_Further_Global_Variables() [Executable]='HYBRID_software_executable[Afterburner]' [Input_file]='HYBRID_software_base_config_file[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' + [Add_Spectators_From_IC]='HYBRID_optional_feature[Add_Spectators_From_IC]' ) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' @@ -89,10 +90,10 @@ function Define_Further_Global_Variables() [Afterburner]='' ) declare -gA HYBRID_optional_feature=( - [Add_Spectators]='' + [Add_Spectators_From_IC]='FALSE' ) declare -rgA HYBRID_external_python_scripts=( - [Add_Spectators]="${HYBRID_python_folder}/add_spectators.py" + [Add_Spectators_From_IC]="${HYBRID_python_folder}/add_spectators.py" ) } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index b18bbfb..d44a603 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -27,12 +27,20 @@ function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() { for external_file in "${HYBRID_external_python_scripts[@]}"; do if [[ ! -f "${external_file}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The external script ' --emph "${external_file}" ' was not found.' + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The external script ' --emph "${external_file}" ' was not found.' fi done } +function Perform_Sanity_Checks_On_Optional_Features() +{ + if [[ "${HYBRID_optional_feature[Add_Spectators_From_IC]}" = 'TRUE' ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The optional feature "Add_Spectators_From_IC" should be false by default but is ' --emph "${HYBRID_optional_feature[Add_Spectators_From_IC]}" '.' + fi +} + function __static__Ensure_Executable_Exists() { local label=$1 file_path diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index f9340bf..62b7093 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -28,6 +28,7 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts + Perform_Sanity_Checks_On_Optional_Features } function Unit_Test__Afterburner-create-input-file() @@ -79,7 +80,7 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-sp function Unit_Test__Afterburner-create-input-file-with-spectators() { - HYBRID_optional_feature[Add_Spectators]=true + HYBRID_optional_feature[Add_Spectators_From_IC]='TRUE' touch "${HYBRID_software_base_config_file[Afterburner]}" mkdir -p "${HYBRID_software_output_directory[Sampler]}" local -r\ From 4e710f7baf120cb35b133814dede9555ad8b04dc Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 31 Oct 2023 12:53:30 +0100 Subject: [PATCH 197/549] Remove unnecessary test --- tests/unit_tests_Afterburner_functionality.bash | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 62b7093..eae8762 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -37,18 +37,9 @@ function Unit_Test__Afterburner-create-input-file() mkdir -p "${HYBRID_software_output_directory[Sampler]}" local -r\ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ - plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ plist_Final="${HYBRID_software_output_directory[Sampler]}/sampling0" touch "${plist_Sampler}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner - if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then - Print_Error 'The output directory and/or software input file were not properly created.' - return 1 - fi - rm -r "${HYBRID_output_directory}/"* - mkdir -p "${HYBRID_software_output_directory[Sampler]}" - touch "${plist_Sampler}" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then Print_Error 'The input file was not properly created in the output folder.' return 1 @@ -146,14 +137,14 @@ function Unit_Test__Afterburner-check-all-input() touch "${HYBRID_software_configuration_file[Afterburner]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of input file failed.' + Print_Error 'Ensuring existence of input file succeeded.' return 1 fi mkdir -p "${HYBRID_software_output_directory[Sampler]}" touch "${HYBRID_software_output_directory[Sampler]}/sampling0" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then - Print_Error 'Ensuring existence of existing folder/file failed.' + Print_Error 'Ensuring existence of existing folder/file succeeded.' return 1 fi } From a82645157d22a04a985cf9b86dfde84d3b188fc9 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 31 Oct 2023 14:52:36 +0100 Subject: [PATCH 198/549] Replace exectutable file with echo in IC + Afterburner --- tests/unit_tests_Afterburner_functionality.bash | 8 ++------ tests/unit_tests_IC_functionality.bash | 5 +---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index eae8762..bbe97b2 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -23,7 +23,7 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() HYBRID_output_directory="${HYBRIDT_tests_folder}/test_dir_Afterburner" HYBRID_software_base_config_file[Afterburner]='my_cool_conf.yaml' HYBRID_given_software_sections=( 'Afterburner' ) - HYBRID_software_executable[Afterburner]=$(which ls) # Use command as fake executable + HYBRID_software_executable[Afterburner]=$(which echo) # Use command as fake executable HYBRID_software_output_directory[Sampler]="${HYBRID_output_directory}/Sampler" HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables @@ -112,8 +112,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file-with-spectators() { - rm "${HYBRID_software_base_config_file[Afterburner]}" - rm -r "${HYBRID_output_directory}" + Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file } function Make_Test_Preliminary_Operations__Afterburner-check-all-input() @@ -161,11 +160,8 @@ function Make_Test_Preliminary_Operations__Afterburner-test-run-software() function Unit_Test__Afterburner-test-run-software() { - HYBRID_software_executable[Afterburner]="${HYBRID_output_directory}/dummy_exec_IC.bash" mkdir -p "${HYBRID_software_output_directory[Afterburner]}" local -r afterburner_terminal_output="${HYBRID_output_directory}/Afterburner/Terminal_Output.txt" - printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Afterburner]}" - chmod a+x "${HYBRID_software_executable[Afterburner]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Afterburner if [[ ! -f "${afterburner_terminal_output}" ]]; then diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 770a97b..308d0bf 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -23,7 +23,7 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() HYBRID_output_directory="./test_dir_IC" HYBRID_software_base_config_file[IC]='my_cool_conf.yaml' HYBRID_given_software_sections=( 'IC' ) - HYBRID_software_executable[IC]=$(which ls) # Use command as fake executable + HYBRID_software_executable[IC]=$(which echo) # Use command as fake executable Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } @@ -92,11 +92,8 @@ function Make_Test_Preliminary_Operations__IC-test-run-software() function Unit_Test__IC-test-run-software() { - HYBRID_software_executable[IC]="${HYBRID_output_directory}/dummy_exec_IC.bash" local -r ic_terminal_output="${HYBRID_output_directory}/IC/Terminal_Output.txt" mkdir -p "${HYBRID_software_output_directory[IC]}" - printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[IC]}" - chmod a+x "${HYBRID_software_executable[IC]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_IC if [[ ! -f "${ic_terminal_output}" ]]; then From 4ce1e3dc8b8555a47024e3c626b9d6605f9a7662 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 1 Nov 2023 10:02:48 +0100 Subject: [PATCH 199/549] Modify afterburner black box to read the config.yaml --- tests/mocks/smash_afterburner_black-box.py | 42 ++++++++++------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index ced77c7..c09f5ed 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -6,6 +6,7 @@ import re import textwrap import time +import yaml def check_config(valid_config): if args.i is None: @@ -139,29 +140,24 @@ def finish(): return def parse_command_line_config_options(): + with open(args.i, 'r') as file: + data_config = yaml.safe_load(file) + sampler_dir="" dir_config=False n_events_config=False - if(args.c == None): - print("No -c command line option was given") + + try: + n_events=data_config['General']['Nevents'] + n_events_config=True + except: + print("Nevents could not be parsed") sys.exit(1) - else: - for option in args.c: - if "Modi:" in option[0].split(): - sampler_dir=option[0].split()[5].split("}")[0] - dir_config=True - elif "Nevents:" in option[0].split(): - try: - n_events=int(option[0].split()[3].strip()) - n_events_config=True - except: - print("Nevents could not be parsed") - sys.exit(1) - - if not (dir_config and n_events_config): - print("Necessary command line options not found\n" - " -c 'Modi: { List: { File_Directory: } }'\n" - " -c 'General: { Nevents: }'") + try: + sampler_dir=data_config['Modi']['List']['File_Directory'] + dir_config=True + except: + print("File directory could not be parsed") sys.exit(1) if not os.path.isdir(sampler_dir): @@ -183,10 +179,10 @@ def parse_command_line_config_options(): help="File to the config.yaml") parser.add_argument("-o", required=False, help="Path to the output folder") - parser.add_argument("-c", required=False,action='append',nargs='+', - help="Use:" - " -c 'Modi: { List: { File_Directory: } }'" - " -c 'General: { Nevents: }'") + # parser.add_argument("-c", required=False,action='append',nargs='+', + # help="Use:" + # " -c 'Modi: { List: { File_Directory: } }'" + # " -c 'General: { Nevents: }'") args = parser.parse_args() From d0cc1db381eaff437989410d972be5700ccd7608 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 1 Nov 2023 13:57:19 +0100 Subject: [PATCH 200/549] Add -n option and check for config existence --- tests/mocks/smash_afterburner_black-box.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index c09f5ed..cff104f 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -179,10 +179,8 @@ def parse_command_line_config_options(): help="File to the config.yaml") parser.add_argument("-o", required=False, help="Path to the output folder") - # parser.add_argument("-c", required=False,action='append',nargs='+', - # help="Use:" - # " -c 'Modi: { List: { File_Directory: } }'" - # " -c 'General: { Nevents: }'") + parser.add_argument("-n", required=False, nargs='?', const='', + help="Option to not store the tabulations") args = parser.parse_args() @@ -195,10 +193,10 @@ def parse_command_line_config_options(): name_oscar = ".oscar" name_bin = ".bin" name_particles_file = "particle_lists" - sampler_dir=parse_command_line_config_options() # initialize the system check_config(config_is_valid) + sampler_dir=parse_command_line_config_options() create_folders_structure() ensure_no_output_is_overwritten() From 375987c35fb7ad6144583d1445194ee71629ea35 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Fri, 3 Nov 2023 14:30:07 +0100 Subject: [PATCH 201/549] Fix location of the sampling0 file to the Afterburner folder --- bash/Afterburner_functionality.bash | 11 ++++++----- tests/unit_tests_Afterburner_functionality.bash | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index d47b600..c4fb535 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -23,7 +23,6 @@ function Prepare_Software_Input_File_Afterburner() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi - if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ' does not exist.' @@ -42,11 +41,13 @@ function Prepare_Software_Input_File_Afterburner() "${HYBRID_external_python_scripts[Add_Spectators_From_IC]}"\ '--sampled_particle_list' "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ - '--output_file' "${HYBRID_software_output_directory[Sampler]}/sampling0"\ + '--output_file' "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" else - ln -s "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" "${HYBRID_software_output_directory[Sampler]}/sampling0" + ln -s "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" "${HYBRID_software_output_directory[Afterburner]}/sampling0" fi + echo " in after func: " + ls -l "${HYBRID_software_output_directory[Afterburner]}" } function Ensure_All_Needed_Input_Exists_Afterburner() @@ -59,9 +60,9 @@ function Ensure_All_Needed_Input_Exists_Afterburner() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'Folder ' --emph "${HYBRID_software_output_directory[Afterburner]}" ' does not exist.' fi - if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then + if [[ ! -f "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_configuration_file[Sampler]}/sampling0" ' was not found.' + 'The input file ' --emph "${HYBRID_software_configuration_file[Afterburner]}/sampling0" ' was not found.' fi } diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index bbe97b2..3a79407 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -37,7 +37,7 @@ function Unit_Test__Afterburner-create-input-file() mkdir -p "${HYBRID_software_output_directory[Sampler]}" local -r\ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ - plist_Final="${HYBRID_software_output_directory[Sampler]}/sampling0" + plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${plist_Sampler}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then @@ -77,7 +77,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() local -r\ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ - plist_Final="${HYBRID_software_output_directory[Sampler]}/sampling0" + plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${plist_Sampler}" rm -r "${HYBRID_output_directory}/"* mkdir -p "${HYBRID_software_output_directory[IC]}" "${HYBRID_software_output_directory[Sampler]}" @@ -140,7 +140,7 @@ function Unit_Test__Afterburner-check-all-input() return 1 fi mkdir -p "${HYBRID_software_output_directory[Sampler]}" - touch "${HYBRID_software_output_directory[Sampler]}/sampling0" + touch "${HYBRID_software_output_directory[Afterburner]}/sampling0" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing folder/file succeeded.' From 43d92ffa024d48b6141038f050874795ce8cbd75 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 1 Nov 2023 16:20:17 +0100 Subject: [PATCH 202/549] Add functional test of the afterburner --- bash/Afterburner_functionality.bash | 2 - tests/functional_tests_Afterburner_only.bash | 76 ++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/functional_tests_Afterburner_only.bash diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index c4fb535..34b1557 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -46,8 +46,6 @@ function Prepare_Software_Input_File_Afterburner() else ln -s "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" "${HYBRID_software_output_directory[Afterburner]}/sampling0" fi - echo " in after func: " - ls -l "${HYBRID_software_output_directory[Afterburner]}" } function Ensure_All_Needed_Input_Exists_Afterburner() diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash new file mode 100644 index 0000000..4b16c87 --- /dev/null +++ b/tests/functional_tests_Afterburner_only.bash @@ -0,0 +1,76 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# NOTE: These functional tests just require code to run and finish with zero exit code. + +function Functional_Test__do-Afterburner-only() +{ + shopt -s nullglob + local -r config_filename='Afterburner_config.yaml' + local unfinished_files output_files terminal_output_file failure_message + mkdir 'Sampler' + touch 'Sampler/particle_lists.oscar' + printf ' + Afterburner: + Executable: %s/tests/mocks/smash_afterburner_black-box.py + Software_keys: + Modi: + List: + File_Directory: "./Afterburner" + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + # Expect success and test absence of "SMASH" unfinished file + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + unfinished_files=( Afterburner/*.unfinished ) + output_files=( Afterburner/* ) + if [[ ${#unfinished_files[@]} -lt 0 ]]; then + Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' + return 1 + elif [[ ${#output_files[@]} -ne 6 ]]; then + Print_Error 'Expected ' --emph '6' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'Afterburner' 'Afterburner-success' + # Expect failure and test "SMASH" message + Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' + terminal_output_file='Afterburner/Terminal_Output.txt' + BLACK_BOX_FAIL='invalid_config'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with invalid Afterburner input.' + return 1 + elif [[ ! -f "${terminal_output_file}" ]]; then + Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' + return 1 + fi + failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') + if [[ "${failure_message}" != 'Validation of SMASH input failed.' ]]; then + Print_Error 'Unexpected failure message: ' --emph "${failure_message}" + return 1 + fi + mv 'Afterburner' 'Afterburner-invalid-config' + # Expect failure and test "SMASH" unfinished/lock files + Print_Info 'Running Hybrid-handler expecting crash in Afterburner software' + BLACK_BOX_FAIL='smash_crashes'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with Afterburner software crashing.' + return 1 + fi + unfinished_files=( Afterburner/*.{unfinished,lock} ) + if [[ ${#unfinished_files[@]} -ne 3 ]]; then + Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." + return 1 + fi + mv 'Afterburner' 'Afterburner-software-crash' +} From ab307e0e3fa4178838ebc85f06bb48431e5e6cc7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 2 Nov 2023 09:02:00 +0100 Subject: [PATCH 203/549] Add pyyaml module to python setup in GitHub actions --- .github/workflows/github-actions-on-github-servers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index da5e72d..ffc851c 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -46,7 +46,7 @@ jobs: run: python -c "import sys; print(sys.version)" # install needed non-standard packages - name: Install Python dependencies - run: python -m pip install --upgrade pip numpy + run: python -m pip install --upgrade pip numpy pyyaml # now everything should be ready to setup bash and run codebase tests - name: code_check # we set some environment variables which depend on the specific matrix element From ea550804b8e322334a18bdf1f25a812b6201894f Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Fri, 3 Nov 2023 08:51:08 +0100 Subject: [PATCH 204/549] Add functional afterburner test when adding spectators --- tests/functional_tests_Afterburner_only.bash | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 4b16c87..3b545c3 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -73,4 +73,31 @@ function Functional_Test__do-Afterburner-only() return 1 fi mv 'Afterburner' 'Afterburner-software-crash' + # Expect success and test the add_spectator functionality + mkdir 'IC' + touch 'IC/config.yaml' 'IC/SMASH_IC.oscar' + printf ' + Afterburner: + Executable: %s/tests/mocks/smash_afterburner_black-box.py + Add_Spectators_From_IC: TRUE + Software_keys: + Modi: + List: + File_Directory: "./Afterburner" + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + unfinished_files=( Afterburner/*.unfinished ) + output_files=( Afterburner/* ) + if [[ ${#unfinished_files[@]} -lt 0 ]]; then + Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' + return 1 + elif [[ ${#output_files[@]} -ne 6 ]]; then + Print_Error 'Expected ' --emph '6' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'Afterburner' 'Afterburner-success-with-spectators' } From 9f038fac769aa2a260d8e7c04b9adb0741bb9b77 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 6 Nov 2023 10:34:06 +0100 Subject: [PATCH 205/549] remove sanity_check function and add static check in functionality --- bash/Afterburner_functionality.bash | 3 +- bash/global_variables.bash | 12 ++--- bash/sanity_checks.bash | 12 +---- tests/functional_tests_Afterburner_only.bash | 48 ++++++++----------- .../unit_tests_Afterburner_functionality.bash | 1 - 5 files changed, 31 insertions(+), 45 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 34b1557..28df14f 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -23,6 +23,7 @@ function Prepare_Software_Input_File_Afterburner() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi + if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ' does not exist.' @@ -58,7 +59,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'Folder ' --emph "${HYBRID_software_output_directory[Afterburner]}" ' does not exist.' fi - if [[ ! -f "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then + if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The input file ' --emph "${HYBRID_software_configuration_file[Afterburner]}/sampling0" ' was not found.' fi diff --git a/bash/global_variables.bash b/bash/global_variables.bash index aa71de5..5b89a15 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -76,6 +76,12 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) + declare -gA HYBRID_optional_feature=( + [Add_Spectators_From_IC]='FALSE' + ) + declare -rgA HYBRID_external_python_scripts=( + [Add_Spectators_From_IC]="${HYBRID_python_folder}/add_spectators.py" + ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( [IC]='' @@ -89,12 +95,6 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) - declare -gA HYBRID_optional_feature=( - [Add_Spectators_From_IC]='FALSE' - ) - declare -rgA HYBRID_external_python_scripts=( - [Add_Spectators_From_IC]="${HYBRID_python_folder}/add_spectators.py" - ) } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index d44a603..9047d95 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -27,20 +27,12 @@ function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() { for external_file in "${HYBRID_external_python_scripts[@]}"; do if [[ ! -f "${external_file}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The external script ' --emph "${external_file}" ' was not found.' + exit_code=${HYBRID_fatal_file_not_found} Print_Internal_And_Exit\ + 'The python script ' --emph "${external_file}" ' was not found.' fi done } -function Perform_Sanity_Checks_On_Optional_Features() -{ - if [[ "${HYBRID_optional_feature[Add_Spectators_From_IC]}" = 'TRUE' ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The optional feature "Add_Spectators_From_IC" should be false by default but is ' --emph "${HYBRID_optional_feature[Add_Spectators_From_IC]}" '.' - fi -} - function __static__Ensure_Executable_Exists() { local label=$1 file_path diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 3b545c3..eee5ee7 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -9,10 +9,27 @@ # NOTE: These functional tests just require code to run and finish with zero exit code. +function __static__Check_Successful_Handler_Run() +{ + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + unfinished_files=( Afterburner/*.unfinished ) + output_files=( Afterburner/* ) + if [[ ${#unfinished_files[@]} -lt 0 ]]; then + Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' + return 1 + elif [[ ${#output_files[@]} -ne 6 ]]; then + Print_Error 'Expected ' --emph '6' " output files, but ${#output_files[@]} found." + return 1 + fi +} + function Functional_Test__do-Afterburner-only() { shopt -s nullglob - local -r config_filename='Afterburner_config.yaml' + local -r config_filename='Handler_config.yaml' local unfinished_files output_files terminal_output_file failure_message mkdir 'Sampler' touch 'Sampler/particle_lists.oscar' @@ -27,19 +44,7 @@ function Functional_Test__do-Afterburner-only() # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -ne 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly failed.' - return 1 - fi - unfinished_files=( Afterburner/*.unfinished ) - output_files=( Afterburner/* ) - if [[ ${#unfinished_files[@]} -lt 0 ]]; then - Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' - return 1 - elif [[ ${#output_files[@]} -ne 6 ]]; then - Print_Error 'Expected ' --emph '6' " output files, but ${#output_files[@]} found." - return 1 - fi + __static__Check_Successful_Handler_Run || return 1 mv 'Afterburner' 'Afterburner-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' @@ -74,6 +79,7 @@ function Functional_Test__do-Afterburner-only() fi mv 'Afterburner' 'Afterburner-software-crash' # Expect success and test the add_spectator functionality + Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' mkdir 'IC' touch 'IC/config.yaml' 'IC/SMASH_IC.oscar' printf ' @@ -86,18 +92,6 @@ function Functional_Test__do-Afterburner-only() File_Directory: "./Afterburner" ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -ne 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly failed.' - return 1 - fi - unfinished_files=( Afterburner/*.unfinished ) - output_files=( Afterburner/* ) - if [[ ${#unfinished_files[@]} -lt 0 ]]; then - Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' - return 1 - elif [[ ${#output_files[@]} -ne 6 ]]; then - Print_Error 'Expected ' --emph '6' " output files, but ${#output_files[@]} found." - return 1 - fi + __static__Check_Successful_Handler_Run || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' } diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 3a79407..5eb869b 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -28,7 +28,6 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts - Perform_Sanity_Checks_On_Optional_Features } function Unit_Test__Afterburner-create-input-file() From 115679c28026c2757de91147b4ed8c5deb65bf46 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 6 Nov 2023 11:26:21 +0100 Subject: [PATCH 206/549] Minor fixes. --- tests/unit_tests_Afterburner_functionality.bash | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 5eb869b..ce70d40 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -24,8 +24,6 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() HYBRID_software_base_config_file[Afterburner]='my_cool_conf.yaml' HYBRID_given_software_sections=( 'Afterburner' ) HYBRID_software_executable[Afterburner]=$(which echo) # Use command as fake executable - HYBRID_software_output_directory[Sampler]="${HYBRID_output_directory}/Sampler" - HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts } @@ -54,7 +52,6 @@ function Unit_Test__Afterburner-create-input-file() Print_Error 'Preparation of input succeeded even though the particle_list.oscar does not exist.' return 1 fi - rm -r "${HYBRID_output_directory}/"* } function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file() @@ -71,16 +68,12 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-sp function Unit_Test__Afterburner-create-input-file-with-spectators() { HYBRID_optional_feature[Add_Spectators_From_IC]='TRUE' - touch "${HYBRID_software_base_config_file[Afterburner]}" - mkdir -p "${HYBRID_software_output_directory[Sampler]}" + mkdir -p "${HYBRID_software_output_directory[Sampler]}" "${HYBRID_software_output_directory[IC]}" local -r\ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" - touch "${plist_Sampler}" - rm -r "${HYBRID_output_directory}/"* - mkdir -p "${HYBRID_software_output_directory[IC]}" "${HYBRID_software_output_directory[Sampler]}" - touch "${plist_Sampler}" "${plist_Final}" + touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Preparation succeeded even though the final particle list already exists.' @@ -92,15 +85,14 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() Print_Error 'Preparation succeeded even though the config.yaml of the IC does not exist.' return 1 fi - rm "${HYBRID_output_directory}/Afterburner/"* + rm "${HYBRID_software_output_directory[Afterburner]}/"* touch "${HYBRID_software_output_directory[IC]}/config.yaml" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Preparation succeeded even though the SMASH_IC.oscar does not exist.' return 1 fi - rm -r "${HYBRID_output_directory}/"* - mkdir -p "${HYBRID_software_output_directory[IC]}" "${HYBRID_software_output_directory[Sampler]}" "${HYBRID_software_output_directory[Afterburner]}" + rm "${HYBRID_software_output_directory[Afterburner]}/"* touch "${HYBRID_software_output_directory[IC]}/config.yaml" "${plist_Sampler}" "${plist_IC}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${plist_Final}" ]]; then From acd45e5a0f6b78474dd5170a483d1813b5fbae42 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 6 Nov 2023 13:22:56 +0100 Subject: [PATCH 207/549] Further fixes --- bash/Afterburner_functionality.bash | 2 +- tests/unit_tests_Afterburner_functionality.bash | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 28df14f..c8021aa 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -57,7 +57,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() fi if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'Folder ' --emph "${HYBRID_software_output_directory[Afterburner]}" ' does not exist.' + 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" ' does not exist.' fi if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index ce70d40..735fa18 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -127,14 +127,13 @@ function Unit_Test__Afterburner-check-all-input() touch "${HYBRID_software_configuration_file[Afterburner]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of input file succeeded.' + Print_Error 'Ensuring existence of auxiliary input data file succeeded.' return 1 fi - mkdir -p "${HYBRID_software_output_directory[Sampler]}" touch "${HYBRID_software_output_directory[Afterburner]}/sampling0" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then - Print_Error 'Ensuring existence of existing folder/file succeeded.' + Print_Error 'Ensuring existence of existing folder/file unexpectedly failed.' return 1 fi } @@ -152,7 +151,7 @@ function Make_Test_Preliminary_Operations__Afterburner-test-run-software() function Unit_Test__Afterburner-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Afterburner]}" - local -r afterburner_terminal_output="${HYBRID_output_directory}/Afterburner/Terminal_Output.txt" + local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Afterburner if [[ ! -f "${afterburner_terminal_output}" ]]; then From 728bab7cdaaf74313cc8c7242168b1e654789e8a Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 8 Nov 2023 09:36:46 +0100 Subject: [PATCH 208/549] Add checks of symbolic links --- .../unit_tests_Afterburner_functionality.bash | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 735fa18..99fe1a9 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -40,6 +40,9 @@ function Unit_Test__Afterburner-create-input-file() if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then Print_Error 'The input file was not properly created in the output folder.' return 1 + elif [[ ! -L "${plist_Final}" ]]; then + Print_Error 'The input particle list was not properly created in the output folder.' + return 1 fi Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then @@ -136,6 +139,27 @@ function Unit_Test__Afterburner-check-all-input() Print_Error 'Ensuring existence of existing folder/file unexpectedly failed.' return 1 fi + rm "${HYBRID_software_output_directory[Afterburner]}/sampling0" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of non-existing file unexpectedly succeeded.' + return 1 + fi + mkdir "${HYBRID_software_output_directory[Sampler]}" + touch "${HYBRID_software_output_directory[Sampler]}/original_sampling0" + ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampling0"\ + "${HYBRID_software_output_directory[Afterburner]}/sampling0" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null + if [[ $? -ne 0 ]]; then + Print_Error 'Ensuring existence of existing file unexpectedly failed.' + return 1 + fi + rm "${HYBRID_software_output_directory[Sampler]}/original_sampling0" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of a link to a non-existing file unexpectedly succeeded.' + return 1 + fi } function Clean_Tests_Environment_For_Following_Test__Afterburner-check-all-input() From 4586483ca26729b22c7b346256a76017234da826 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 8 Nov 2023 11:19:55 +0100 Subject: [PATCH 209/549] Include appropriate linebreaks --- bash/Afterburner_functionality.bash | 21 ++++++++++++------- .../unit_tests_Afterburner_functionality.bash | 10 ++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index c8021aa..4788c02 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -26,18 +26,22 @@ function Prepare_Software_Input_File_Afterburner() if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ' does not exist.' + 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ + ' does not exist.' fi if [[ "${HYBRID_optional_feature[Add_Spectators_From_IC]}" = 'TRUE' ]]; then if [[ -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0" ' already exists.' + 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0"\ + ' already exists.' elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml" ' does not exist which is needed to check number of initial nucleons.' + 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ + ' does not exist which is needed to check number of initial nucleons.' elif [[ ! -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ' does not exist.' + 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ + ' does not exist.' fi "${HYBRID_external_python_scripts[Add_Spectators_From_IC]}"\ '--sampled_particle_list' "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ @@ -45,7 +49,8 @@ function Prepare_Software_Input_File_Afterburner() '--output_file' "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" else - ln -s "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" "${HYBRID_software_output_directory[Afterburner]}/sampling0" + ln -s "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ + "${HYBRID_software_output_directory[Afterburner]}/sampling0" fi } @@ -57,11 +62,13 @@ function Ensure_All_Needed_Input_Exists_Afterburner() fi if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" ' does not exist.' + 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}"\ + ' does not exist.' fi if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_configuration_file[Afterburner]}/sampling0" ' was not found.' + 'The input file ' --emph "${HYBRID_software_configuration_file[Afterburner]}/sampling0"\ + ' was not found.' fi } diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 99fe1a9..d01a86e 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -71,7 +71,9 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-sp function Unit_Test__Afterburner-create-input-file-with-spectators() { HYBRID_optional_feature[Add_Spectators_From_IC]='TRUE' - mkdir -p "${HYBRID_software_output_directory[Sampler]}" "${HYBRID_software_output_directory[IC]}" + mkdir -p "${HYBRID_software_output_directory[Sampler]}"\ + "${HYBRID_software_output_directory[IC]}"\ + "${HYBRID_software_output_directory[Afterburner]}" local -r\ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ @@ -82,7 +84,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() Print_Error 'Preparation succeeded even though the final particle list already exists.' return 1 fi - rm "${plist_Final}" "${HYBRID_output_directory}/Afterburner/"* + rm "${HYBRID_software_output_directory[Afterburner]}/"* Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Preparation succeeded even though the config.yaml of the IC does not exist.' @@ -183,7 +185,9 @@ function Unit_Test__Afterburner-test-run-software() return 1 fi terminal_output_result=$(< "${afterburner_terminal_output}") - correct_result="-i ${HYBRID_software_configuration_file[Afterburner]} -o ${HYBRID_software_output_directory[Afterburner]} -n" + printf -v correct_result '%s'\ + "-i ${HYBRID_software_configuration_file[Afterburner]} "\ + "-o ${HYBRID_software_output_directory[Afterburner]} -n" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then Print_Error 'The terminal output has not the expected content.' return 1 From c68c495dae113ed515eb9adad4d191b59de2f896 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Fri, 10 Nov 2023 09:42:21 +0100 Subject: [PATCH 210/549] Replace Add_spectators_from_IC and add line --- bash/Afterburner_functionality.bash | 5 +++-- bash/global_variables.bash | 6 +++--- tests/functional_tests_Afterburner_only.bash | 2 +- tests/unit_tests_Afterburner_functionality.bash | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 4788c02..367c92b 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -29,7 +29,7 @@ function Prepare_Software_Input_File_Afterburner() 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ ' does not exist.' fi - if [[ "${HYBRID_optional_feature[Add_Spectators_From_IC]}" = 'TRUE' ]]; then + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then if [[ -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0"\ @@ -43,7 +43,7 @@ function Prepare_Software_Input_File_Afterburner() 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ ' does not exist.' fi - "${HYBRID_external_python_scripts[Add_Spectators_From_IC]}"\ + "${HYBRID_external_python_scripts[Add_spectators_from_IC]}"\ '--sampled_particle_list' "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ '--output_file' "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ @@ -82,4 +82,5 @@ function Run_Software_Afterburner() >> "${afterburner_terminal_output}" } + Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 5b89a15..2de048d 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -50,7 +50,7 @@ function Define_Further_Global_Variables() [Executable]='HYBRID_software_executable[Afterburner]' [Input_file]='HYBRID_software_base_config_file[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' - [Add_Spectators_From_IC]='HYBRID_optional_feature[Add_Spectators_From_IC]' + [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' ) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' @@ -77,10 +77,10 @@ function Define_Further_Global_Variables() [Afterburner]='' ) declare -gA HYBRID_optional_feature=( - [Add_Spectators_From_IC]='FALSE' + [Add_spectators_from_IC]='FALSE' ) declare -rgA HYBRID_external_python_scripts=( - [Add_Spectators_From_IC]="${HYBRID_python_folder}/add_spectators.py" + [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index eee5ee7..f4f1156 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -85,7 +85,7 @@ function Functional_Test__do-Afterburner-only() printf ' Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py - Add_Spectators_From_IC: TRUE + Add_spectators_from_IC: TRUE Software_keys: Modi: List: diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index d01a86e..767c24a 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -70,7 +70,7 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-sp function Unit_Test__Afterburner-create-input-file-with-spectators() { - HYBRID_optional_feature[Add_Spectators_From_IC]='TRUE' + HYBRID_optional_feature[Add_spectators_from_IC]='TRUE' mkdir -p "${HYBRID_software_output_directory[Sampler]}"\ "${HYBRID_software_output_directory[IC]}"\ "${HYBRID_software_output_directory[Afterburner]}" From 5d50201f91ac42205b5bfdb76676b602058bc1cc Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Fri, 10 Nov 2023 10:31:09 +0100 Subject: [PATCH 211/549] Add comment and pass argument to __static__Check_Successful_Handler_Run --- bash/Afterburner_functionality.bash | 1 + bash/global_variables.bash | 6 +++--- tests/functional_tests_Afterburner_only.bash | 6 +++--- tests/unit_tests_Afterburner_functionality.bash | 5 ----- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 367c92b..2b4b8c4 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -65,6 +65,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}"\ ' does not exist.' fi + # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The input file ' --emph "${HYBRID_software_configuration_file[Afterburner]}/sampling0"\ diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 2de048d..93f25c1 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -28,6 +28,9 @@ function Define_Further_Global_Variables() ) readonly HYBRID_default_configurations_folder="${HYBRID_top_level_path}/configs" readonly HYBRID_python_folder="${HYBRID_top_level_path}/python" + declare -rgA HYBRID_external_python_scripts=( + [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" + ) # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=() @@ -79,9 +82,6 @@ function Define_Further_Global_Variables() declare -gA HYBRID_optional_feature=( [Add_spectators_from_IC]='FALSE' ) - declare -rgA HYBRID_external_python_scripts=( - [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" - ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( [IC]='' diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index f4f1156..e72d275 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -11,7 +11,7 @@ function __static__Check_Successful_Handler_Run() { - if [[ $? -ne 0 ]]; then + if [[ $1 -ne 0 ]]; then Print_Error 'Hybrid-handler unexpectedly failed.' return 1 fi @@ -44,7 +44,7 @@ function Functional_Test__do-Afterburner-only() # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - __static__Check_Successful_Handler_Run || return 1 + __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' @@ -92,6 +92,6 @@ function Functional_Test__do-Afterburner-only() File_Directory: "./Afterburner" ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - __static__Check_Successful_Handler_Run || return 1 + __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' } diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 767c24a..5e43bf1 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -142,11 +142,6 @@ function Unit_Test__Afterburner-check-all-input() return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/sampling0" - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null - if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of non-existing file unexpectedly succeeded.' - return 1 - fi mkdir "${HYBRID_software_output_directory[Sampler]}" touch "${HYBRID_software_output_directory[Sampler]}/original_sampling0" ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampling0"\ From 7370252a29ee37dcfe2a815462782aa25fb22b48 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Fri, 10 Nov 2023 13:45:27 +0100 Subject: [PATCH 212/549] cd into output directory in all modules --- bash/Afterburner_functionality.bash | 1 + bash/Hydro_functionality.bash | 1 + bash/IC_functionality.bash | 1 + tests/functional_tests_Afterburner_only.bash | 4 ++-- tests/unit_tests_Hydro_functionality.bash | 4 ++-- tests/unit_tests_IC_functionality.bash | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 2b4b8c4..e232ac0 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -75,6 +75,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() function Run_Software_Afterburner() { + cd "${HYBRID_software_output_directory[Afterburner]}" local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" "${HYBRID_software_executable[Afterburner]}" \ '-i' "${HYBRID_software_configuration_file[Afterburner]}" \ diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 707ecad..9b19f4b 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -43,6 +43,7 @@ function Ensure_All_Needed_Input_Exists_Hydro() function Run_Software_Hydro() { + cd "${HYBRID_software_output_directory[Hydro]}" local -r\ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ ic_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 8d8c237..25abdfb 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -39,6 +39,7 @@ function Ensure_All_Needed_Input_Exists_IC() function Run_Software_IC() { + cd "${HYBRID_software_output_directory[IC]}" local ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" "${HYBRID_software_executable[IC]}" \ '-i' "${HYBRID_software_configuration_file[IC]}" \ diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index e72d275..c6353b0 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -39,7 +39,7 @@ function Functional_Test__do-Afterburner-only() Software_keys: Modi: List: - File_Directory: "./Afterburner" + File_Directory: "." ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' @@ -89,7 +89,7 @@ function Functional_Test__do-Afterburner-only() Software_keys: Modi: List: - File_Directory: "./Afterburner" + File_Directory: "." ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 6fa6115..89aeb43 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -20,7 +20,7 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done Define_Further_Global_Variables - HYBRID_output_directory="./test_dir_Hydro" + HYBRID_output_directory="${HYBRIDT_tests_folder}/test_dir_Hydro" HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' HYBRID_given_software_sections=('Hydro' ) HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" @@ -93,7 +93,7 @@ function Make_Test_Preliminary_Operations__Hydro-test-run-software() function Unit_Test__Hydro-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" - local -r hydro_terminal_output="${HYBRID_output_directory}/Hydro/Terminal_Output.txt"\ + local -r hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt"\ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" local terminal_output_result correct_result diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 308d0bf..e714d0e 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -20,7 +20,7 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done Define_Further_Global_Variables - HYBRID_output_directory="./test_dir_IC" + HYBRID_output_directory="${HYBRIDT_tests_folder}/test_dir_IC" HYBRID_software_base_config_file[IC]='my_cool_conf.yaml' HYBRID_given_software_sections=( 'IC' ) HYBRID_software_executable[IC]=$(which echo) # Use command as fake executable From 25aaec5baa622fc453f04b03aeb86f816bd884e0 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 13 Nov 2023 11:46:09 +0100 Subject: [PATCH 213/549] Change folder where the unit tests run. --- tests/unit_tests_Afterburner_functionality.bash | 2 +- tests/unit_tests_Hydro_functionality.bash | 2 +- tests/unit_tests_IC_functionality.bash | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 5e43bf1..f8060a8 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -20,7 +20,7 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done Define_Further_Global_Variables - HYBRID_output_directory="${HYBRIDT_tests_folder}/test_dir_Afterburner" + HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Afterburner" HYBRID_software_base_config_file[Afterburner]='my_cool_conf.yaml' HYBRID_given_software_sections=( 'Afterburner' ) HYBRID_software_executable[Afterburner]=$(which echo) # Use command as fake executable diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 89aeb43..af31f57 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -20,7 +20,7 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done Define_Further_Global_Variables - HYBRID_output_directory="${HYBRIDT_tests_folder}/test_dir_Hydro" + HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Hydro" HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' HYBRID_given_software_sections=('Hydro' ) HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index e714d0e..2429403 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -20,7 +20,7 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done Define_Further_Global_Variables - HYBRID_output_directory="${HYBRIDT_tests_folder}/test_dir_IC" + HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_IC" HYBRID_software_base_config_file[IC]='my_cool_conf.yaml' HYBRID_given_software_sections=( 'IC' ) HYBRID_software_executable[IC]=$(which echo) # Use command as fake executable From 383b2f352ffc6ed8ce6281c60e11f8b6389b62eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Thu, 2 Nov 2023 15:06:53 +0100 Subject: [PATCH 214/549] Add necessary global variables --- bash/global_variables.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 93f25c1..42c78e8 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -85,14 +85,14 @@ function Define_Further_Global_Variables() # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( [IC]='' - [Hydro]='' - [Sampler]='' + [Hydro]='Hydro' + [Sampler]='Sampler' [Afterburner]='' ) declare -gA HYBRID_software_configuration_file=( [IC]='' [Hydro]='' - [Sampler]='' + [Sampler]='Sampler' [Afterburner]='' ) } From 916fbb3a0929c2ff3c1b5349dd03cb4f23fc87b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Thu, 2 Nov 2023 15:08:13 +0100 Subject: [PATCH 215/549] Add sampler functionality --- bash/Sampler_functionality.bash | 34 ++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 2f4575a..0c28716 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -9,17 +9,45 @@ function Prepare_Software_Input_File_Sampler() { - Print_Not_Implemented_Function_Error + mkdir -p "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} + if [[ -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}" ' is already existing.' + elif [[ ! -f "${HYBRID_software_base_config_file[Sampler]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Sampler]}" ' was not found.' + fi + + cp "${HYBRID_software_base_config_file[Sampler]}"\ + "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} + if [[ "${HYBRID_software_new_input_keys[Sampler]}" != '' ]]; then + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + 'TXT' "${HYBRID_software_configuration_file[Sampler]}" "${HYBRID_software_new_input_keys[Sampler]}" + fi } function Ensure_All_Needed_Input_Exists_Sampler() { - Print_Not_Implemented_Function_Error + if [[ ! -d "${HYBRID_software_output_directory[Sampler]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}" ' does not exist.' + fi + if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}" ' was not found.' + fi + if [[ ! -f "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Freezeout hypersurface ' --emph "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" ' does not exist.' + fi } function Run_Software_Sampler() { - Print_Not_Implemented_Function_Error + local -r Sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}"\ + Sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" + "${HYBRID_software_executable[Sampler]}" "events" 1 \ + "${Sampler_config_file_path}" >> "${Sampler_terminal_output}" } From 4719b096544e371d30dc6f30f81e48e059cc6c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Thu, 2 Nov 2023 15:09:11 +0100 Subject: [PATCH 216/549] Add unit tests for sampler functionality --- tests/unit_tests_Sampler_functionality.bash | 124 ++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/unit_tests_Sampler_functionality.bash diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash new file mode 100644 index 0000000..a68b4f0 --- /dev/null +++ b/tests/unit_tests_Sampler_functionality.bash @@ -0,0 +1,124 @@ + #=================================================== + # + # Copyright (c) 2023 + # SMASH Hybrid Team + # + # GNU General Public License (GPLv3 or later) + # + #=================================================== + + + + function Make_Test_Preliminary_Operations__Sampler-create-input-file() + { + local file_to_be_sourced list_of_files + list_of_files=( + 'Sampler_functionality.bash' + 'global_variables.bash' + 'software_input_functionality.bash' + 'sanity_checks.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables + HYBRID_output_directory="./test_dir_Sampler" + HYBRID_software_base_config_file[Sampler]='fake_sampler_config' + HYBRID_given_software_sections=('Sampler') + HYBRID_software_output_directory[Hydro]="${HYBRID_output_directory}/Hydro" + HYBRID_software_executable[Sampler]="${HYBRID_output_directory}/dummy_exec_Sampler.bash" + mkdir -p ${HYBRID_output_directory} + printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Sampler]}" + chmod a+x "${HYBRID_software_executable[Sampler]}" + Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables + + } + + function Unit_Test__Sampler-create-input-file() + { + touch "${HYBRID_software_base_config_file[Sampler]}" + mkdir -p "${HYBRID_software_output_directory[Hydro]}" + local -r plist_hydro="${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + touch "${plist_hydro}" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler + if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then + Print_Error 'The output directory and/or software input file were not properly created.' + return 1 + fi + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation of input with existent config succeeded.' + return 1 + fi + rm -r "${HYBRID_output_directory}/"* + } + + function Clean_Tests_Environment_For_Following_Test__Sampler-create-input-file() + { + rm "${HYBRID_software_base_config_file[Sampler]}" + rm -r "${HYBRID_output_directory}" + } + + function Make_Test_Preliminary_Operations__Sampler-check-all-input() + { + Make_Test_Preliminary_Operations__Sampler-create-input-file + } + + function Unit_Test__Sampler-check-all-input() + { + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing output directory succeeded, although failure was expected.' + return 1 + fi + mkdir -p "${HYBRID_software_output_directory[Sampler]}" \ + "${HYBRID_software_output_directory[Hydro]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing config file succeeded, although failure was expected.' + return 1 + fi + touch "${HYBRID_software_configuration_file[Sampler]}" \ + "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + if [[ $? -ne 0 ]]; then + Print_Error 'Ensuring existence of existing folder/file failed.' + return 1 + fi + } + + function Clean_Tests_Environment_For_Following_Test__Sampler-check-all-input() + { + rm -r "${HYBRID_output_directory}" + } + + function Make_Test_Preliminary_Operations__Sampler-test-run-software() + { + Make_Test_Preliminary_Operations__Sampler-create-input-file + } + + function Unit_Test__Sampler-test-run-software() + { + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + local -r Sampler_terminal_output="${HYBRID_output_directory}/Sampler/Terminal_Output.txt"\ + Sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}"\ + Hydro_output_file_path="${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + local terminal_output_result correct_result + Call_Codebase_Function_In_Subshell Run_Software_Sampler + if [[ ! -f "${Sampler_terminal_output}" ]]; then + Print_Error 'The terminal output was not created.' + return 1 + fi + terminal_output_result=$(< "${Sampler_terminal_output}") + correct_result="events 1 ${HYBRID_software_configuration_file[Sampler]}" + if [[ "${terminal_output_result}" != "${correct_result}" ]]; then + Print_Error 'The terminal output has not the expected content.' + return 1 + fi + + } + + function Clean_Tests_Environment_For_Following_Test__Sampler-test-run-software() + { + Clean_Tests_Environment_For_Following_Test__Sampler-check-all-input + } \ No newline at end of file From 586e1f54713ac5fe97d35320a8e29c3e9e649874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Thu, 2 Nov 2023 15:09:49 +0100 Subject: [PATCH 217/549] Add functional tests for Sampler --- tests/functional_tests_Sampler_only.bash | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/functional_tests_Sampler_only.bash diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash new file mode 100644 index 0000000..4770524 --- /dev/null +++ b/tests/functional_tests_Sampler_only.bash @@ -0,0 +1,76 @@ + #=================================================== + # + # Copyright (c) 2023 + # SMASH Hybrid Team + # + # GNU General Public License (GPLv3 or later) + # + #=================================================== + + function Functional_Test__do-Sampler-only() + { + #how to define the output directory + shopt -s nullglob + local -r Hybrid_handler_config='hybrid_config' + local output_files + mkdir -p './Hydro' + touch './Hydro/freezeout.dat' + printf ' + Sampler: + Executable: %s/tests/mocks/sampler_black_box.py + Software_keys: + surface: %s + spectra_dir: %s + ' "${HYBRIDT_repository_top_level_path}"\ + "Hydro/freezeout.dat"\ + "Sampler" > "${Hybrid_handler_config}" + # Expect success and test presence of particle_lists + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${Hybrid_handler_config}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + output_files=( Sampler/* ) + if [[ ${#output_files[@]} -ne 3 ]]; then + Print_Error 'Expected ' --emph '3' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'Sampler' 'Sampler-success' + + # Expect failure and test terminal output + Print_Info 'Running Hybrid-handler expecting crash in Sampler' + BLACK_BOX_FAIL='true'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${Hybrid_handler_config}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with Sampler crashing.' + return 1 + fi + mv 'Sampler' 'Sampler-crash' + + # Expect failure + Print_Info 'Running Hybrid-handler expecting invalid config argument' + BLACK_BOX_FAIL='false' + mkdir -p Sampler + local -r invalid_sampler_config="invalid_hadron_sampler" + local terminal_output_file='Sampler/Terminal_Output.txt' + touch "${invalid_sampler_config}" + printf ' + Sampler: + Executable: %s/tests/mocks/sampler_black_box.py + Input_file: %s + ' "${HYBRIDT_repository_top_level_path}"\ + "${invalid_sampler_config}" > "${Hybrid_handler_config}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${Hybrid_handler_config}"\ + > "${terminal_output_file}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Sampler.' + return 1 + elif [[ ! -f "${terminal_output_file}" ]]; then + Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' + return 1 + fi + rm "${invalid_sampler_config}" + mv 'Sampler' 'Sampler-invalid-config' + + } \ No newline at end of file From 4df726a53fa62e648e6d538b9bfdd4deef48176c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Sat, 11 Nov 2023 16:11:02 +0100 Subject: [PATCH 218/549] Implement Alessandro's suggestions to improve the code --- bash/Sampler_functionality.bash | 98 +++++--- bash/global_variables.bash | 6 +- configs/hadron_sampler | 4 +- tests/functional_tests_Sampler_only.bash | 144 ++++++------ tests/unit_tests_Hydro_functionality.bash | 2 - tests/unit_tests_Sampler_functionality.bash | 241 +++++++++++--------- 6 files changed, 274 insertions(+), 221 deletions(-) diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 0c28716..3fcc8d3 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -10,45 +10,87 @@ function Prepare_Software_Input_File_Sampler() { mkdir -p "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} - if [[ -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}" ' is already existing.' - elif [[ ! -f "${HYBRID_software_base_config_file[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Sampler]}" ' was not found.' - fi + if [[ -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}"\ + ' is already existing.' + elif [[ ! -f "${HYBRID_software_base_config_file[Sampler]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Sampler]}"\ + ' was not found.' + fi + cp "${HYBRID_software_base_config_file[Sampler]}"\ + "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} + if [[ "${HYBRID_software_new_input_keys[Sampler]}" != '' ]]; then + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + 'TXT' "${HYBRID_software_configuration_file[Sampler]}"\ + "${HYBRID_software_new_input_keys[Sampler]}" + fi + + # Replace potentially relative paths in Sampler config with absolute paths + local freezeout_path output_directory + freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') + output_directory=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'spectra_dir') + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + 'TXT' "${HYBRID_software_configuration_file[Sampler]}"\ + "$(printf "%s: %s\n"\ + 'surface' "${freezeout_path}"\ + 'spectra_dir' "${output_directory}")" - cp "${HYBRID_software_base_config_file[Sampler]}"\ - "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} - if [[ "${HYBRID_software_new_input_keys[Sampler]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ - 'TXT' "${HYBRID_software_configuration_file[Sampler]}" "${HYBRID_software_new_input_keys[Sampler]}" - fi + # The following symbolic link is not needed by the sampler, as it refers to its input file. + # However, we want to have all input in the output folder for future easier reproducibility + # (and we do so for all blocks in the workflow). + if [[ "$( dirname "${freezeout_path}" )" != "${HYBRID_software_output_directory[Sampler]}" ]]; then + ln -s "${freezeout_path}"\ + "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" + fi } function Ensure_All_Needed_Input_Exists_Sampler() { - if [[ ! -d "${HYBRID_software_output_directory[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}" ' does not exist.' - fi - if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then + if [[ ! -d "${HYBRID_software_output_directory[Sampler]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}" ' was not found.' - fi - if [[ ! -f "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'Freezeout hypersurface ' --emph "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" ' does not exist.' - fi + 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}"\ + ' does not exist.' + fi + if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}"\ + ' was not found.' + fi + local freezeout_path + freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') + echo "DGFSHJ " "${freezeout_path}" + if [[ ! -f "${freezeout_path}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Freezeout hypersurface ' --emph "${freezeout_path}" ' does not exist.' + fi } function Run_Software_Sampler() { - local -r Sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}"\ - Sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" - "${HYBRID_software_executable[Sampler]}" "events" 1 \ - "${Sampler_config_file_path}" >> "${Sampler_terminal_output}" + local -r sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" + local sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" + cd "${HYBRID_software_output_directory[Sampler]}" + "${HYBRID_software_executable[Sampler]}" 'events' '1' \ + "${sampler_config_file_path}" >> "${sampler_terminal_output}" } +function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() +{ + local field value + field=$1 + value=$(awk -v name="${field}" '$1 == name {print $2; exit}'\ + "${HYBRID_software_configuration_file[Sampler]}") + ( + cd "${HYBRID_software_output_directory[Sampler]}" + # If realpath succeeds, it prints the path that is the result of the function + if ! realpath "${value}" 2> /dev/null; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Unable to transform relatrive path "' --emph "${value}"\ + '" into global one.' + fi + ) +} Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 42c78e8..93f25c1 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -85,14 +85,14 @@ function Define_Further_Global_Variables() # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( [IC]='' - [Hydro]='Hydro' - [Sampler]='Sampler' + [Hydro]='' + [Sampler]='' [Afterburner]='' ) declare -gA HYBRID_software_configuration_file=( [IC]='' [Hydro]='' - [Sampler]='Sampler' + [Sampler]='' [Afterburner]='' ) } diff --git a/configs/hadron_sampler b/configs/hadron_sampler index 27acf67..24bb38e 100644 --- a/configs/hadron_sampler +++ b/configs/hadron_sampler @@ -1,5 +1,5 @@ -surface /path/to/freezeout/surface -spectra_dir /path/to/output +surface ../Hydro/freezeout.dat +spectra_dir . number_of_events 100 weakContribution 0 shear 1 diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 4770524..6e7b87c 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -1,76 +1,76 @@ - #=================================================== - # - # Copyright (c) 2023 - # SMASH Hybrid Team - # - # GNU General Public License (GPLv3 or later) - # - #=================================================== +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== - function Functional_Test__do-Sampler-only() - { - #how to define the output directory - shopt -s nullglob - local -r Hybrid_handler_config='hybrid_config' - local output_files - mkdir -p './Hydro' - touch './Hydro/freezeout.dat' - printf ' - Sampler: - Executable: %s/tests/mocks/sampler_black_box.py - Software_keys: - surface: %s - spectra_dir: %s - ' "${HYBRIDT_repository_top_level_path}"\ - "Hydro/freezeout.dat"\ - "Sampler" > "${Hybrid_handler_config}" - # Expect success and test presence of particle_lists - Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${Hybrid_handler_config}" - if [[ $? -ne 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly failed.' - return 1 - fi - output_files=( Sampler/* ) - if [[ ${#output_files[@]} -ne 3 ]]; then - Print_Error 'Expected ' --emph '3' " output files, but ${#output_files[@]} found." - return 1 - fi - mv 'Sampler' 'Sampler-success' +function Functional_Test__do-Sampler-only() +{ + shopt -s nullglob + local -r hybrid_handler_config='hybrid_config' + local output_files + mkdir -p 'Hydro' + touch 'Hydro/freezeout.dat' + printf ' + Sampler: + Executable: %s/tests/mocks/sampler_black_box.py + ' "${HYBRIDT_repository_top_level_path}" > "${hybrid_handler_config}" + # Expect success and test presence of particle_lists + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + output_files=( Sampler/* ) + if [[ ${#output_files[@]} -ne 4 ]]; then + Print_Error 'Expected ' --emph '4' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'Sampler' 'Sampler-success' - # Expect failure and test terminal output - Print_Info 'Running Hybrid-handler expecting crash in Sampler' - BLACK_BOX_FAIL='true'\ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${Hybrid_handler_config}" - if [[ $? -eq 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeeded with Sampler crashing.' - return 1 - fi - mv 'Sampler' 'Sampler-crash' + # Expect failure and test terminal output + local terminal_output_file + terminal_output_file='Sampler/Terminal_Output.txt' + Print_Info 'Running Hybrid-handler expecting crash in Sampler' + BLACK_BOX_FAIL='true'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with Sampler crashing.' + return 1 + elif [[ ! -f "${terminal_output_file}" ]]; then + Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' + return 1 + fi - # Expect failure - Print_Info 'Running Hybrid-handler expecting invalid config argument' - BLACK_BOX_FAIL='false' - mkdir -p Sampler - local -r invalid_sampler_config="invalid_hadron_sampler" - local terminal_output_file='Sampler/Terminal_Output.txt' - touch "${invalid_sampler_config}" - printf ' - Sampler: - Executable: %s/tests/mocks/sampler_black_box.py - Input_file: %s - ' "${HYBRIDT_repository_top_level_path}"\ - "${invalid_sampler_config}" > "${Hybrid_handler_config}" - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${Hybrid_handler_config}"\ - > "${terminal_output_file}" - if [[ $? -eq 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Sampler.' - return 1 - elif [[ ! -f "${terminal_output_file}" ]]; then - Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' - return 1 - fi - rm "${invalid_sampler_config}" - mv 'Sampler' 'Sampler-invalid-config' + # Check whether terminal output contains expected error message + local error_message + error_message="$(<"${terminal_output_file}")" + if [[ "${error_message}" != 'Sampler black-box crashed!' ]]; then + Print_Error 'Sampler crashed with unexpected terminal output.' + return 1 + fi + mv 'Sampler' 'Sampler-crash' - } \ No newline at end of file + # Expect Hybrid-handler to crash before calling the Sampler because of invalid config file + Print_Info 'Running Hybrid-handler expecting invalid config argument' + BLACK_BOX_FAIL='false' + mkdir -p Sampler + local -r invalid_sampler_config="invalid_hadron_sampler" + touch "${invalid_sampler_config}" + printf ' + Sampler: + Executable: %s/tests/mocks/sampler_black_box.py + Input_file: %s + ' "${HYBRIDT_repository_top_level_path}"\ + "${invalid_sampler_config}" > "${hybrid_handler_config}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Sampler.' + return 1 + fi + mv 'Sampler' 'Sampler-invalid-config' +} \ No newline at end of file diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index af31f57..6a668b1 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -33,8 +33,6 @@ function Unit_Test__Hydro-create-input-file() { touch "${HYBRID_software_base_config_file[Hydro]}" mkdir -p "${HYBRID_software_output_directory[IC]}" - local -r plist_ic="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" - touch "${plist_ic}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then Print_Error 'The config was not properly created in the output folder.' diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index a68b4f0..68a6f2e 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -1,124 +1,137 @@ - #=================================================== - # - # Copyright (c) 2023 - # SMASH Hybrid Team - # - # GNU General Public License (GPLv3 or later) - # - #=================================================== +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== +function Make_Test_Preliminary_Operations__Sampler-create-input-file() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'Sampler_functionality.bash' + 'global_variables.bash' + 'software_input_functionality.bash' + 'sanity_checks.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables + HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Sampler" + HYBRID_software_base_config_file[Sampler]='fake_sampler_config' + HYBRID_given_software_sections=('Sampler') + HYBRID_software_output_directory[Hydro]="${HYBRID_output_directory}/Hydro" + HYBRID_software_executable[Sampler]="$(which echo)" + Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables +} +function Unit_Test__Sampler-create-input-file() +{ + printf '%s\n' \ + 'surface ../Hydro/freezeout.dat' \ + 'spectra_dir .' \ + > "${HYBRID_software_base_config_file[Sampler]}" + mkdir -p "${HYBRID_software_output_directory[Hydro]}" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler + if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then + Print_Error 'The output directory and/or software input file were not properly created.' + return 1 + fi + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation of input with existent config succeeded.' + return 1 + fi - function Make_Test_Preliminary_Operations__Sampler-create-input-file() - { - local file_to_be_sourced list_of_files - list_of_files=( - 'Sampler_functionality.bash' - 'global_variables.bash' - 'software_input_functionality.bash' - 'sanity_checks.bash' - ) - for file_to_be_sourced in "${list_of_files[@]}"; do - source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} - done - Define_Further_Global_Variables - HYBRID_output_directory="./test_dir_Sampler" - HYBRID_software_base_config_file[Sampler]='fake_sampler_config' - HYBRID_given_software_sections=('Sampler') - HYBRID_software_output_directory[Hydro]="${HYBRID_output_directory}/Hydro" - HYBRID_software_executable[Sampler]="${HYBRID_output_directory}/dummy_exec_Sampler.bash" - mkdir -p ${HYBRID_output_directory} - printf '#!/usr/bin/env bash\n\necho "$@"\n' > "${HYBRID_software_executable[Sampler]}" - chmod a+x "${HYBRID_software_executable[Sampler]}" - Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables + # Ensure that paths in Sampler config were replaced by global paths + local surface_path spectra_dir_path + surface_path=$(awk '$1 == "surface" {print $2; exit}' \ + "${HYBRID_software_configuration_file[Sampler]}") + spectra_dir_path=$(awk '$1 == "spectra_dir" {print $2; exit}' \ + "${HYBRID_software_configuration_file[Sampler]}") + if [[ "${surface_path}" != /* || "${spectra_dir_path}" != /* ]]; then + Print_Error 'freezeout and/or output directory path in Sampler config is not a global path.' + return 1 + fi +} - } +function Clean_Tests_Environment_For_Following_Test__Sampler-create-input-file() +{ + rm "${HYBRID_software_base_config_file[Sampler]}" + rm -r "${HYBRID_output_directory}" +} - function Unit_Test__Sampler-create-input-file() - { - touch "${HYBRID_software_base_config_file[Sampler]}" - mkdir -p "${HYBRID_software_output_directory[Hydro]}" - local -r plist_hydro="${HYBRID_software_output_directory[Hydro]}/freezeout.dat" - touch "${plist_hydro}" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler - if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then - Print_Error 'The output directory and/or software input file were not properly created.' - return 1 - fi - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler &> /dev/null - if [[ $? -eq 0 ]]; then - Print_Error 'Preparation of input with existent config succeeded.' - return 1 - fi - rm -r "${HYBRID_output_directory}/"* - } +function Make_Test_Preliminary_Operations__Sampler-check-all-input() +{ + Make_Test_Preliminary_Operations__Sampler-create-input-file +} - function Clean_Tests_Environment_For_Following_Test__Sampler-create-input-file() - { - rm "${HYBRID_software_base_config_file[Sampler]}" - rm -r "${HYBRID_output_directory}" - } +function Unit_Test__Sampler-check-all-input() +{ + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing output directory succeeded, although failure was expected.' + return 1 + fi + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing config file succeeded, although failure was expected.' + return 1 + fi + touch "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring correctness of empty sampler input file succeeded, although failure was expected.' + return 1 + fi + printf 'surface not-existing-file\n' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing freezeout surface file succeeded.' + return 1 + fi + printf 'surface %s\n' "$(which ls)" > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + if [[ $? -ne 0 ]]; then + Print_Error 'Ensuring existence of all input files unexpectedly failed.' + return 1 + fi +} - function Make_Test_Preliminary_Operations__Sampler-check-all-input() - { - Make_Test_Preliminary_Operations__Sampler-create-input-file - } +function Clean_Tests_Environment_For_Following_Test__Sampler-check-all-input() +{ + rm -r "${HYBRID_output_directory}" +} - function Unit_Test__Sampler-check-all-input() - { - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null - if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of not-existing output directory succeeded, although failure was expected.' - return 1 - fi - mkdir -p "${HYBRID_software_output_directory[Sampler]}" \ - "${HYBRID_software_output_directory[Hydro]}" - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null - if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of not-existing config file succeeded, although failure was expected.' - return 1 - fi - touch "${HYBRID_software_configuration_file[Sampler]}" \ - "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null - if [[ $? -ne 0 ]]; then - Print_Error 'Ensuring existence of existing folder/file failed.' - return 1 - fi - } +function Make_Test_Preliminary_Operations__Sampler-test-run-software() +{ + Make_Test_Preliminary_Operations__Sampler-create-input-file +} - function Clean_Tests_Environment_For_Following_Test__Sampler-check-all-input() - { - rm -r "${HYBRID_output_directory}" - } +function Unit_Test__Sampler-test-run-software() +{ + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt"\ + sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" + local terminal_output_result correct_result + Call_Codebase_Function_In_Subshell Run_Software_Sampler + if [[ ! -f "${sampler_terminal_output}" ]]; then + Print_Error 'The terminal output was not created.' + return 1 + fi + terminal_output_result=$(< "${sampler_terminal_output}") + correct_result="events 1 ${HYBRID_software_configuration_file[Sampler]}" + if [[ "${terminal_output_result}" != "${correct_result}" ]]; then + Print_Error 'The terminal output has not the expected content.' + return 1 + fi +} - function Make_Test_Preliminary_Operations__Sampler-test-run-software() - { - Make_Test_Preliminary_Operations__Sampler-create-input-file - } - - function Unit_Test__Sampler-test-run-software() - { - mkdir -p "${HYBRID_software_output_directory[Sampler]}" - local -r Sampler_terminal_output="${HYBRID_output_directory}/Sampler/Terminal_Output.txt"\ - Sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}"\ - Hydro_output_file_path="${HYBRID_software_output_directory[Hydro]}/freezeout.dat" - local terminal_output_result correct_result - Call_Codebase_Function_In_Subshell Run_Software_Sampler - if [[ ! -f "${Sampler_terminal_output}" ]]; then - Print_Error 'The terminal output was not created.' - return 1 - fi - terminal_output_result=$(< "${Sampler_terminal_output}") - correct_result="events 1 ${HYBRID_software_configuration_file[Sampler]}" - if [[ "${terminal_output_result}" != "${correct_result}" ]]; then - Print_Error 'The terminal output has not the expected content.' - return 1 - fi - - } - - function Clean_Tests_Environment_For_Following_Test__Sampler-test-run-software() - { - Clean_Tests_Environment_For_Following_Test__Sampler-check-all-input - } \ No newline at end of file +function Clean_Tests_Environment_For_Following_Test__Sampler-test-run-software() +{ + Clean_Tests_Environment_For_Following_Test__Sampler-check-all-input +} From e4a7dc8871047d1c59af178a189093bdd65f4d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Mon, 13 Nov 2023 19:01:28 +0100 Subject: [PATCH 219/549] Add validation of sampler config and corresponding unit test --- bash/Sampler_functionality.bash | 130 +++++++++++++++++--- tests/unit_tests_Sampler_functionality.bash | 123 ++++++++++++++++-- 2 files changed, 228 insertions(+), 25 deletions(-) diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 3fcc8d3..db2b752 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -27,6 +27,11 @@ function Prepare_Software_Input_File_Sampler() "${HYBRID_software_new_input_keys[Sampler]}" fi + if ! __static__Validate_Sampler_Config_File; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + "Sampler configuration file invalid." + fi + # Replace potentially relative paths in Sampler config with absolute paths local freezeout_path output_directory freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') @@ -58,12 +63,11 @@ function Ensure_All_Needed_Input_Exists_Sampler() 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}"\ ' was not found.' fi - local freezeout_path - freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') - echo "DGFSHJ " "${freezeout_path}" - if [[ ! -f "${freezeout_path}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'Freezeout hypersurface ' --emph "${freezeout_path}" ' does not exist.' + # This is already done preparing the input file, but it's logically belonging here, too. + # Therefore, we repeat the validation, as its cost is substantially negligible. + if ! __static__Validate_Sampler_Config_File; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + "Sampler configuration file validation failed when ensuring existence of all input." fi } @@ -79,18 +83,114 @@ function Run_Software_Sampler() function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() { local field value - field=$1 + field="$1" + # We assume here that the configuration file is fine as it was validated before value=$(awk -v name="${field}" '$1 == name {print $2; exit}'\ "${HYBRID_software_configuration_file[Sampler]}") - ( - cd "${HYBRID_software_output_directory[Sampler]}" - # If realpath succeeds, it prints the path that is the result of the function - if ! realpath "${value}" 2> /dev/null; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'Unable to transform relatrive path "' --emph "${value}"\ - '" into global one.' - fi + cd "${HYBRID_software_output_directory[Sampler]}" + # If realpath succeeds, it prints the path that is the result of the function + if ! realpath "${value}" 2> /dev/null; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Unable to transform relatrive path "' --emph "${value}"\ + '" into global one.' + fi + cd - > /dev/null +} + +function __static__Validate_Sampler_Config_File() { + local -r config_file="${HYBRID_software_configuration_file[Sampler]}" + + # Remove empty lines from configuration file + if ! sed -i '/^[[:space:]]*$/d' "${config_file}"; then + Print_Internal_And_Exit "Empty lines removal in ${FUNCNAME} failed." + fi + + # Check if the config file is empty + if [[ ! -s "${config_file}" ]]; then + Print_Error 'Sampler configuration file is empty.' + return 1 + fi + + # Check for two columns in each line + if [[ $(awk 'NF!=2 {exit 1}' "${config_file}") ]]; then + Print_Error 'Each line should consist of two columns.' + return 1 + fi + + # Check that no key is repeated + if [[ $(awk '{print $1}' "${config_file}" | sort | uniq -d) != '' ]]; then + Print_Error 'Found repeated key in sampler configuration file.' + return 1 + fi + + # Define allowed keys as an array + local -r allowed_keys=( + 'surface' + 'spectra_dir' + 'number_of_events' + 'rescatter' + 'weakContribution' + 'shear' + 'ecrit' + 'Nbins' + 'q_max' ) + + local to_be_found_keys=2 + while read key value; do + if ! Element_In_Array_Equals_To "${key}" "${allowed_keys[@]}"; then + Print_Error 'Invalid key ' --emph "${key}" ' found in sampler configuration file.' + return 1 + fi + case "${key}" in + surface ) + cd "${HYBRID_software_output_directory[Sampler]}" + if [[ ! -f "${value}" ]]; then + cd - > /dev/null + Print_Error 'Freeze-out surface file ' --emph "${value}" ' not found!' + return 1 + fi + (( to_be_found_keys-- )) + ;; + spectra_dir ) + cd "${HYBRID_software_output_directory[Sampler]}" + if [[ ! -d "${value}" ]]; then + cd - > /dev/null + Print_Error 'Sampler output folder ' --emph "${value}" ' not found!' + return 1 + fi + (( to_be_found_keys-- )) + ;; + rescatter | weakContribution | shear ) + if [[ ! "${value}" =~ ^[01]$ ]]; then + Print_Error 'Key ' --emph "${key}" ' must be either '\ + --emph '0' ' or ' --emph '1' '.' + return 1 + fi + ;; + number_of_events | Nbins ) + if [[ ! "${value}" =~ ^[1-9][0-9]*$ ]]; then + Print_Error 'Found not-integer value ' --emph "${value}"\ + ' for ' --emph "${key}" ' key.' + return 1 + fi + ;; + * ) + if [[ ! "${value}" =~ ^[+-]?[0-9]+(\.[0-9]*)?$ ]]; then + Print_Error 'Found invalid value ' --emph "${value}"\ + ' for ' --emph "${key}" ' key.' + return 1 + fi + ;; + esac + done < "${config_file}" + + if [[ ${to_be_found_keys} -gt 0 ]]; then + Print_Error 'Either ' --emph 'surface' ' or ' --emph 'spectra_dir'\ + ' key is missing in sampler configuration file.' + return 1 + fi } + Make_Functions_Defined_In_This_File_Readonly diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 68a6f2e..53e4e25 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -35,17 +35,15 @@ function Unit_Test__Sampler-create-input-file() 'spectra_dir .' \ > "${HYBRID_software_base_config_file[Sampler]}" mkdir -p "${HYBRID_software_output_directory[Hydro]}" + touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler - if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then - Print_Error 'The output directory and/or software input file were not properly created.' + if [[ $? -ne 0 ]]; then + Print_Error 'Preparation of input unexpectedly failed.' return 1 - fi - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler &> /dev/null - if [[ $? -eq 0 ]]; then - Print_Error 'Preparation of input with existent config succeeded.' + elif [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then + Print_Error 'The output directory and/or software input file were not properly created.' return 1 fi - # Ensure that paths in Sampler config were replaced by global paths local surface_path spectra_dir_path surface_path=$(awk '$1 == "surface" {print $2; exit}' \ @@ -53,7 +51,13 @@ function Unit_Test__Sampler-create-input-file() spectra_dir_path=$(awk '$1 == "spectra_dir" {print $2; exit}' \ "${HYBRID_software_configuration_file[Sampler]}") if [[ "${surface_path}" != /* || "${spectra_dir_path}" != /* ]]; then - Print_Error 'freezeout and/or output directory path in Sampler config is not a global path.' + Print_Error 'Freezeout and/or output directory path in Sampler config is not a global path.' + return 1 + fi + + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation of input with existent config succeeded.' return 1 fi } @@ -94,8 +98,10 @@ function Unit_Test__Sampler-check-all-input() Print_Error 'Ensuring existence of not-existing freezeout surface file succeeded.' return 1 fi - printf 'surface %s\n' "$(which ls)" > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null + printf '%s\n'\ + "surface $(which ls)"\ + "spectra_dir ${HOME}" > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of all input files unexpectedly failed.' return 1 @@ -107,6 +113,103 @@ function Clean_Tests_Environment_For_Following_Test__Sampler-check-all-input() rm -r "${HYBRID_output_directory}" } +function Make_Test_Preliminary_Operations__Sampler-validate-config-file() +{ + Make_Test_Preliminary_Operations__Sampler-create-input-file +} + +function Unit_Test__Sampler-validate-config-file() +{ + local -r config_file='sampler_config.txt' + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + + # Empty config file + touch "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed though config file is empty.' + return 1 + fi + + # Config file with invalid key + printf '%s\n' 'invalidKey value' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed though config file has invalid key.' + return 1 + fi + + # Config file missing required key 'surface' + printf '%s\n' 'spectra_dir .' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed though config file does not contain "surface" key.' + return 1 + fi + + # Config file missing required key 'spectra_dir' + mkdir -p "${HYBRID_software_output_directory[Hydro]}" + touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + printf '%s\n' 'surface ../Hydro/freezeout.dat' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed though config file does not contain "surface" key.' + return 1 + fi + + # Config file missing required keys surface and spectra_dir + printf '%s\n' 'numberOfEvents 100' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed though config file does not contain "surface" and "spectra_dir" keys.' + return 1 + fi + + # Config file with incorrect surface + printf '%s\n' 'surface not-a-file' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed though surface key has no string as value.' + return 1 + fi + + # Config file with incorrect spectra_dir + printf '%s\n' "spectra_dir ${config_file}" > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed though spectra_dir key has no directory as value.' + return 1 + fi + + # Config file with incorrect value type for other keys + local wrong_key_value + for wrong_key_value in \ + 'number_of_events 3.14'\ + 'rescatter 3..14'\ + 'weakContribution #_#'\ + 'shear true'\ + 'ecrit +-1'\ + 'Nbins -100'\ + 'q_max 1.6'; do + printf '%s\n' "${wrong_key_value}" > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File #&> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error "Unexpected success: Key '${wrong_key_value}' accepted." + return 1 + fi + done + + # Valid config file + printf '%s\n'\ + 'surface ../Hydro/freezeout.dat'\ + 'spectra_dir .' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File + if [[ $? -ne 0 ]]; then + Print_Error 'Sampler config unexpectedly detected as incorrect.' + return 1 + fi +} + function Make_Test_Preliminary_Operations__Sampler-test-run-software() { Make_Test_Preliminary_Operations__Sampler-create-input-file From a4990c43699b4c53f6c35c2f255cf445140c7653 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 14 Nov 2023 10:20:24 +0100 Subject: [PATCH 220/549] Rename function to make it consistent in style --- tests/tests_runner | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/tests_runner b/tests/tests_runner index 7b59f46..8f07c83 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -17,7 +17,7 @@ # run the test, plus optional preliminary and subsequent operations to the test. # # The functions that shall be provided by "external" code can be easily located -# in this file, as they are called by the 'Call_through_interface' function. +# in this file, as they are called by the 'Call_Through_Interface' function. # The driver has a first command line positional option that selects which suite # is considered. The parser is then sourcing the specific code and then the code # here is ready to be executed. @@ -32,7 +32,7 @@ function Main() Print_Helper_And_Exit_If_Requested "$@" Parse_Tests_Suite_Parameter_And_Source_Specific_Code "${1-}" Check_System_Requirements - Call_through_interface 'Define_Available_Tests' + Call_Through_Interface 'Define_Available_Tests' Parse_Tests_Command_Line_Options "${@:2}" Prepare_Test_Environment Run_Tests @@ -41,7 +41,7 @@ function Main() Exit_With_Tests_Outcome_Dependent_Exit_Code } -function Call_through_interface() +function Call_Through_Interface() { Call_Function_If_Existing_Or_Exit $1 "${@:2}" } @@ -129,10 +129,10 @@ function Run_Tests() # Make sure each test is run from the same folder (no matter from where tests are run) Announce_Running_Test "${test_name}" cd "${HYBRIDT_folder_to_run_tests}" || exit ${HYBRID_fatal_builtin} - Call_through_interface 'Make_Test_Preliminary_Operations' "${test_name}" - Call_through_interface 'Run_Test' "${test_name}" + Call_Through_Interface 'Make_Test_Preliminary_Operations' "${test_name}" + Call_Through_Interface 'Run_Test' "${test_name}" local test_outcome=$? - Call_through_interface 'Clean_Tests_Environment_For_Following_Test' "${test_name}" + Call_Through_Interface 'Clean_Tests_Environment_For_Following_Test' "${test_name}" exit ${test_outcome} ) Inspect_Test_Outcome $? "${test_name}" From 397de7770ce95ea0e94c317ef1b55315e4ffaad4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 14 Nov 2023 10:21:36 +0100 Subject: [PATCH 221/549] Minor refinements to sampler functionality and its tests Mainly comments and renamings were done. A couple of missing unit tests about the validation of the sampler input file were added. --- bash/Sampler_functionality.bash | 39 +++----- tests/functional_tests_Sampler_only.bash | 13 +-- tests/unit_tests_Hydro_functionality.bash | 4 +- tests/unit_tests_Sampler_functionality.bash | 102 ++++++++++---------- 4 files changed, 71 insertions(+), 87 deletions(-) diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index db2b752..968b03f 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -20,18 +20,16 @@ function Prepare_Software_Input_File_Sampler() ' was not found.' fi cp "${HYBRID_software_base_config_file[Sampler]}"\ - "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} + "${HYBRID_software_configuration_file[Sampler]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Sampler]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'TXT' "${HYBRID_software_configuration_file[Sampler]}"\ "${HYBRID_software_new_input_keys[Sampler]}" fi - - if ! __static__Validate_Sampler_Config_File; then + if ! __static__Is_Sampler_Config_Valid; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ "Sampler configuration file invalid." fi - # Replace potentially relative paths in Sampler config with absolute paths local freezeout_path output_directory freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') @@ -41,10 +39,9 @@ function Prepare_Software_Input_File_Sampler() "$(printf "%s: %s\n"\ 'surface' "${freezeout_path}"\ 'spectra_dir' "${output_directory}")" - - # The following symbolic link is not needed by the sampler, as it refers to its input file. - # However, we want to have all input in the output folder for future easier reproducibility - # (and we do so for all blocks in the workflow). + # The following symbolic link is not needed by the sampler, as the sampler only refers to information + # specified in its input file. However, we want to have all input for a software in the output folder + # for future easier reproducibility (and we do so for all software handled in the codebase). if [[ "$( dirname "${freezeout_path}" )" != "${HYBRID_software_output_directory[Sampler]}" ]]; then ln -s "${freezeout_path}"\ "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" @@ -65,7 +62,7 @@ function Ensure_All_Needed_Input_Exists_Sampler() fi # This is already done preparing the input file, but it's logically belonging here, too. # Therefore, we repeat the validation, as its cost is substantially negligible. - if ! __static__Validate_Sampler_Config_File; then + if ! __static__Is_Sampler_Config_Valid; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ "Sampler configuration file validation failed when ensuring existence of all input." fi @@ -80,6 +77,7 @@ function Run_Software_Sampler() "${sampler_config_file_path}" >> "${sampler_terminal_output}" } + function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() { local field value @@ -91,38 +89,32 @@ function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() # If realpath succeeds, it prints the path that is the result of the function if ! realpath "${value}" 2> /dev/null; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'Unable to transform relatrive path "' --emph "${value}"\ - '" into global one.' + 'Unable to transform relative path ' --emph "${value}" ' into global one.' fi cd - > /dev/null } -function __static__Validate_Sampler_Config_File() { +function __static__Is_Sampler_Config_Valid() { local -r config_file="${HYBRID_software_configuration_file[Sampler]}" - # Remove empty lines from configuration file if ! sed -i '/^[[:space:]]*$/d' "${config_file}"; then Print_Internal_And_Exit "Empty lines removal in ${FUNCNAME} failed." fi - # Check if the config file is empty if [[ ! -s "${config_file}" ]]; then Print_Error 'Sampler configuration file is empty.' return 1 fi - # Check for two columns in each line if [[ $(awk 'NF!=2 {exit 1}' "${config_file}") ]]; then - Print_Error 'Each line should consist of two columns.' + Print_Error 'Each line should consist of two columns.' return 1 fi - # Check that no key is repeated if [[ $(awk '{print $1}' "${config_file}" | sort | uniq -d) != '' ]]; then Print_Error 'Found repeated key in sampler configuration file.' return 1 fi - # Define allowed keys as an array local -r allowed_keys=( 'surface' @@ -135,8 +127,7 @@ function __static__Validate_Sampler_Config_File() { 'Nbins' 'q_max' ) - - local to_be_found_keys=2 + local keys_to_be_found=2 while read key value; do if ! Element_In_Array_Equals_To "${key}" "${allowed_keys[@]}"; then Print_Error 'Invalid key ' --emph "${key}" ' found in sampler configuration file.' @@ -150,7 +141,7 @@ function __static__Validate_Sampler_Config_File() { Print_Error 'Freeze-out surface file ' --emph "${value}" ' not found!' return 1 fi - (( to_be_found_keys-- )) + (( keys_to_be_found-- )) ;; spectra_dir ) cd "${HYBRID_software_output_directory[Sampler]}" @@ -159,7 +150,7 @@ function __static__Validate_Sampler_Config_File() { Print_Error 'Sampler output folder ' --emph "${value}" ' not found!' return 1 fi - (( to_be_found_keys-- )) + (( keys_to_be_found-- )) ;; rescatter | weakContribution | shear ) if [[ ! "${value}" =~ ^[01]$ ]]; then @@ -184,8 +175,8 @@ function __static__Validate_Sampler_Config_File() { ;; esac done < "${config_file}" - - if [[ ${to_be_found_keys} -gt 0 ]]; then + # Check that all required keys were found + if [[ ${keys_to_be_found} -gt 0 ]]; then Print_Error 'Either ' --emph 'surface' ' or ' --emph 'spectra_dir'\ ' key is missing in sampler configuration file.' return 1 diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 6e7b87c..0aeea94 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -18,7 +18,7 @@ function Functional_Test__do-Sampler-only() Sampler: Executable: %s/tests/mocks/sampler_black_box.py ' "${HYBRIDT_repository_top_level_path}" > "${hybrid_handler_config}" - # Expect success and test presence of particle_lists + # Expect success and test presence of output files Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" if [[ $? -ne 0 ]]; then @@ -31,9 +31,8 @@ function Functional_Test__do-Sampler-only() return 1 fi mv 'Sampler' 'Sampler-success' - # Expect failure and test terminal output - local terminal_output_file + local terminal_output_file error_message terminal_output_file='Sampler/Terminal_Output.txt' Print_Info 'Running Hybrid-handler expecting crash in Sampler' BLACK_BOX_FAIL='true'\ @@ -45,16 +44,12 @@ function Functional_Test__do-Sampler-only() Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' return 1 fi - - # Check whether terminal output contains expected error message - local error_message error_message="$(<"${terminal_output_file}")" if [[ "${error_message}" != 'Sampler black-box crashed!' ]]; then Print_Error 'Sampler crashed with unexpected terminal output.' return 1 fi mv 'Sampler' 'Sampler-crash' - # Expect Hybrid-handler to crash before calling the Sampler because of invalid config file Print_Info 'Running Hybrid-handler expecting invalid config argument' BLACK_BOX_FAIL='false' @@ -67,10 +62,10 @@ function Functional_Test__do-Sampler-only() Input_file: %s ' "${HYBRIDT_repository_top_level_path}"\ "${invalid_sampler_config}" > "${hybrid_handler_config}" - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Sampler.' return 1 fi mv 'Sampler' 'Sampler-invalid-config' -} \ No newline at end of file +} diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 6a668b1..7476be3 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -26,13 +26,11 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" HYBRID_software_executable[Hydro]="$(which echo)" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables - } function Unit_Test__Hydro-create-input-file() { touch "${HYBRID_software_base_config_file[Hydro]}" - mkdir -p "${HYBRID_software_output_directory[IC]}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then Print_Error 'The config was not properly created in the output folder.' @@ -106,7 +104,7 @@ function Unit_Test__Hydro-test-run-software() Print_Error 'The terminal output has not the expected content.' return 1 fi - + } function Clean_Tests_Environment_For_Following_Test__Hydro-test-run-software() diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 53e4e25..d7123c1 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -21,15 +21,14 @@ function Make_Test_Preliminary_Operations__Sampler-create-input-file() done Define_Further_Global_Variables HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Sampler" - HYBRID_software_base_config_file[Sampler]='fake_sampler_config' HYBRID_given_software_sections=('Sampler') - HYBRID_software_output_directory[Hydro]="${HYBRID_output_directory}/Hydro" HYBRID_software_executable[Sampler]="$(which echo)" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } function Unit_Test__Sampler-create-input-file() { + HYBRID_software_base_config_file[Sampler]='fake_sampler_config' printf '%s\n' \ 'surface ../Hydro/freezeout.dat' \ 'spectra_dir .' \ @@ -54,7 +53,7 @@ function Unit_Test__Sampler-create-input-file() Print_Error 'Freezeout and/or output directory path in Sampler config is not a global path.' return 1 fi - + # Creating again the input should fail Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Preparation of input with existent config succeeded.' @@ -77,19 +76,19 @@ function Unit_Test__Sampler-check-all-input() { Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of not-existing output directory succeeded, although failure was expected.' + Print_Error 'Ensuring existence of not-existing output directory succeeded.' return 1 fi mkdir -p "${HYBRID_software_output_directory[Sampler]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of not-existing config file succeeded, although failure was expected.' + Print_Error 'Ensuring existence of not-existing config file succeeded.' return 1 fi touch "${HYBRID_software_configuration_file[Sampler]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring correctness of empty sampler input file succeeded, although failure was expected.' + Print_Error 'Ensuring correctness of empty sampler input file succeeded.' return 1 fi printf 'surface not-existing-file\n' > "${HYBRID_software_configuration_file[Sampler]}" @@ -118,98 +117,99 @@ function Make_Test_Preliminary_Operations__Sampler-validate-config-file() Make_Test_Preliminary_Operations__Sampler-create-input-file } -function Unit_Test__Sampler-validate-config-file() +function Unit_Test__Sampler-validate-config-file() { - local -r config_file='sampler_config.txt' - mkdir -p "${HYBRID_software_output_directory[Sampler]}" - + mkdir -p "${HYBRID_software_output_directory[Sampler]}" + cd "${HYBRID_software_output_directory[Sampler]}" # Empty config file touch "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Config validation passed though config file is empty.' + Print_Error 'Config validation passed although config file is empty.' + return 1 + fi + # Too many columns in config file + printf '%s\n' 'surface whatever wrong' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed although config file has wrong number of columns.' + return 1 + fi + # Repeated key in config file + printf '%s\n' 'spectra_dir ~' 'spectra_dir ~' > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Config validation passed although config file has repeated lines.' return 1 fi - # Config file with invalid key printf '%s\n' 'invalidKey value' > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Config validation passed though config file has invalid key.' + Print_Error 'Config validation passed although config file has invalid key.' return 1 fi - # Config file missing required key 'surface' printf '%s\n' 'spectra_dir .' > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Config validation passed though config file does not contain "surface" key.' + Print_Error 'Config validation passed although config file does not contain "surface" key.' return 1 fi - # Config file missing required key 'spectra_dir' - mkdir -p "${HYBRID_software_output_directory[Hydro]}" - touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" - printf '%s\n' 'surface ../Hydro/freezeout.dat' > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + printf '%s\n' "surface $(which ls)" > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Config validation passed though config file does not contain "surface" key.' + Print_Error 'Config validation passed although config file does not contain "spectra_dir" key.' return 1 fi - - # Config file missing required keys surface and spectra_dir - printf '%s\n' 'numberOfEvents 100' > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null - if [[ $? -eq 0 ]]; then - Print_Error 'Config validation passed though config file does not contain "surface" and "spectra_dir" keys.' - return 1 - fi - # Config file with incorrect surface printf '%s\n' 'surface not-a-file' > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Config validation passed though surface key has no string as value.' + Print_Error 'Config validation passed although surface key has no string as value.' return 1 fi - # Config file with incorrect spectra_dir - printf '%s\n' "spectra_dir ${config_file}" > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File &> /dev/null + printf '%s\n' "spectra_dir $(which ls)" > "${HYBRID_software_configuration_file[Sampler]}" + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Config validation passed though spectra_dir key has no directory as value.' + Print_Error 'Config validation passed although spectra_dir key has no directory as value.' return 1 fi - # Config file with incorrect value type for other keys local wrong_key_value for wrong_key_value in \ 'number_of_events 3.14'\ 'rescatter 3..14'\ - 'weakContribution #_#'\ + 'weakContribution false'\ 'shear true'\ 'ecrit +-1'\ 'Nbins -100'\ 'q_max 1.6'; do printf '%s\n' "${wrong_key_value}" > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File #&> /dev/null + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null if [[ $? -eq 0 ]]; then Print_Error "Unexpected success: Key '${wrong_key_value}' accepted." return 1 fi done - - # Valid config file - printf '%s\n'\ - 'surface ../Hydro/freezeout.dat'\ - 'spectra_dir .' > "${HYBRID_software_configuration_file[Sampler]}" - Call_Codebase_Function_In_Subshell __static__Validate_Sampler_Config_File + # Validate base configuration file we ship in the codebase + cp "${HYBRID_software_base_config_file[Sampler]}" "${HYBRID_software_configuration_file[Sampler]}" + mkdir -p "${HYBRID_software_output_directory[Hydro]}" + touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid if [[ $? -ne 0 ]]; then - Print_Error 'Sampler config unexpectedly detected as incorrect.' + Print_Error 'Shipped sampler configuration unexpectedly detected as incorrect.' return 1 fi } +function Clean_Tests_Environment_For_Following_Test__Sampler-validate-config-file() +{ + rm -r "${HYBRID_output_directory}" +} + function Make_Test_Preliminary_Operations__Sampler-test-run-software() { Make_Test_Preliminary_Operations__Sampler-create-input-file @@ -227,7 +227,7 @@ function Unit_Test__Sampler-test-run-software() return 1 fi terminal_output_result=$(< "${sampler_terminal_output}") - correct_result="events 1 ${HYBRID_software_configuration_file[Sampler]}" + correct_result="events 1 ${HYBRID_software_configuration_file[Sampler]}" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then Print_Error 'The terminal output has not the expected content.' return 1 @@ -237,4 +237,4 @@ function Unit_Test__Sampler-test-run-software() function Clean_Tests_Environment_For_Following_Test__Sampler-test-run-software() { Clean_Tests_Environment_For_Following_Test__Sampler-check-all-input -} +} From 99db4f34e787f82f6a8d641614c59f5a906c556f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 14 Nov 2023 16:35:35 +0100 Subject: [PATCH 222/549] Fix expansion quoting --- bash/utility_functions.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index b69c549..cc5de7d 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -141,7 +141,7 @@ function Remove_Comments_In_File() local filename comment_character filename=$1 comment_character=${2:-#} - if [[ ! -f ${filename} ]]; then + if [[ ! -f "${filename}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'File ' --emph "${filename}" ' not found.' elif [[ ${#comment_character} -ne 1 ]]; then From 625967db10a5410782e31e35920d27b6a7473c02 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 14 Nov 2023 16:35:54 +0100 Subject: [PATCH 223/549] Preserve user comments in handler configuration file --- bash/configuration_parser.bash | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 610468c..e69dc78 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -12,7 +12,10 @@ # and deleting the key from the variable content. function Validate_And_Parse_Configuration_File() { - Remove_Comments_In_File "${HYBRID_configuration_file}" # This checks for existence, too + if [[ ! -f "${HYBRID_configuration_file}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'Handler configuration file ' --emph "${filename}" ' not found.' + fi __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File __static__Abort_If_Sections_Are_Violating_Any_Requirement __static__Abort_If_Invalid_Keys_Were_Used From 2a364331d8588d765c20295c27a2a3ffe3bae527 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 14 Nov 2023 16:36:23 +0100 Subject: [PATCH 224/549] Make replacement of keys in text file much stricter In particular, it is now checked that the user does not put a colon after keys and endlines in the base input file are possible (although stripped in the processing). --- bash/software_input_functionality.bash | 18 ++++++-- ...it_tests_software_input_functionality.bash | 45 ++++++++++++++----- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 1fe54c4..7942197 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -56,18 +56,30 @@ function __static__Replace_Keys_Into_YAML_File() fi } +# NOTE: In this function we might try to keep the empty lines, but the YAML library will +# strip them anyway and therefore it is convenient to strip them immediately. function __static__Replace_Keys_Into_Txt_File() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty base_input_file keys_to_be_replaced + # Strip lines with space only both in file and in the new keys list + sed -i '/^[[:space:]]*$/d' "${base_input_file}" + keys_to_be_replaced="$(sed '/^[[:space:]]*$/d' <(printf "${keys_to_be_replaced}\n"))" # Impose that both file and new keys have two entries per line - if ! awk 'NF != 2 { exit 1 }' "${base_input_file}"; then + if ! awk 'NF!=2 {exit 1}' "${base_input_file}"; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ 'File ' --emph "${base_input_file}" ' does not seem to contain two columns per line only!' - elif ! awk 'NF != 2 { exit 1 }' <(printf "${keys_to_be_replaced}\n"); then + elif ! awk 'NF!=2 {exit 1}' <(printf "${keys_to_be_replaced}\n"); then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ 'Keys to be replaced do not seem to contain valid key-value syntax.' fi - local number_of_fields_per_line=( $(awk 'BEGIN{FS=":"}{print NF}' <(printf "${keys_to_be_replaced}\n") | sort -u) ) + if ! awk '$1 ~ /:$/ {exit 1}' "${base_input_file}"; then + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + 'File ' --emph "${base_input_file}" ' should not have a colon at the end of keys!' + fi + local number_of_fields_per_line + number_of_fields_per_line=( + $(awk 'BEGIN{FS=":"}{print NF}' <(printf "${keys_to_be_replaced}\n") | sort -u) + ) if [[ ${#number_of_fields_per_line[@]} -gt 1 ]]; then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ 'Keys to be replaced do not have consistent colon-terminated keys syntax.' diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 6b2f464..f23353c 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -19,14 +19,14 @@ function Unit_Test__replace-in-software-input-YAML() # by __static__Replace_Keys_Into_YAML_File function local base_input_file keys_to_be_replaced expected_result base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml - # Test case 1: + #--------------------------------------------------------------------------- printf 'Scalar\nKey: Value\n' > "${base_input_file}" Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_YAML_File &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'YAML replacement in invalid file succeeded.' return 1 fi - # Test case 2: + #--------------------------------------------------------------------------- printf 'Key: Value\n' > "${base_input_file}" keys_to_be_replaced=$'Invalid\nyaml: syntax' Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_YAML_File &> /dev/null @@ -34,7 +34,7 @@ function Unit_Test__replace-in-software-input-YAML() Print_Error 'Invalid YAML replacement in valid file succeeded.' return 1 fi - # Test case 3: + #--------------------------------------------------------------------------- printf 'Key: Value\n' > "${base_input_file}" keys_to_be_replaced='New_key: value' Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_YAML_File &> /dev/null @@ -42,7 +42,7 @@ function Unit_Test__replace-in-software-input-YAML() Print_Error 'Valid YAML replacement but with non existent key in valid file succeeded.' return 1 fi - # Test case 4: + #--------------------------------------------------------------------------- printf 'Array: - 1 - 2 @@ -90,14 +90,22 @@ function Unit_Test__replace-in-software-input-TXT() # by __static__Replace_Keys_Into_Txt_File function local base_input_file keys_to_be_replaced expected_result base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml - # Test case 1: + #--------------------------------------------------------------------------- printf 'Key Value Extra-field\n' > "${base_input_file}" Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'TXT replacement in invalid file succeeded' + Print_Error 'TXT replacement in invalid (too many columns) file succeeded.' return 1 fi - # Test case 2: + #--------------------------------------------------------------------------- + printf 'Key: Value\n' > "${base_input_file}" + keys_to_be_replaced='Key: Another_value' + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'TXT replacement in invalid file (colon at end of key) succeeded.' + return 1 + fi + #--------------------------------------------------------------------------- printf 'Key: Value\n' > "${base_input_file}" keys_to_be_replaced='Invalid txt syntax' Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null @@ -105,7 +113,7 @@ function Unit_Test__replace-in-software-input-TXT() Print_Error 'Invalid TXT replacement in valid file succeeded' return 1 fi - # Test case 3: + #--------------------------------------------------------------------------- printf 'Key Value\n' > "${base_input_file}" keys_to_be_replaced='New_key value' Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null @@ -113,7 +121,7 @@ function Unit_Test__replace-in-software-input-TXT() Print_Error 'Valid TXT replacement but with non existent key in valid file succeeded' return 1 fi - # Test case 5: + #--------------------------------------------------------------------------- printf 'Key Value\n' > "${base_input_file}" keys_to_be_replaced=$'New_key: value\nOther_key value' Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null @@ -121,7 +129,7 @@ function Unit_Test__replace-in-software-input-TXT() Print_Error 'Invalid TXT replacement with inconsistent colons succeeded' return 1 fi - # Test case 6: + #--------------------------------------------------------------------------- printf 'Key Value\n' > "${base_input_file}" keys_to_be_replaced=$'New_key:: value\nOther_key:: value' Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null @@ -129,7 +137,7 @@ function Unit_Test__replace-in-software-input-TXT() Print_Error 'Invalid TXT replacement with too many colons succeeded' return 1 fi - # Test case 7: + #--------------------------------------------------------------------------- printf 'a 0.123\nb 0.456\nc 0.789\n' > "${base_input_file}" keys_to_be_replaced=$'a 42\nc 77' printf -v expected_result "%-20s %s\n" 'a' '42' 'b' '0.456' 'c' '77' @@ -142,7 +150,20 @@ function Unit_Test__replace-in-software-input-TXT() '-------------------' return 1 fi - # Test case 8: + #--------------------------------------------------------------------------- + printf 'a 0.123\n \nb 0.456\n\nc 0.789\n' > "${base_input_file}" + keys_to_be_replaced=$'a 42\n \n\nc 77' + printf -v expected_result "%-20s %s\n" 'a' '42' 'b' '0.456' 'c' '77' + expected_result=${expected_result%?} # Get rid of trailing endline + Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File + if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then + Print_Error "YAML replacement failed!"\ + "---- OBTAINED: ----\n$(< "${base_input_file}")"\ + "---- EXPECTED: ----\n${expected_result}"\ + '-------------------' + return 1 + fi + #--------------------------------------------------------------------------- printf 'a 0.123\nb 0.456\nvery_very_very_long_key 0.789\n' > "${base_input_file}" keys_to_be_replaced=$'a: 42\nvery_very_very_long_key: 77' printf -v expected_result "%-20s %s\n" 'a' '42' 'b' '0.456' 'very_very_very_long_key' '77' From eb50b33f0c6c8d70b399119abea5682825fb85cc Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 15 Nov 2023 12:08:41 +0100 Subject: [PATCH 225/549] Treat auxiliary input file to Hydro as done in other blocks In particular, we always want a symbolic link in the software output folder to the auxiliary input file. This enables for easier understanding of what was used for the run and hence makes future reproducibility easier. If the user for some reason puts a file with the right name in the output folder, this is used and no symlink is created. --- bash/Hydro_functionality.bash | 12 ++++++-- tests/functional_tests_Hydro_only.bash | 4 +-- tests/unit_tests_Hydro_functionality.bash | 35 +++++++++++++++++++---- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 9b19f4b..e9a4038 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -23,6 +23,12 @@ function Prepare_Software_Input_File_Hydro() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'TXT' "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_new_input_keys[Hydro]}" fi + # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). + # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. + if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then + ln -s -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ + "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + fi } function Ensure_All_Needed_Input_Exists_Hydro() @@ -35,7 +41,7 @@ function Ensure_All_Needed_Input_Exists_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi - if [[ ! -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" ]]; then + if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'IC output file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" ' does not exist.' fi @@ -46,10 +52,10 @@ function Run_Software_Hydro() cd "${HYBRID_software_output_directory[Hydro]}" local -r\ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - ic_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ + ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat"\ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}"\ - "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" + "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" } diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 1683a91..25407ce 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -26,8 +26,8 @@ function Functional_Test__do-Hydro-only() return 1 fi output_files=( Hydro/* ) - if [[ ${#output_files[@]} -ne 3 ]]; then - Print_Error 'Expected ' --emph '3' " output files, but ${#output_files[@]} found." + if [[ ${#output_files[@]} -ne 4 ]]; then + Print_Error 'Expected ' --emph '4' " output files, but ${#output_files[@]} found." return 1 fi mv 'Hydro' 'Hydro-success' diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 7476be3..40ad366 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -30,11 +30,22 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() function Unit_Test__Hydro-create-input-file() { + local -r ic_file="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" touch "${HYBRID_software_base_config_file[Hydro]}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then Print_Error 'The config was not properly created in the output folder.' return 1 + elif [[ ! -L "${ic_file}" ]]; then + Print_Error 'The symbolic link to the IC file was not properly created in the output folder.' + return 1 + fi + rm "${HYBRID_software_output_directory[Hydro]}"/* + touch "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null + if [[ ! -f "${ic_file}" ]]; then + Print_Error 'The already existing ic regular file was somehow lost.' + return 1 fi Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null if [[ $? -eq 0 ]]; then @@ -58,17 +69,31 @@ function Unit_Test__Hydro-check-all-input() { Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of not-existing output directory succeeded, although failure was expected.' + Print_Error 'Ensuring existence of not-existing output directory succeeded.' return 1 fi mkdir -p "${HYBRID_software_output_directory[Hydro]}"\ - "${HYBRID_software_output_directory[IC]}" + "${HYBRID_software_output_directory[IC]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing config file succeeded.' + return 1 + fi + touch "${HYBRID_software_configuration_file[Hydro]}" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Ensuring existence of not-existing link to IC file succeeded.' + return 1 + fi + ln -s 'not-existing-target' "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Ensuring existence of not-existing config file succeeded, although failure was expected.' + Print_Error 'Ensuring existence of broken link to IC file succeeded.' return 1 fi - touch "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + touch "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + ln -s -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ + "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing folder/file failed.' @@ -91,7 +116,7 @@ function Unit_Test__Hydro-test-run-software() mkdir -p "${HYBRID_software_output_directory[Hydro]}" local -r hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt"\ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + IC_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro if [[ ! -f "${hydro_terminal_output}" ]]; then From bc46dcb0217f1ffd207e008a99b7b5c2616dfd6c Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 16 Nov 2023 11:02:50 +0100 Subject: [PATCH 226/549] Check whether the executable is set globally --- bash/sanity_checks.bash | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 9047d95..cf388bd 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -35,17 +35,19 @@ function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() function __static__Ensure_Executable_Exists() { - local label=$1 file_path - file_path="${HYBRID_software_executable[${label}]}" - if [[ "${file_path}" = '' ]]; then + local label=$1 executable + executable="${HYBRID_software_executable[${label}]}" + if [[ "${executable}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ 'Software executable for ' --emph "${label}" ' run was not specified.' - elif [[ ! -f "${file_path}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run was not found.' - elif [[ ! -x "${file_path}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run is not executable.' + elif ! hash "${executable}"; then + if [[ ! -f "${executable}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The executable file for the ' --emph "${label}" ' run was not found.' + elif [[ ! -x "${executable}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The executable file for the ' --emph "${label}" ' run is not executable.' + fi fi } From 21e7f9d5db1376598ab01cd06d31d0a62640605c Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 16 Nov 2023 12:58:32 +0100 Subject: [PATCH 227/549] Only accept gobal variables to the executable --- bash/sanity_checks.bash | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index cf388bd..d777e75 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -40,8 +40,11 @@ function __static__Ensure_Executable_Exists() if [[ "${executable}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ 'Software executable for ' --emph "${label}" ' run was not specified.' - elif ! hash "${executable}"; then - if [[ ! -f "${executable}" ]]; then + elif [[ ! "${executable}" =~ / ]]; then + if [[ ! "${executable}" =~ ^[/~] ]]; then + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + 'The executable file for the ' --emph "${label}" ' should be either a command or a global path.' + elif [[ ! -f "${executable}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The executable file for the ' --emph "${label}" ' run was not found.' elif [[ ! -x "${executable}" ]]; then From 721f1e5ff18a7a0c761bc68528d168b81a8daa9d Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 16 Nov 2023 14:06:35 +0100 Subject: [PATCH 228/549] Write functionality to create a symbolic link to the eos folder --- bash/Hydro_functionality.bash | 8 ++++++++ bash/sanity_checks.bash | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index e9a4038..3d62f8b 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -29,6 +29,14 @@ function Prepare_Software_Input_File_Hydro() ln -s -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" fi + # Create a symbolic link to the eos folder, which is assumed to exist in the vhlle repository. + local eos_folder + eos_folder="$(dirname $(which "${HYBRID_software_executable[Hydro]}"))/eos" + if [[ ! -d "${eos_folder}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The folder ' --emph "${eos_folder}" ' does not exist.' + fi + ln -s "${eos_folder}" "${HYBRID_software_output_directory[Hydro]}" } function Ensure_All_Needed_Input_Exists_Hydro() diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index d777e75..1a065c6 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -40,7 +40,7 @@ function __static__Ensure_Executable_Exists() if [[ "${executable}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ 'Software executable for ' --emph "${label}" ' run was not specified.' - elif [[ ! "${executable}" =~ / ]]; then + elif [[ "${executable}" =~ / ]]; then if [[ ! "${executable}" =~ ^[/~] ]]; then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ 'The executable file for the ' --emph "${label}" ' should be either a command or a global path.' From 29c16cea8472ff352b0c9ea0710c3093196b47b6 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 16 Nov 2023 16:07:04 +0100 Subject: [PATCH 229/549] Fix the unit and functional tests. --- tests/functional_tests_Hydro_only.bash | 15 +++++++++------ tests/unit_tests_Hydro_functionality.bash | 11 +++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 25407ce..8254f85 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -10,12 +10,15 @@ function Functional_Test__do-Hydro-only() { shopt -s nullglob - local -r config_filename='vhlle_hydro' + local -r config_filename='vhlle_hydro_config' local output_files terminal_output_file failure_message - printf ' + # Make a symlink to the python mock such that the eos folder doesn't have to be created in the mock folder + ln -s "${HYBRIDT_repository_top_level_path}/tests/mocks/vhlle_black-box.py" "vhlle_black-box.py" + mkdir 'eos' + printf " Hydro: - Executable: %s/tests/mocks/vhlle_black-box.py - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + Executable: ${HYBRIDT_repository_top_level_path}/tests/run_tests/do-Hydro-only/vhlle_black-box.py + " "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated mkdir -p 'IC' touch 'IC/SMASH_IC.dat' @@ -26,8 +29,8 @@ function Functional_Test__do-Hydro-only() return 1 fi output_files=( Hydro/* ) - if [[ ${#output_files[@]} -ne 4 ]]; then - Print_Error 'Expected ' --emph '4' " output files, but ${#output_files[@]} found." + if [[ ${#output_files[@]} -ne 5 ]]; then + Print_Error 'Expected ' --emph '5' " output files, but ${#output_files[@]} found." return 1 fi mv 'Hydro' 'Hydro-success' diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 40ad366..3eae23a 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -32,6 +32,17 @@ function Unit_Test__Hydro-create-input-file() { local -r ic_file="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" touch "${HYBRID_software_base_config_file[Hydro]}" + local dummy_exec='tmp_exec' + touch "${dummy_exec}" + chmod +x "${dummy_exec}" + HYBRID_software_executable[Hydro]="${HYBRIDT_folder_to_run_tests}/${dummy_exec}" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation succeeded even though the eos folder does not exist.' + return 1 + fi + mkdir 'eos' + rm "${HYBRID_software_output_directory[Hydro]}"/* Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then Print_Error 'The config was not properly created in the output folder.' From 590b0ef6913a1795593923c4fb5768323a059600 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 16 Nov 2023 16:26:18 +0100 Subject: [PATCH 230/549] Fix path to the executable in the functional test --- tests/functional_tests_Hydro_only.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 8254f85..260d325 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -17,7 +17,7 @@ function Functional_Test__do-Hydro-only() mkdir 'eos' printf " Hydro: - Executable: ${HYBRIDT_repository_top_level_path}/tests/run_tests/do-Hydro-only/vhlle_black-box.py + Executable: $(pwd)/vhlle_black-box.py " "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated mkdir -p 'IC' From dfbc1f941a552f8ad2eafaa176d8b3ccd2ac130e Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Fri, 17 Nov 2023 14:05:05 +0100 Subject: [PATCH 231/549] Replace hard-coded configuration file names. --- bash/global_variables.bash | 6 ++++++ bash/sanity_checks.bash | 26 ++++++++++---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 93f25c1..4354528 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -55,6 +55,12 @@ function Define_Further_Global_Variables() [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' ) + declare -rgA HYBRID_software_input_filename=( + [IC]='IC_config.yaml' + [Hydro]='hydro_config.txt' + [Sampler]='sampler_config.txt' + [Afterburner]='afterburner_config.yaml' + ) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 1a065c6..bc07874 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -16,8 +16,7 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var HYBRID_software_output_directory[${key}]="${HYBRID_output_directory}/${key}" if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then __static__Ensure_Executable_Exists "${key}" - base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") - HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" + HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${HYBRID_software_input_filename[${key}]}" fi done readonly HYBRID_software_output_directory HYBRID_software_configuration_file @@ -35,22 +34,17 @@ function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() function __static__Ensure_Executable_Exists() { - local label=$1 executable - executable="${HYBRID_software_executable[${label}]}" - if [[ "${executable}" = '' ]]; then + local label=$1 file_path + file_path="${HYBRID_software_executable[${label}]}" + if [[ "${file_path}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ 'Software executable for ' --emph "${label}" ' run was not specified.' - elif [[ "${executable}" =~ / ]]; then - if [[ ! "${executable}" =~ ^[/~] ]]; then - exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' should be either a command or a global path.' - elif [[ ! -f "${executable}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run was not found.' - elif [[ ! -x "${executable}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run is not executable.' - fi + elif [[ ! -f "${file_path}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The executable file for the ' --emph "${label}" ' run was not found.' + elif [[ ! -x "${file_path}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The executable file for the ' --emph "${label}" ' run is not executable.' fi } From 8b266b436a81d5a3564e133b4cf48af58aff8a83 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 20 Nov 2023 15:20:47 +0100 Subject: [PATCH 232/549] Change sneaky behaviour of function calling interface The utility function to call functions if existing was calling them and adding a "|| return $?" after the call, which was making the shell call the function in a context were errexit option was ignored. --- bash/utility_functions.bash | 8 ++------ tests/functional_tests_Sampler_only.bash | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index cc5de7d..f7f8d29 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -162,10 +162,7 @@ function Call_Function_If_Existing_Or_Exit() local name_of_the_function=$1 shift if [[ "$(type -t ${name_of_the_function})" = 'function' ]]; then - # Return value propagates automatically since a function returns the last exit code. - # However, when exit on error behavior is active, the script would terminate here if - # the function returns non-zero exit code and, instead we want this propagate up! - ${name_of_the_function} "$@" || return $? + ${name_of_the_function} "$@" else exit_code=${HYBRID_fatal_missing_feature} Print_Internal_And_Exit\ '\nFunction ' --emph "${name_of_the_function}" ' not found!'\ @@ -178,8 +175,7 @@ function Call_Function_If_Existing_Or_No_Op() local name_of_the_function=$1 shift if [[ "$(type -t ${name_of_the_function})" = 'function' ]]; then - # See 'Call_Function_If_Existing_Or_Exit' for more information about 'return $?' - ${name_of_the_function} "$@" || return $? + ${name_of_the_function} "$@" fi } diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 0aeea94..1f07607 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -51,7 +51,7 @@ function Functional_Test__do-Sampler-only() fi mv 'Sampler' 'Sampler-crash' # Expect Hybrid-handler to crash before calling the Sampler because of invalid config file - Print_Info 'Running Hybrid-handler expecting invalid config argument' + Print_Info 'Running Hybrid-handler expecting invalid config error' BLACK_BOX_FAIL='false' mkdir -p Sampler local -r invalid_sampler_config="invalid_hadron_sampler" From 082f1617a5f0f976574870c69cb7c78d17ee7d32 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 21 Nov 2023 11:07:00 +0100 Subject: [PATCH 233/549] Add logic to the checks of the existstence of a symlink. --- bash/Hydro_functionality.bash | 24 +++++++++++- tests/functional_tests_Hydro_only.bash | 6 +-- tests/unit_tests_Hydro_functionality.bash | 48 +++++++++++++++++------ 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 3d62f8b..2a42d68 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -30,13 +30,35 @@ function Prepare_Software_Input_File_Hydro() "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" fi # Create a symbolic link to the eos folder, which is assumed to exist in the vhlle repository. + # The user-specified software executable is guaranteed to be either a command name or + # a global path and in both cases 'which' is expected to succeed and print a global path. local eos_folder eos_folder="$(dirname $(which "${HYBRID_software_executable[Hydro]}"))/eos" if [[ ! -d "${eos_folder}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The folder ' --emph "${eos_folder}" ' does not exist.' fi - ln -s "${eos_folder}" "${HYBRID_software_output_directory[Hydro]}" + local -r link_to_eos_folder="${HYBRID_software_output_directory[Hydro]}/eos" + if [[ -d "${link_to_eos_folder}" ]]; then + if [[ ! "${link_to_eos_folder}" -ef "${eos_folder}" ]]; then + if [[ -L "${link_to_eos_folder}" ]]; then + Print_Warning 'Found a symlink ' --emph "${HYBRID_software_output_directory[Hydro]}/eos"\ + ', pointing to a different eos folder. Unlink and link again!\n' + unlink "${HYBRID_software_output_directory[Hydro]}/eos" + ln -s "${eos_folder}" "${link_to_eos_folder}" + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'A folder called eos already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ + '. Please clean up the directory.' + fi + fi + elif [[ -e "${link_to_eos_folder}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'A file called eos already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ + '. Please clean up the directory.' + else + ln -s "${eos_folder}" "${link_to_eos_folder}" + fi } function Ensure_All_Needed_Input_Exists_Hydro() diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 260d325..73b92b6 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -15,10 +15,10 @@ function Functional_Test__do-Hydro-only() # Make a symlink to the python mock such that the eos folder doesn't have to be created in the mock folder ln -s "${HYBRIDT_repository_top_level_path}/tests/mocks/vhlle_black-box.py" "vhlle_black-box.py" mkdir 'eos' - printf " + printf ' Hydro: - Executable: $(pwd)/vhlle_black-box.py - " "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + Executable: %s/vhlle_black-box.py + ' "$(pwd)" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated mkdir -p 'IC' touch 'IC/SMASH_IC.dat' diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 3eae23a..cb2f9e9 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -23,7 +23,6 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Hydro" HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' HYBRID_given_software_sections=('Hydro' ) - HYBRID_software_output_directory[IC]="${HYBRID_output_directory}/IC" HYBRID_software_executable[Hydro]="$(which echo)" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } @@ -32,17 +31,9 @@ function Unit_Test__Hydro-create-input-file() { local -r ic_file="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" touch "${HYBRID_software_base_config_file[Hydro]}" - local dummy_exec='tmp_exec' - touch "${dummy_exec}" - chmod +x "${dummy_exec}" - HYBRID_software_executable[Hydro]="${HYBRIDT_folder_to_run_tests}/${dummy_exec}" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null - if [[ $? -eq 0 ]]; then - Print_Error 'Preparation succeeded even though the eos folder does not exist.' - return 1 - fi + ln -s "$(which ls)" dummy_exec + HYBRID_software_executable[Hydro]="${HYBRIDT_folder_to_run_tests}/dummy_exec" mkdir 'eos' - rm "${HYBRID_software_output_directory[Hydro]}"/* Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then Print_Error 'The config was not properly created in the output folder.' @@ -63,6 +54,41 @@ function Unit_Test__Hydro-create-input-file() Print_Error 'Preparation of input with existent config succeeded.' return 1 fi + rm -r "${HYBRID_software_output_directory[Hydro]}"/* + mkdir "${HYBRID_software_output_directory[Hydro]}/eos" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation succeeded even though the eos folder already exists.' + return 1 + fi + rm -r "${HYBRID_software_output_directory[Hydro]}"/* + touch "${HYBRID_software_output_directory[Hydro]}/eos" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation succeeded even though a file called eos already exists.' + return 1 + fi + rm "${HYBRID_software_output_directory[Hydro]}"/* + ln -s ~ "${HYBRID_software_output_directory[Hydro]}/eos" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro + if [[ $? -eq 1 ]]; then + Print_Error 'Preparation failed to replace existing symlink.' + return 1 + fi + rm -r "${HYBRID_software_output_directory[Hydro]}"/* + ln -s "${HYBRIDT_folder_to_run_tests}/eos" "${HYBRID_software_output_directory[Hydro]}/eos" + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro + if [[ $? -eq 1 ]]; then + Print_Error 'Preparation failed although the correct symlink exists.' + return 1 + fi + rm -r 'eos' + rm "${HYBRID_software_output_directory[Hydro]}"/* + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Preparation succeeded even though the eos folder does not exist.' + return 1 + fi } function Clean_Tests_Environment_For_Following_Test__Hydro-create-input-file() From 30a248c5e7b817f62d4111e2d29113523f7659d8 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 21 Nov 2023 11:50:57 +0100 Subject: [PATCH 234/549] Replace which with type -P --- bash/Hydro_functionality.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 2a42d68..a2f4b28 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -33,7 +33,7 @@ function Prepare_Software_Input_File_Hydro() # The user-specified software executable is guaranteed to be either a command name or # a global path and in both cases 'which' is expected to succeed and print a global path. local eos_folder - eos_folder="$(dirname $(which "${HYBRID_software_executable[Hydro]}"))/eos" + eos_folder="$(dirname $(type -P "${HYBRID_software_executable[Hydro]}"))/eos" if [[ ! -d "${eos_folder}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The folder ' --emph "${eos_folder}" ' does not exist.' From a49cea7ce1a761fc5ed89ab003c9ae7f184d7f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Wed, 22 Nov 2023 11:07:19 +0100 Subject: [PATCH 235/549] Add energy to IC custom yaml file --- configs/smash_initial_conditions_custom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/smash_initial_conditions_custom.yaml b/configs/smash_initial_conditions_custom.yaml index f5bb624..9427916 100644 --- a/configs/smash_initial_conditions_custom.yaml +++ b/configs/smash_initial_conditions_custom.yaml @@ -23,5 +23,6 @@ Modi: Particles: {2212: 79, 2112: 118} #Gold197 Fermi_Motion: frozen + Sqrtsnn: 200.0 Impact: Max: 5.2 From bab02aede937ea41afc69ea0574b10f54f5c54c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= <90659054+nilssass@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:11:46 +0100 Subject: [PATCH 236/549] Relocate position of key Sqrtsnn --- configs/smash_initial_conditions_custom.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/smash_initial_conditions_custom.yaml b/configs/smash_initial_conditions_custom.yaml index 9427916..ffa8271 100644 --- a/configs/smash_initial_conditions_custom.yaml +++ b/configs/smash_initial_conditions_custom.yaml @@ -22,7 +22,7 @@ Modi: Target: Particles: {2212: 79, 2112: 118} #Gold197 - Fermi_Motion: frozen Sqrtsnn: 200.0 + Fermi_Motion: frozen Impact: Max: 5.2 From 1084d7248f57a094d772706372a2165be8fc821c Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 23 Nov 2023 14:51:31 +0100 Subject: [PATCH 237/549] Fix comments, add sanity_checks logic in Ensure_Executable_Exists --- bash/Hydro_functionality.bash | 16 ++++++------ bash/global_variables.bash | 8 +++--- bash/sanity_checks.bash | 30 ++++++++++++++++------- tests/unit_tests_Hydro_functionality.bash | 4 +-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index a2f4b28..34bedd1 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -29,9 +29,9 @@ function Prepare_Software_Input_File_Hydro() ln -s -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" fi - # Create a symbolic link to the eos folder, which is assumed to exist in the vhlle repository. - # The user-specified software executable is guaranteed to be either a command name or - # a global path and in both cases 'which' is expected to succeed and print a global path. + # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software + # folder. The user-specified software executable is guaranteed to be either a command name + # or a global path and in both cases 'type -P' is expected to succeed and print a global path. local eos_folder eos_folder="$(dirname $(type -P "${HYBRID_software_executable[Hydro]}"))/eos" if [[ ! -d "${eos_folder}" ]]; then @@ -43,19 +43,19 @@ function Prepare_Software_Input_File_Hydro() if [[ ! "${link_to_eos_folder}" -ef "${eos_folder}" ]]; then if [[ -L "${link_to_eos_folder}" ]]; then Print_Warning 'Found a symlink ' --emph "${HYBRID_software_output_directory[Hydro]}/eos"\ - ', pointing to a different eos folder. Unlink and link again!\n' + '\npointing to a different eos folder. Unlink and link again!\n' unlink "${HYBRID_software_output_directory[Hydro]}/eos" ln -s "${eos_folder}" "${link_to_eos_folder}" else exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'A folder called eos already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ - '. Please clean up the directory.' + 'A ' --emph 'eos' ' folder called already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ + '.' 'Please remove it and run the hybrid handler again.' fi fi elif [[ -e "${link_to_eos_folder}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'A file called eos already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ - '. Please clean up the directory.' + 'A ' --emph 'eos' ' file already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ + '.' 'Please remove it and run the hybrid handler again.' else ln -s "${eos_folder}" "${link_to_eos_folder}" fi diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 4354528..7aded3d 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -56,10 +56,10 @@ function Define_Further_Global_Variables() [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' ) declare -rgA HYBRID_software_input_filename=( - [IC]='IC_config.yaml' - [Hydro]='hydro_config.txt' - [Sampler]='sampler_config.txt' - [Afterburner]='afterburner_config.yaml' + [IC]='IC_config.yaml' + [Hydro]='hydro_config.txt' + [Sampler]='sampler_config.txt' + [Afterburner]='afterburner_config.yaml' ) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index bc07874..a236c52 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -16,7 +16,8 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var HYBRID_software_output_directory[${key}]="${HYBRID_output_directory}/${key}" if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then __static__Ensure_Executable_Exists "${key}" - HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${HYBRID_software_input_filename[${key}]}" + printf -v HYBRID_software_configuration_file[${key}] \ + "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_input_filename[${key}]}" fi done readonly HYBRID_software_output_directory HYBRID_software_configuration_file @@ -34,17 +35,28 @@ function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() function __static__Ensure_Executable_Exists() { - local label=$1 file_path - file_path="${HYBRID_software_executable[${label}]}" - if [[ "${file_path}" = '' ]]; then + local label=$1 executable + executable="${HYBRID_software_executable[${label}]}" + if [[ "${executable}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ 'Software executable for ' --emph "${label}" ' run was not specified.' - elif [[ ! -f "${file_path}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run was not found.' - elif [[ ! -x "${file_path}" ]]; then + elif ! hash "${executable}"; then + if [[ ! -f "${executable}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The executable file for the ' --emph "${label}" ' run was not found.' + elif [[ ! -x "${executable}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The executable file for the ' --emph "${label}" ' run is not executable.' + fi + # It is important to perform this check with 'type' and not with 'hash' because 'hash' with + # paths always succeed -> https://stackoverflow.com/a/42362142/14967071 + # This will be entered if the user gives something stupid as '~' as executable. + elif ! type "${executable}" &> /dev/null; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run is not executable.' + 'The command ' --emph "${executable}" ' specified for the '\ + --emph "${label}" ' run was not located by the shell.'\ + 'Please check your ' --emph 'PATH' ' environment variable and make sure'\ + 'that '--emph "type \"${executable}\"" ' succeeds in your terminal.' fi } diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index cb2f9e9..658da32 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -71,14 +71,14 @@ function Unit_Test__Hydro-create-input-file() rm "${HYBRID_software_output_directory[Hydro]}"/* ln -s ~ "${HYBRID_software_output_directory[Hydro]}/eos" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro - if [[ $? -eq 1 ]]; then + if [[ $? -ne 0 ]]; then Print_Error 'Preparation failed to replace existing symlink.' return 1 fi rm -r "${HYBRID_software_output_directory[Hydro]}"/* ln -s "${HYBRIDT_folder_to_run_tests}/eos" "${HYBRID_software_output_directory[Hydro]}/eos" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro - if [[ $? -eq 1 ]]; then + if [[ $? -ne 0 ]]; then Print_Error 'Preparation failed although the correct symlink exists.' return 1 fi From df5b05e016d2d501862168a575acab957a1cdc7d Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 23 Nov 2023 15:28:40 +0100 Subject: [PATCH 238/549] Fix Ensure_Executable_Exists --- bash/sanity_checks.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index a236c52..8ad6835 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -40,7 +40,7 @@ function __static__Ensure_Executable_Exists() if [[ "${executable}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ 'Software executable for ' --emph "${label}" ' run was not specified.' - elif ! hash "${executable}"; then + elif [[ "${executable}" != / ]]; then if [[ ! -f "${executable}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The executable file for the ' --emph "${label}" ' run was not found.' @@ -51,12 +51,12 @@ function __static__Ensure_Executable_Exists() # It is important to perform this check with 'type' and not with 'hash' because 'hash' with # paths always succeed -> https://stackoverflow.com/a/42362142/14967071 # This will be entered if the user gives something stupid as '~' as executable. - elif ! type "${executable}" &> /dev/null; then + elif ! type -P "${executable}" &> /dev/null; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The command ' --emph "${executable}" ' specified for the '\ --emph "${label}" ' run was not located by the shell.'\ 'Please check your ' --emph 'PATH' ' environment variable and make sure'\ - 'that '--emph "type \"${executable}\"" ' succeeds in your terminal.' + 'that '--emph "type -P \"${executable}\"" ' succeeds in your terminal.' fi } From 854e2d8f2508b11078e60c55d1de7edde119f097 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Thu, 23 Nov 2023 16:01:13 +0100 Subject: [PATCH 239/549] Add a comment about config.yaml file --- bash/Afterburner_functionality.bash | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index e232ac0..24e1f63 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -34,6 +34,8 @@ function Prepare_Software_Input_File_Afterburner() exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0"\ ' already exists.' + # Here the config.yaml file that SMASH produces in the output folder is used to determine + # the initial number of particles elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ From 1df189f26a2b159b45b909c753fcc9f2244f2c6e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 24 Nov 2023 08:37:21 +0100 Subject: [PATCH 240/549] Make error message slightly more precise --- bash/Afterburner_functionality.bash | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 24e1f63..0c337bc 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -23,7 +23,7 @@ function Prepare_Software_Input_File_Afterburner() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi - + if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ @@ -34,12 +34,13 @@ function Prepare_Software_Input_File_Afterburner() exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0"\ ' already exists.' - # Here the config.yaml file that SMASH produces in the output folder is used to determine - # the initial number of particles + # Here the config.yaml file is expected to be produced by SMASH in the output folder + # during the IC run. It is used to determine the initial number of particles. elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ - ' does not exist which is needed to check number of initial nucleons.' + '\ndoes not exist, but is needed to check number of initial nucleons.' \ + 'This file is expected to be produced by the IC software run.' elif [[ ! -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ @@ -52,7 +53,7 @@ function Prepare_Software_Input_File_Afterburner() '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" else ln -s "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" + "${HYBRID_software_output_directory[Afterburner]}/sampling0" fi } From ad47f7e3d9aa4d5a53cae88654fc58f9630cad73 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Wed, 15 Nov 2023 16:26:26 +0100 Subject: [PATCH 241/549] Rename input in config to config, add option to specify input files --- bash/Hydro_functionality.bash | 18 ++++++++----- bash/global_variables.bash | 21 ++++++++++++--- bash/sanity_checks.bash | 8 +++++- tests/functional_tests_Sampler_only.bash | 2 +- tests/unit_tests_configuration.bash | 33 +++++++++++++++--------- 5 files changed, 58 insertions(+), 24 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 34bedd1..4a943ad 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -25,9 +25,15 @@ function Prepare_Software_Input_File_Hydro() fi # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. - if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then - ln -s -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ - "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + #if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then + # ln -s -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ + # "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + #fi + #echo "${HYBRID_software_input_file[Hydro]}" + base_file=$(basename "${HYBRID_software_input_file[Hydro]}") + if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${base_file}" ]]; then + ln -s -f "${HYBRID_software_output_directory[IC]}/${base_file}"\ + "${HYBRID_software_input_file[Hydro]}" fi # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software # folder. The user-specified software executable is guaranteed to be either a command name @@ -71,9 +77,9 @@ function Ensure_All_Needed_Input_Exists_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi - if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then + if [[ ! -e "${HYBRID_software_input_file[Hydro]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'IC output file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" ' does not exist.' + 'IC output file ' --emph "${HYBRID_software_input_file[Hydro]}" ' does not exist.' fi } @@ -82,7 +88,7 @@ function Run_Software_Hydro() cd "${HYBRID_software_output_directory[Hydro]}" local -r\ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat"\ + ic_output_file_path="${HYBRID_software_input_file[Hydro]}"\ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}"\ "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 7aded3d..8bc1209 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -36,22 +36,25 @@ function Define_Further_Global_Variables() declare -rgA HYBRID_hybrid_handler_valid_keys=() declare -rgA HYBRID_ic_valid_keys=( [Executable]='HYBRID_software_executable[IC]' - [Input_file]='HYBRID_software_base_config_file[IC]' + [Config_file]='HYBRID_software_base_config_file[IC]' [Software_keys]='HYBRID_software_new_input_keys[IC]' ) declare -rgA HYBRID_hydro_valid_keys=( [Executable]='HYBRID_software_executable[Hydro]' - [Input_file]='HYBRID_software_base_config_file[Hydro]' + [Config_file]='HYBRID_software_base_config_file[Hydro]' + [Input_file]='HYBRID_software_default_input_file[Hydro]' [Software_keys]='HYBRID_software_new_input_keys[Hydro]' ) declare -rgA HYBRID_sampler_valid_keys=( [Executable]='HYBRID_software_executable[Sampler]' - [Input_file]='HYBRID_software_base_config_file[Sampler]' + [Config_file]='HYBRID_software_base_config_file[Sampler]' + [Input_file]='HYBRID_software_default_input_file[Sampler]' [Software_keys]='HYBRID_software_new_input_keys[Sampler]' ) declare -rgA HYBRID_afterburner_valid_keys=( [Executable]='HYBRID_software_executable[Afterburner]' - [Input_file]='HYBRID_software_base_config_file[Afterburner]' + [Config_file]='HYBRID_software_base_config_file[Afterburner]' + [Input_file]='HYBRID_software_default_input_file[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' ) @@ -79,6 +82,11 @@ function Define_Further_Global_Variables() [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" ) + declare -gA HYBRID_software_default_input_file=( + [Hydro]="${HYBRID_output_directory}/IC/SMASH_IC.dat" + [Sampler]="${HYBRID_output_directory}/Hydro/freezeout.dat" + [Afterburner]="${HYBRID_output_directory}/Sampler/sampling0" + ) declare -gA HYBRID_software_new_input_keys=( [IC]='' [Hydro]='' @@ -101,6 +109,11 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) + declare -gA HYBRID_software_input_file=( + [Hydro]='' + [Sampler]='' + [Afterburner]='' + ) } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 8ad6835..10d8f67 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -18,9 +18,15 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var __static__Ensure_Executable_Exists "${key}" printf -v HYBRID_software_configuration_file[${key}] \ "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_input_filename[${key}]}" + base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") + HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" + if [[ ${key} != 'IC' ]]; then + base_file=$(basename "${HYBRID_software_default_input_file[${key}]}") + HYBRID_software_input_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" + fi fi done - readonly HYBRID_software_output_directory HYBRID_software_configuration_file + readonly HYBRID_software_output_directory HYBRID_software_configuration_file HYBRID_software_input_file } function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 1f07607..e41c782 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -59,7 +59,7 @@ function Functional_Test__do-Sampler-only() printf ' Sampler: Executable: %s/tests/mocks/sampler_black_box.py - Input_file: %s + Config_file: %s ' "${HYBRIDT_repository_top_level_path}"\ "${invalid_sampler_config}" > "${hybrid_handler_config}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 80623b3..a97ac12 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -174,8 +174,9 @@ function __static__Test_Section_Parsing_In_Subshell() local section executable input_file new_keys section=$1 executable=$2 - input_file=$3 - new_keys=$4 + config_file=$3 + input_file=$4 + new_keys=$5 Call_Codebase_Function Validate_And_Parse_Configuration_File if [[ "${#HYBRID_given_software_sections[@]}" -ne 1 ]] ||\ [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then @@ -184,8 +185,13 @@ function __static__Test_Section_Parsing_In_Subshell() if [[ ${HYBRID_software_executable[${section}]} != "${executable}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (software executable).' fi - if [[ ${HYBRID_software_base_config_file[${section}]} != "${input_file}" ]]; then - Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (input file).' + if [[ ${HYBRID_software_base_config_file[${section}]} != "${config_file}" ]]; then + Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (config file).' + fi + if [[ ${section} != 'IC' ]]; then + if [[ ${HYBRID_software_default_input_file[${section}]} != "${input_file}" ]]; then + Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (input file).' + fi fi if [[ ${HYBRID_software_new_input_keys[${section}]} != "${new_keys}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (software keys).' @@ -203,13 +209,13 @@ function Unit_Test__configuration-parse-IC-section() printf ' IC: Executable: foo - Input_file: bar + Config_file: bar Software_keys: General: Randomseed: 12345 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ - 'IC' 'foo' 'bar' $'General:\n Randomseed: 12345' + 'IC' 'foo' 'bar' '' $'General:\n Randomseed: 12345' if [[ $? -ne 0 ]]; then return 1 fi @@ -229,12 +235,13 @@ function Unit_Test__configuration-parse-Hydro-section() printf ' Hydro: Executable: foo - Input_file: bar + Config_file: bar + Input_file: ket Software_keys: etaS: 0.12345 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ - 'Hydro' 'foo' 'bar' 'etaS: 0.12345' + 'Hydro' 'foo' 'bar' 'ket' 'etaS: 0.12345' if [[ $? -ne 0 ]]; then return 1 fi @@ -254,12 +261,13 @@ function Unit_Test__configuration-parse-Sampler-section() printf ' Sampler: Executable: foo - Input_file: bar + Config_file: bar + Input_file: ket Software_keys: shear: 1.2345 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ - 'Sampler' 'foo' 'bar' 'shear: 1.2345' + 'Sampler' 'foo' 'bar' 'ket' 'shear: 1.2345' if [[ $? -ne 0 ]]; then return 1 fi @@ -279,13 +287,14 @@ function Unit_Test__configuration-parse-Afterburner-section() printf ' Afterburner: Executable: foo - Input_file: bar + Config_file: bar + Input_file: ket Software_keys: General: End_Time: 42000 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ - 'Afterburner' 'foo' 'bar' $'General:\n End_Time: 42000' + 'Afterburner' 'foo' 'bar' 'ket' $'General:\n End_Time: 42000' if [[ $? -ne 0 ]]; then return 1 fi From 63c989238bafabbf6bff7dbbdd305d15e7ec3678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 16 Nov 2023 16:29:38 +0100 Subject: [PATCH 242/549] Extend alternative input data name to Afterburner --- bash/Afterburner_functionality.bash | 23 ++++++------- bash/Hydro_functionality.bash | 5 --- bash/global_variables.bash | 7 ++-- bash/sanity_checks.bash | 5 ++- tests/IC/SMASH_IC.dat | 0 tests/functional_tests_Afterburner_only.bash | 35 ++++++++++++++++++++ tests/functional_tests_Hydro_only.bash | 21 ++++++++++++ tests/functional_tests_Sampler_only.bash | 21 ++++++++++++ tests/unit_tests_configuration.bash | 5 ++- 9 files changed, 97 insertions(+), 25 deletions(-) create mode 100644 tests/IC/SMASH_IC.dat diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 0c337bc..890638d 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -23,10 +23,10 @@ function Prepare_Software_Input_File_Afterburner() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi - - if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" ]]; then + + if [[ ! -f "${HYBRID_software_input_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Sampler output file ' --emph "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ + 'Sampler output file ' --emph "${HYBRID_software_input_file[Afterburner]}"\ ' does not exist.' fi if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then @@ -39,21 +39,20 @@ function Prepare_Software_Input_File_Afterburner() elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ - '\ndoes not exist, but is needed to check number of initial nucleons.' \ - 'This file is expected to be produced by the IC software run.' - elif [[ ! -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" ]]; then + ' does not exist which is needed to check number of initial nucleons.' + elif [[ ! -f "${HYBRID_software_output_directory[IC]}/${HYBRID_optional_feature[Spectator_Source]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ + 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/${HYBRID_optional_feature[Spectator_Source]}"\ ' does not exist.' fi "${HYBRID_external_python_scripts[Add_spectators_from_IC]}"\ - '--sampled_particle_list' "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ - '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ + '--sampled_particle_list' "${HYBRID_software_input_file[Afterburner]}"\ + '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/${HYBRID_optional_feature[Spectator_Source]}"\ '--output_file' "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" else - ln -s "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" + ln -s "${HYBRID_software_input_file[Afterburner]}"\ + "${HYBRID_software_output_directory[Afterburner]}/sampling0" fi } @@ -71,7 +70,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_configuration_file[Afterburner]}/sampling0"\ + 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ ' was not found.' fi } diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 4a943ad..30a6ad7 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -25,11 +25,6 @@ function Prepare_Software_Input_File_Hydro() fi # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. - #if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then - # ln -s -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ - # "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" - #fi - #echo "${HYBRID_software_input_file[Hydro]}" base_file=$(basename "${HYBRID_software_input_file[Hydro]}") if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${base_file}" ]]; then ln -s -f "${HYBRID_software_output_directory[IC]}/${base_file}"\ diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 8bc1209..7b5ee66 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -48,7 +48,6 @@ function Define_Further_Global_Variables() declare -rgA HYBRID_sampler_valid_keys=( [Executable]='HYBRID_software_executable[Sampler]' [Config_file]='HYBRID_software_base_config_file[Sampler]' - [Input_file]='HYBRID_software_default_input_file[Sampler]' [Software_keys]='HYBRID_software_new_input_keys[Sampler]' ) declare -rgA HYBRID_afterburner_valid_keys=( @@ -57,6 +56,7 @@ function Define_Further_Global_Variables() [Input_file]='HYBRID_software_default_input_file[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' + [Spectator_Source]='HYBRID_optional_feature[Spectator_Source]' ) declare -rgA HYBRID_software_input_filename=( [IC]='IC_config.yaml' @@ -84,8 +84,7 @@ function Define_Further_Global_Variables() ) declare -gA HYBRID_software_default_input_file=( [Hydro]="${HYBRID_output_directory}/IC/SMASH_IC.dat" - [Sampler]="${HYBRID_output_directory}/Hydro/freezeout.dat" - [Afterburner]="${HYBRID_output_directory}/Sampler/sampling0" + [Afterburner]="${HYBRID_output_directory}/Sampler/particle_lists.oscar" ) declare -gA HYBRID_software_new_input_keys=( [IC]='' @@ -95,6 +94,7 @@ function Define_Further_Global_Variables() ) declare -gA HYBRID_optional_feature=( [Add_spectators_from_IC]='FALSE' + [Spectator_Source]="/SMASH_IC.oscar" ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( @@ -111,7 +111,6 @@ function Define_Further_Global_Variables() ) declare -gA HYBRID_software_input_file=( [Hydro]='' - [Sampler]='' [Afterburner]='' ) } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 10d8f67..7ab7e8f 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -20,9 +20,12 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_input_filename[${key}]}" base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" - if [[ ${key} != 'IC' ]]; then + if [[ ${key} == 'Hydro' ]]; then base_file=$(basename "${HYBRID_software_default_input_file[${key}]}") HYBRID_software_input_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" + elif [[ ${key} == 'Afterburner' ]]; then + base_file=$(basename "${HYBRID_software_default_input_file[${key}]}") + HYBRID_software_input_file['Afterburner']="${HYBRID_software_output_directory['Sampler']}/${base_file}" fi fi done diff --git a/tests/IC/SMASH_IC.dat b/tests/IC/SMASH_IC.dat new file mode 100644 index 0000000..e69de29 diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index c6353b0..5838bfd 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -46,6 +46,22 @@ function Functional_Test__do-Afterburner-only() Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success' + rm 'Sampler/particle_lists.oscar' + touch 'Sampler/particle_lists_2.oscar' + printf ' + Afterburner: + Executable: %s/tests/mocks/smash_afterburner_black-box.py + Input_file: particle_lists_2.oscar + Software_keys: + Modi: + List: + File_Directory: "." + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + # Expect success and test absence of "SMASH" unfinished file + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + __static__Check_Successful_Handler_Run $? || return 1 + mv 'Afterburner' 'Afterburner-success-custom-input' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' terminal_output_file='Afterburner/Terminal_Output.txt' @@ -82,10 +98,29 @@ function Functional_Test__do-Afterburner-only() Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' mkdir 'IC' touch 'IC/config.yaml' 'IC/SMASH_IC.oscar' + touch 'Sampler/particle_lists.oscar' + printf ' + Afterburner: + Executable: %s/tests/mocks/smash_afterburner_black-box.py + Add_spectators_from_IC: TRUE + Software_keys: + Modi: + List: + File_Directory: "." + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + __static__Check_Successful_Handler_Run $? || return 1 + mv 'Afterburner' 'Afterburner-success-with-spectators' + # Expect success and test the add_spectator functionality with custom spectator input + Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' + rm -r 'IC' + mkdir 'IC' + touch 'IC/config.yaml' 'IC/SMASH_IC_2.oscar' printf ' Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py Add_spectators_from_IC: TRUE + Spectator_Source: SMASH_IC_2.oscar Software_keys: Modi: List: diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 73b92b6..38e2ffd 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -34,6 +34,27 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-success' + #Expect sucess with custom input file name + printf ' + Hydro: + Executable: %s/tests/mocks/vhlle_black-box.py + Input_file: input + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + # Run the hydro stage and check if freezeout is successfully generated + rm 'IC/SMASH_IC.dat' + touch 'IC/input' + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + output_files=( Hydro/* ) + if [[ ${#output_files[@]} -ne 4 ]]; then + Print_Error 'Expected ' --emph '4' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'Hydro' 'Hydro-succes-custom-input' # Expect failure when giving an invalid IC output Print_Info 'Running Hybrid-handler expecting invalid IC argument' terminal_output_file='Hydro/Terminal_Output.txt' diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index e41c782..c5cabc6 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -31,6 +31,27 @@ function Functional_Test__do-Sampler-only() return 1 fi mv 'Sampler' 'Sampler-success' + rm 'Hydro/freezeout.dat' + touch 'Hydro/freezeout_2.dat' + printf ' + Sampler: + Executable: %s/tests/mocks/sampler_black_box.py + Software_keys: + surface: ../Hydro/freezeout_2.dat + ' "${HYBRIDT_repository_top_level_path}" > "${hybrid_handler_config}" + # Expect success and test presence of output files + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + output_files=( Sampler/* ) + if [[ ${#output_files[@]} -ne 4 ]]; then + Print_Error 'Expected ' --emph '4' " output files, but ${#output_files[@]} found." + return 1 + fi + mv 'Sampler' 'Sampler-success-custom-input' # Expect failure and test terminal output local terminal_output_file error_message terminal_output_file='Sampler/Terminal_Output.txt' diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index a97ac12..d765fe7 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -188,7 +188,7 @@ function __static__Test_Section_Parsing_In_Subshell() if [[ ${HYBRID_software_base_config_file[${section}]} != "${config_file}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (config file).' fi - if [[ ${section} != 'IC' ]]; then + if [[ ${section} != 'IC' ]] && [[ ${section} != 'Sampler' ]]; then if [[ ${HYBRID_software_default_input_file[${section}]} != "${input_file}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (input file).' fi @@ -262,12 +262,11 @@ function Unit_Test__configuration-parse-Sampler-section() Sampler: Executable: foo Config_file: bar - Input_file: ket Software_keys: shear: 1.2345 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ - 'Sampler' 'foo' 'bar' 'ket' 'shear: 1.2345' + 'Sampler' 'foo' 'bar' '' 'shear: 1.2345' if [[ $? -ne 0 ]]; then return 1 fi From 0ea66efa5ed9357fee63fe0e5b748485c540ea0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Wed, 22 Nov 2023 17:49:55 +0100 Subject: [PATCH 243/549] Allow for input files outside of the build folder --- bash/Afterburner_functionality.bash | 98 ++++++++++++++++---- bash/Hydro_functionality.bash | 40 ++++++-- bash/global_variables.bash | 32 +++++-- bash/sanity_checks.bash | 17 ++-- tests/functional_tests_Afterburner_only.bash | 15 +-- tests/functional_tests_Hydro_only.bash | 7 +- tests/unit_tests_Hydro_functionality.bash | 6 +- tests/unit_tests_configuration.bash | 2 +- 8 files changed, 165 insertions(+), 52 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 890638d..36ac9d0 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -23,36 +23,94 @@ function Prepare_Software_Input_File_Afterburner() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi - - if [[ ! -f "${HYBRID_software_input_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Sampler output file ' --emph "${HYBRID_software_input_file[Afterburner]}"\ - ' does not exist.' - fi + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then - if [[ -f "${HYBRID_software_output_directory[Sampler]}/sampling0" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/sampling0"\ + + if [[ "${HYBRID_software_user_custom_input_file[Afterburner]}" = '' ]]; then + if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_software_default_input_file[Afterburner]}" ]]; then + if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Input to Afterburner' --emph "${HYBRID_software_input_file[Afterburner]}"\ + ' does not exist.' + fi + sampled_particle_list="${HYBRID_software_input_file[Afterburner]}" + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The input file for the afterburner ' \ + --emph "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_software_default_input_file[Afterburner]}" ' already exists.' - # Here the config.yaml file is expected to be produced by SMASH in the output folder - # during the IC run. It is used to determine the initial number of particles. - elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then + fi + else + base_file=$(basename "${HYBRID_software_user_custom_input_file[Afterburner]}") + if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/${base_file}" ]]; then + if [[ ! -f "${HYBRID_software_user_custom_input_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Input to Afterburner' --emph "${HYBRID_software_user_custom_input_file[Afterburner]}"\ + ' does not exist.' + fi + sampled_particle_list="${HYBRID_software_user_custom_input_file[Afterburner]}" + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The input file for the afterburner ' \ + --emph "${HYBRID_software_output_directory[Afterburner]}/${base_file}" + ' already exists.' + fi + + fi + if [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ ' does not exist which is needed to check number of initial nucleons.' - elif [[ ! -f "${HYBRID_software_output_directory[IC]}/${HYBRID_optional_feature[Spectator_Source]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Initial condition file ' --emph "${HYBRID_software_output_directory[IC]}/${HYBRID_optional_feature[Spectator_Source]}"\ - ' does not exist.' + fi + if [[ "${HYBRID_software_user_custom_input_file[Spectators]}" = '' ]]; then + if [[ ! -f "${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_file[Spectators]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Spectator list ' --emph "${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_file[Spectators]}"\ + ' does not exist.' + fi + spectator_list="${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_file[Spectators]}" + else + if [[ ! -f "${HYBRID_software_user_custom_input_file[Spectators]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Spectator list ' --emph "${HYBRID_software_user_custom_input_file[Spectators]}"\ + ' does not exist.' + fi + spectator_list="${HYBRID_software_user_custom_input_file[Spectators]}" fi "${HYBRID_external_python_scripts[Add_spectators_from_IC]}"\ - '--sampled_particle_list' "${HYBRID_software_input_file[Afterburner]}"\ - '--initial_particle_list' "${HYBRID_software_output_directory[IC]}/${HYBRID_optional_feature[Spectator_Source]}"\ + '--sampled_particle_list' "${sampled_particle_list}"\ + '--initial_particle_list' "${spectator_list}"\ '--output_file' "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" + else - ln -s "${HYBRID_software_input_file[Afterburner]}"\ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" + if [[ "${HYBRID_software_user_custom_input_file[Afterburner]}" = '' ]]; then + if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then + if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Input to Afterburner' --emph "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" \ + ' does not exist.' + fi + ln -s -f "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" \ + "${HYBRID_software_output_directory[Afterburner]}/sampling0" + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" ' already exists.' + fi + else + if [[ ! -f "${HYBRID_software_user_custom_input_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Input to Afterburner' --emph "${HYBRID_software_user_custom_input_file[Afterburner]}"\ + ' does not exist.' + fi + if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then + ln -s -f "${HYBRID_software_user_custom_input_file[Afterburner]}"\ + "${HYBRID_software_output_directory[Afterburner]}/sampling0" + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0" ' already exists.' + fi + fi fi } diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 30a6ad7..373d582 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -25,10 +25,36 @@ function Prepare_Software_Input_File_Hydro() fi # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. - base_file=$(basename "${HYBRID_software_input_file[Hydro]}") - if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${base_file}" ]]; then - ln -s -f "${HYBRID_software_output_directory[IC]}/${base_file}"\ - "${HYBRID_software_input_file[Hydro]}" + if [[ "${HYBRID_software_user_custom_input_file[Hydro]}" = '' ]]; then + if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ]]; then + if [[ ! -f "${HYBRID_software_input_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Input to Hydro' --emph "${HYBRID_software_input_file[Hydro]}"\ + ' does not exist.' + fi + ln -s -f "${HYBRID_software_input_file[Hydro]}"\ + "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The input file for Hydro ' --emph \ + "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}"\ + ' already exists.' + fi + else + if [[ ! -f "${HYBRID_software_user_custom_input_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Input to Hydro' --emph "${HYBRID_software_user_custom_input_file[Hydro]}"\ + ' does not exist.' + fi + if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ]]; then + ln -s -f "${HYBRID_software_user_custom_input_file[Hydro]}"\ + "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The input file for Hydro ' --emph \ + "${HYBRID_software_output_directory[Hydro]}/${base_file}"\ + ' already exists.' + fi fi # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software # folder. The user-specified software executable is guaranteed to be either a command name @@ -72,9 +98,9 @@ function Ensure_All_Needed_Input_Exists_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi - if [[ ! -e "${HYBRID_software_input_file[Hydro]}" ]]; then + if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'IC output file ' --emph "${HYBRID_software_input_file[Hydro]}" ' does not exist.' + 'IC output file ' --emph "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ' does not exist.' fi } @@ -83,7 +109,7 @@ function Run_Software_Hydro() cd "${HYBRID_software_output_directory[Hydro]}" local -r\ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - ic_output_file_path="${HYBRID_software_input_file[Hydro]}"\ + ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}"\ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}"\ "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 7b5ee66..5c800e8 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -42,7 +42,7 @@ function Define_Further_Global_Variables() declare -rgA HYBRID_hydro_valid_keys=( [Executable]='HYBRID_software_executable[Hydro]' [Config_file]='HYBRID_software_base_config_file[Hydro]' - [Input_file]='HYBRID_software_default_input_file[Hydro]' + [Input_file]='HYBRID_software_user_custom_input_file[Hydro]' [Software_keys]='HYBRID_software_new_input_keys[Hydro]' ) declare -rgA HYBRID_sampler_valid_keys=( @@ -53,10 +53,24 @@ function Define_Further_Global_Variables() declare -rgA HYBRID_afterburner_valid_keys=( [Executable]='HYBRID_software_executable[Afterburner]' [Config_file]='HYBRID_software_base_config_file[Afterburner]' - [Input_file]='HYBRID_software_default_input_file[Afterburner]' + [Input_file]='HYBRID_software_user_custom_input_file[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' - [Spectator_Source]='HYBRID_optional_feature[Spectator_Source]' + [Spectators_source]='HYBRID_optional_feature[Spectators_source]' + ) + declare -rgA HYBRID_software_default_input_file=( + [IC]='' + [Hydro]="SMASH_IC.dat" + [Sampler]="freezeout.dat" + [Spectators]="SMASH_IC.oscar" + [Afterburner_without_spectators]="particle_lists.oscar" + [Afterburner]="sampling0" + ) + declare -rgA HYBRID_relative_key=( + [Hydro]="IC" + [Sampler]="Hydro" + [Spectators]="IC" + [Afterburner]="Afterburner" ) declare -rgA HYBRID_software_input_filename=( [IC]='IC_config.yaml' @@ -76,16 +90,17 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) + declare -gA HYBRID_software_user_custom_input_file=( + [Hydro]='' + [Spectators]='' + [Afterburner]='' + ) declare -gA HYBRID_software_base_config_file=( [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" ) - declare -gA HYBRID_software_default_input_file=( - [Hydro]="${HYBRID_output_directory}/IC/SMASH_IC.dat" - [Afterburner]="${HYBRID_output_directory}/Sampler/particle_lists.oscar" - ) declare -gA HYBRID_software_new_input_keys=( [IC]='' [Hydro]='' @@ -94,7 +109,7 @@ function Define_Further_Global_Variables() ) declare -gA HYBRID_optional_feature=( [Add_spectators_from_IC]='FALSE' - [Spectator_Source]="/SMASH_IC.oscar" + [Spectators_source]='' ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( @@ -111,6 +126,7 @@ function Define_Further_Global_Variables() ) declare -gA HYBRID_software_input_file=( [Hydro]='' + [Spectators]='' [Afterburner]='' ) } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 7ab7e8f..0336a30 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -20,15 +20,20 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_input_filename[${key}]}" base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" - if [[ ${key} == 'Hydro' ]]; then - base_file=$(basename "${HYBRID_software_default_input_file[${key}]}") - HYBRID_software_input_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" - elif [[ ${key} == 'Afterburner' ]]; then - base_file=$(basename "${HYBRID_software_default_input_file[${key}]}") - HYBRID_software_input_file['Afterburner']="${HYBRID_software_output_directory['Sampler']}/${base_file}" + # Set here input data file of software if it was not set by user + if [[ ${key} != 'IC' ]] && [[ ${key} != 'Sampler' ]]; then + if [[ "${HYBRID_software_input_file[${key}]}" = '' ]]; then + HYBRID_software_input_file[${key}]="${HYBRID_software_output_directory[${HYBRID_relative_key[${key}]}]}/${HYBRID_software_default_input_file["${key}"]}" + else + base_file_input=$(basename "${HYBRID_software_default_input_file[${key}]}") + HYBRID_software_input_file[${key}]="${HYBRID_software_output_directory[${HYBRID_relative_key[${key}]}]}/${base_file_input}" + fi fi fi done + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]] && [[ "${HYBRID_optional_feature[Spectators_source]}" != '' ]]; then + HYBRID_software_user_custom_input_file['Spectators']="${HYBRID_optional_feature[Spectators_source]}" + fi readonly HYBRID_software_output_directory HYBRID_software_configuration_file HYBRID_software_input_file } diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 5838bfd..3930761 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -47,16 +47,17 @@ function Functional_Test__do-Afterburner-only() __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success' rm 'Sampler/particle_lists.oscar' - touch 'Sampler/particle_lists_2.oscar' + mkdir -p test + touch 'test/particle_lists_2.oscar' printf ' Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py - Input_file: particle_lists_2.oscar + Input_file: %s/tests/run_tests/do-Afterburner-only/test/particle_lists_2.oscar Software_keys: Modi: List: File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -115,17 +116,19 @@ function Functional_Test__do-Afterburner-only() Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' rm -r 'IC' mkdir 'IC' - touch 'IC/config.yaml' 'IC/SMASH_IC_2.oscar' + touch 'IC/config.yaml' + mkdir -p test + touch 'test/SMASH_IC_2.oscar' printf ' Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py Add_spectators_from_IC: TRUE - Spectator_Source: SMASH_IC_2.oscar + Spectators_source: %s/tests/run_tests/do-Afterburner-only/test/SMASH_IC_2.oscar Software_keys: Modi: List: File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 38e2ffd..5fa9642 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -38,11 +38,12 @@ function Functional_Test__do-Hydro-only() printf ' Hydro: Executable: %s/tests/mocks/vhlle_black-box.py - Input_file: input - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + Input_file: %s/tests/run_tests/do-Hydro-only/test/input + ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated rm 'IC/SMASH_IC.dat' - touch 'IC/input' + mkdir -p test + touch 'test/input' Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 0 ]]; then diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 658da32..502552b 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -34,6 +34,8 @@ function Unit_Test__Hydro-create-input-file() ln -s "$(which ls)" dummy_exec HYBRID_software_executable[Hydro]="${HYBRIDT_folder_to_run_tests}/dummy_exec" mkdir 'eos' + mkdir -p "${HYBRID_software_output_directory[IC]}" + touch "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then Print_Error 'The config was not properly created in the output folder.' @@ -153,7 +155,7 @@ function Unit_Test__Hydro-test-run-software() mkdir -p "${HYBRID_software_output_directory[Hydro]}" local -r hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt"\ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - IC_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro if [[ ! -f "${hydro_terminal_output}" ]]; then @@ -163,6 +165,8 @@ function Unit_Test__Hydro-test-run-software() terminal_output_result=$(< "${hydro_terminal_output}") correct_result="-params ${Hydro_config_file_path} -ISinput ${IC_output_file_path} -outputDir ${HYBRID_software_output_directory[Hydro]}" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then + echo "${terminal_output_result}" + echo "${correct_result}" Print_Error 'The terminal output has not the expected content.' return 1 fi diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index d765fe7..2a27ed3 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -189,7 +189,7 @@ function __static__Test_Section_Parsing_In_Subshell() Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (config file).' fi if [[ ${section} != 'IC' ]] && [[ ${section} != 'Sampler' ]]; then - if [[ ${HYBRID_software_default_input_file[${section}]} != "${input_file}" ]]; then + if [[ ${HYBRID_software_user_custom_input_file[${section}]} != "${input_file}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (input file).' fi fi From 7e931043d2f5dd1d9fc20769938bbe3e1012630b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Wed, 22 Nov 2023 17:55:05 +0100 Subject: [PATCH 244/549] Fix Hydro unit test --- tests/unit_tests_Hydro_functionality.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 502552b..13fb41a 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -155,7 +155,7 @@ function Unit_Test__Hydro-test-run-software() mkdir -p "${HYBRID_software_output_directory[Hydro]}" local -r hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt"\ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - IC_output_file_path="${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" + IC_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro if [[ ! -f "${hydro_terminal_output}" ]]; then From 35dba9d889d2fcf8c5dc8f73b3a548c3256e4f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 23 Nov 2023 18:16:19 +0100 Subject: [PATCH 245/549] Move custom input logic to sanity checks; fail when custom input is used along input stage --- bash/Afterburner_functionality.bash | 98 ++++--------------- bash/Hydro_functionality.bash | 17 +++- bash/global_variables.bash | 10 ++ bash/sanity_checks.bash | 43 ++++++-- tests/IC/SMASH_IC.dat | 0 tests/functional_tests_Afterburner_only.bash | 52 +++++++++- tests/functional_tests_Hydro_only.bash | 21 ++++ .../unit_tests_Afterburner_functionality.bash | 21 +++- 8 files changed, 169 insertions(+), 93 deletions(-) delete mode 100644 tests/IC/SMASH_IC.dat diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 36ac9d0..15ddc19 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -23,94 +23,34 @@ function Prepare_Software_Input_File_Afterburner() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi - + local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampling0" + if [[ ! -f "${HYBRID_software_input_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Afterburner input file ' --emph "${HYBRID_software_input_file[Afterburner]}"\ + ' does not exist.' + fi if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then - - if [[ "${HYBRID_software_user_custom_input_file[Afterburner]}" = '' ]]; then - if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_software_default_input_file[Afterburner]}" ]]; then - if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Input to Afterburner' --emph "${HYBRID_software_input_file[Afterburner]}"\ - ' does not exist.' - fi - sampled_particle_list="${HYBRID_software_input_file[Afterburner]}" - else - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The input file for the afterburner ' \ - --emph "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_software_default_input_file[Afterburner]}" - ' already exists.' - fi - else - base_file=$(basename "${HYBRID_software_user_custom_input_file[Afterburner]}") - if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/${base_file}" ]]; then - if [[ ! -f "${HYBRID_software_user_custom_input_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Input to Afterburner' --emph "${HYBRID_software_user_custom_input_file[Afterburner]}"\ - ' does not exist.' - fi - sampled_particle_list="${HYBRID_software_user_custom_input_file[Afterburner]}" - else - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The input file for the afterburner ' \ - --emph "${HYBRID_software_output_directory[Afterburner]}/${base_file}" + if [[ -f "${target_link_name}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'The input file for the afterburner ' --emph "${target_link_name}"\ ' already exists.' - fi - - fi - if [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then + elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ ' does not exist which is needed to check number of initial nucleons.' - fi - if [[ "${HYBRID_software_user_custom_input_file[Spectators]}" = '' ]]; then - if [[ ! -f "${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_file[Spectators]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Spectator list ' --emph "${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_file[Spectators]}"\ - ' does not exist.' - fi - spectator_list="${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_file[Spectators]}" - else - if [[ ! -f "${HYBRID_software_user_custom_input_file[Spectators]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Spectator list ' --emph "${HYBRID_software_user_custom_input_file[Spectators]}"\ - ' does not exist.' - fi - spectator_list="${HYBRID_software_user_custom_input_file[Spectators]}" + elif [[ ! -f "${HYBRID_software_input_file[Spectators]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Spectator file ' --emph "${HYBRID_software_input_file[Spectators]}"\ + ' does not exist.' fi "${HYBRID_external_python_scripts[Add_spectators_from_IC]}"\ - '--sampled_particle_list' "${sampled_particle_list}"\ - '--initial_particle_list' "${spectator_list}"\ - '--output_file' "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ + '--sampled_particle_list' "${HYBRID_software_input_file[Afterburner]}" \ + '--initial_particle_list' "${HYBRID_software_input_file[Spectators]}"\ + '--output_file' "${target_link_name}"\ '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" - else - if [[ "${HYBRID_software_user_custom_input_file[Afterburner]}" = '' ]]; then - if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then - if [[ ! -f "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Input to Afterburner' --emph "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" \ - ' does not exist.' - fi - ln -s -f "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" \ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" - else - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_file[Afterburner_without_spectators]}" ' already exists.' - fi - else - if [[ ! -f "${HYBRID_software_user_custom_input_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Input to Afterburner' --emph "${HYBRID_software_user_custom_input_file[Afterburner]}"\ - ' does not exist.' - fi - if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then - ln -s -f "${HYBRID_software_user_custom_input_file[Afterburner]}"\ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" - else - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The input file for the afterburner ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0" ' already exists.' - fi - fi + ln -s "${HYBRID_software_input_file[Afterburner]}" \ + "${target_link_name}" fi } diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 373d582..b978a6c 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -25,6 +25,7 @@ function Prepare_Software_Input_File_Hydro() fi # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. +<<<<<<< HEAD if [[ "${HYBRID_software_user_custom_input_file[Hydro]}" = '' ]]; then if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ]]; then if [[ ! -f "${HYBRID_software_input_file[Hydro]}" ]]; then @@ -86,6 +87,16 @@ function Prepare_Software_Input_File_Hydro() else ln -s "${eos_folder}" "${link_to_eos_folder}" fi +======= + local -r target_link_name="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then + ln -s -f "${HYBRID_software_input_file[Hydro]}" "${target_link_name}" + elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file '\ + --emph "${HYBRID_software_input_file[Hydro]}" ' to be used.' +fi +>>>>>>> f60577c (Move custom input logic to sanity checks; fail when custom input is used along input stage) } function Ensure_All_Needed_Input_Exists_Hydro() @@ -98,9 +109,9 @@ function Ensure_All_Needed_Input_Exists_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi - if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ]]; then + if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'IC output file ' --emph "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ' does not exist.' + 'IC output file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" ' does not exist.' fi } @@ -109,7 +120,7 @@ function Run_Software_Hydro() cd "${HYBRID_software_output_directory[Hydro]}" local -r\ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}"\ + ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat"\ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}"\ "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 5c800e8..fa1891c 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -30,6 +30,13 @@ function Define_Further_Global_Variables() readonly HYBRID_python_folder="${HYBRID_top_level_path}/python" declare -rgA HYBRID_external_python_scripts=( [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" + ) + declare -rgA HYBRID_software_default_input_filename=( + [IC]='' + [Hydro]="SMASH_IC.dat" + [Sampler]="freezeout.dat" + [Spectators]="SMASH_IC.oscar" + [Afterburner]="particle_lists.oscar" ) # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. @@ -58,6 +65,7 @@ function Define_Further_Global_Variables() [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' [Spectators_source]='HYBRID_optional_feature[Spectators_source]' ) +<<<<<<< HEAD declare -rgA HYBRID_software_default_input_file=( [IC]='' [Hydro]="SMASH_IC.dat" @@ -78,6 +86,8 @@ function Define_Further_Global_Variables() [Sampler]='sampler_config.txt' [Afterburner]='afterburner_config.yaml' ) +======= +>>>>>>> f60577c (Move custom input logic to sanity checks; fail when custom input is used along input stage) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 0336a30..30c4606 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -21,18 +21,47 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" # Set here input data file of software if it was not set by user - if [[ ${key} != 'IC' ]] && [[ ${key} != 'Sampler' ]]; then - if [[ "${HYBRID_software_input_file[${key}]}" = '' ]]; then - HYBRID_software_input_file[${key}]="${HYBRID_software_output_directory[${HYBRID_relative_key[${key}]}]}/${HYBRID_software_default_input_file["${key}"]}" + if [[ ${key} =~ ^(Hydro|Afterburner)$ ]]; then + local filename="${HYBRID_software_user_custom_input_file[${key}]}" + if [[ "${filename}" = '' ]]; then + case "${key}" in + Hydro ) + filename="${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_filename[Hydro]}" + ;; + Afterburner ) + filename="${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_filename[Afterburner]}" + ;; + esac else - base_file_input=$(basename "${HYBRID_software_default_input_file[${key}]}") - HYBRID_software_input_file[${key}]="${HYBRID_software_output_directory[${HYBRID_relative_key[${key}]}]}/${base_file_input}" + case "${key}" in + Hydro ) + if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Requesting custom Hydro input although executing IC with default output name.' + fi + ;; + Afterburner ) + if Element_In_Array_Equals_To "Sampler" "${HYBRID_given_software_sections[@]}"; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Requesting custom Afterburner input although executing Sampler with default output name.' + fi + ;; + esac fi + HYBRID_software_input_file[${key}]="${filename}" fi fi done - if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]] && [[ "${HYBRID_optional_feature[Spectators_source]}" != '' ]]; then - HYBRID_software_user_custom_input_file['Spectators']="${HYBRID_optional_feature[Spectators_source]}" + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]];then + if [[ "${HYBRID_optional_feature[Spectators_source]}" != '' ]]; then + HYBRID_software_input_file['Spectators']="${HYBRID_optional_feature[Spectators_source]}" + if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'Requesting custom Spectator input although executing IC with default output name.' + fi + else + HYBRID_software_input_file['Spectators']="${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_filename[Spectators]}" + fi fi readonly HYBRID_software_output_directory HYBRID_software_configuration_file HYBRID_software_input_file } diff --git a/tests/IC/SMASH_IC.dat b/tests/IC/SMASH_IC.dat deleted file mode 100644 index e69de29..0000000 diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 3930761..fba9ac5 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -46,6 +46,7 @@ function Functional_Test__do-Afterburner-only() Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success' + #Test with custom input rm 'Sampler/particle_lists.oscar' mkdir -p test touch 'test/particle_lists_2.oscar' @@ -59,12 +60,39 @@ function Functional_Test__do-Afterburner-only() File_Directory: "." ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file - Print_Info 'Running Hybrid-handler expecting success' + Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-custom-input' + # Expect failure when using custom input while also running the sampler + printf ' + Sampler: + Executable: %s/tests/mocks/sampler_black_box.py + Afterburner: + Executable: %s/tests/mocks/smash_afterburner_black-box.py + Input_file: %s/tests/run_tests/do-Afterburner-only/test/particle_lists_2.oscar + Software_keys: + Modi: + List: + File_Directory: "." + ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + Print_Info 'Running Hybrid-handler expecting failure' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeded.' + return 1 + fi # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' + touch 'Sampler/particle_lists.oscar' + printf ' + Afterburner: + Executable: %s/tests/mocks/smash_afterburner_black-box.py + Software_keys: + Modi: + List: + File_Directory: "." + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" terminal_output_file='Afterburner/Terminal_Output.txt' BLACK_BOX_FAIL='invalid_config'\ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -113,7 +141,7 @@ function Functional_Test__do-Afterburner-only() __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' # Expect success and test the add_spectator functionality with custom spectator input - Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' + Print_Info 'Running Hybrid-handler expecting success with the custom add_spectator option' rm -r 'IC' mkdir 'IC' touch 'IC/config.yaml' @@ -132,4 +160,24 @@ function Functional_Test__do-Afterburner-only() Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' + # Expect failure when combining custom spectator lists and running IC + Print_Info 'Running Hybrid-handler expecting failure with the add_spectator option and IC at the same time' + printf ' + IC: + Executable: %s/tests/mocks/smash_IC_black-box.py + Afterburner: + Executable: %s/tests/mocks/smash_afterburner_black-box.py + Add_spectators_from_IC: TRUE + Spectators_source: %s/tests/run_tests/do-Afterburner-only/test/SMASH_IC_2.oscar + Software_keys: + Modi: + List: + File_Directory: "." + ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}"\ + > "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeded.' + return 1 + fi } diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 5fa9642..66830b2 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -56,8 +56,29 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-succes-custom-input' + #Expect failure with custom input file name while also using IC + printf ' + IC: + Executable: %s/tests/mocks/smash_IC_black-box.py + Hydro: + Executable: %s/tests/mocks/vhlle_black-box.py + Input_file: %s/tests/run_tests/do-Hydro-only/test/input + ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + # Run the hydro stage and check if freezeout is successfully generated + Print_Info 'Running Hybrid-handler expecting failure' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeded.' + return 1 + fi # Expect failure when giving an invalid IC output Print_Info 'Running Hybrid-handler expecting invalid IC argument' + printf ' + Hydro: + Executable: %s/tests/mocks/vhlle_black-box.py + ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + mkdir -p 'IC' + touch 'IC/SMASH_IC.dat' terminal_output_file='Hydro/Terminal_Output.txt' BLACK_BOX_FAIL='invalid_input'\ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index f8060a8..0d80dce 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -65,12 +65,29 @@ function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-fi function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-spectators() { - Make_Test_Preliminary_Operations__Afterburner-create-input-file + + local file_to_be_sourced list_of_files + list_of_files=( + 'Afterburner_functionality.bash' + 'global_variables.bash' + 'software_input_functionality.bash' + 'sanity_checks.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables + HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Afterburner" + HYBRID_software_base_config_file[Afterburner]='my_cool_conf.yaml' + HYBRID_given_software_sections=( 'Afterburner' ) + HYBRID_software_executable[Afterburner]=$(which echo) # Use command as fake executable + HYBRID_optional_feature[Add_spectators_from_IC]='TRUE' + Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables + Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts } function Unit_Test__Afterburner-create-input-file-with-spectators() { - HYBRID_optional_feature[Add_spectators_from_IC]='TRUE' mkdir -p "${HYBRID_software_output_directory[Sampler]}"\ "${HYBRID_software_output_directory[IC]}"\ "${HYBRID_software_output_directory[Afterburner]}" From 08d6dd17defd1c9fc47a9c2df4e012c682f57c5d Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Mon, 27 Nov 2023 15:38:12 +0100 Subject: [PATCH 246/549] Cleanup and reduction of code duplication --- bash/Afterburner_functionality.bash | 25 ++-- bash/Hydro_functionality.bash | 9 +- bash/global_variables.bash | 4 +- bash/sanity_checks.bash | 44 +++---- tests/functional_tests_Afterburner_only.bash | 113 ++++++++---------- tests/functional_tests_Hydro_only.bash | 77 ++++++------ tests/functional_tests_Sampler_only.bash | 21 ---- .../unit_tests_Afterburner_functionality.bash | 52 ++++---- tests/unit_tests_Hydro_functionality.bash | 8 +- tests/unit_tests_configuration.bash | 6 +- 10 files changed, 156 insertions(+), 203 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 15ddc19..5cd61a6 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -24,11 +24,7 @@ function Prepare_Software_Input_File_Afterburner() 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampling0" - if [[ ! -f "${HYBRID_software_input_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Afterburner input file ' --emph "${HYBRID_software_input_file[Afterburner]}"\ - ' does not exist.' - fi + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then if [[ -f "${target_link_name}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ @@ -49,8 +45,15 @@ function Prepare_Software_Input_File_Afterburner() '--output_file' "${target_link_name}"\ '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" else - ln -s "${HYBRID_software_input_file[Afterburner]}" \ - "${target_link_name}" + # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). + # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. + if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then + ln -s -f "${HYBRID_software_input_file[Afterburner]}" "${target_link_name}" + elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file '\ + --emph "${HYBRID_software_input_file[Afterburner]}" ' to be used.' + fi fi } @@ -66,10 +69,12 @@ function Ensure_All_Needed_Input_Exists_Afterburner() ' does not exist.' fi # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary - if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then + if [[ ! -e "${HYBRID_software_input_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ - ' was not found.' + 'The input file ' --emph "${HYBRID_software_input_file[Afterburner]}" ' was not found.' + elif [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then + Print_Internal_And_Exit \ + 'Something went wrong when creating the Afterburner symbolic link.' fi } diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index b978a6c..4f57bbe 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -109,9 +109,12 @@ function Ensure_All_Needed_Input_Exists_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi - if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'IC output file ' --emph "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" ' does not exist.' + if [[ ! -e "${HYBRID_software_input_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The input file ' --emph "${HYBRID_software_input_file[Hydro]}" ' was not found.' + elif [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then + Print_Internal_And_Exit \ + 'Something went wrong when creating the Afterburner symbolic link.' fi } diff --git a/bash/global_variables.bash b/bash/global_variables.bash index fa1891c..eb9c664 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -34,7 +34,7 @@ function Define_Further_Global_Variables() declare -rgA HYBRID_software_default_input_filename=( [IC]='' [Hydro]="SMASH_IC.dat" - [Sampler]="freezeout.dat" + [Sampler]="freezeout.dat" # Not used at the moment for how the sampler works [Spectators]="SMASH_IC.oscar" [Afterburner]="particle_lists.oscar" ) @@ -101,7 +101,9 @@ function Define_Further_Global_Variables() [Afterburner]='' ) declare -gA HYBRID_software_user_custom_input_file=( + [IC]='' [Hydro]='' + [Sampler]='' [Spectators]='' [Afterburner]='' ) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 30c4606..7095513 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -22,31 +22,24 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" # Set here input data file of software if it was not set by user if [[ ${key} =~ ^(Hydro|Afterburner)$ ]]; then - local filename="${HYBRID_software_user_custom_input_file[${key}]}" + local filename relative_key + filename="${HYBRID_software_user_custom_input_file[${key}]}" + case "${key}" in + Hydro ) + relative_key='IC' + ;; + Afterburner ) + relative_key='Sampler' + ;; + esac if [[ "${filename}" = '' ]]; then - case "${key}" in - Hydro ) - filename="${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_filename[Hydro]}" - ;; - Afterburner ) - filename="${HYBRID_software_output_directory[Sampler]}/${HYBRID_software_default_input_filename[Afterburner]}" - ;; - esac + filename="${HYBRID_software_output_directory[${relative_key}]}/${HYBRID_software_default_input_filename[${key}]}" else - case "${key}" in - Hydro ) - if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Requesting custom Hydro input although executing IC with default output name.' - fi - ;; - Afterburner ) - if Element_In_Array_Equals_To "Sampler" "${HYBRID_given_software_sections[@]}"; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Requesting custom Afterburner input although executing Sampler with default output name.' - fi - ;; - esac + if Element_In_Array_Equals_To "${relative_key}" "${HYBRID_given_software_sections[@]}"; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'Requesting custom ' --emph "${key}" ' input file although executing ' \ + --emph "${relative_key}" ' with default output name.' + fi fi HYBRID_software_input_file[${key}]="${filename}" fi @@ -56,8 +49,9 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var if [[ "${HYBRID_optional_feature[Spectators_source]}" != '' ]]; then HYBRID_software_input_file['Spectators']="${HYBRID_optional_feature[Spectators_source]}" if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Requesting custom Spectator input although executing IC with default output name.' + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'Requesting custom ' --emph 'Spectators' ' input file although executing ' \ + --emph 'IC' ' with default output name.' fi else HYBRID_software_input_file['Spectators']="${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_filename[Spectators]}" diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index fba9ac5..d7f9bea 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -46,53 +46,8 @@ function Functional_Test__do-Afterburner-only() Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success' - #Test with custom input - rm 'Sampler/particle_lists.oscar' - mkdir -p test - touch 'test/particle_lists_2.oscar' - printf ' - Afterburner: - Executable: %s/tests/mocks/smash_afterburner_black-box.py - Input_file: %s/tests/run_tests/do-Afterburner-only/test/particle_lists_2.oscar - Software_keys: - Modi: - List: - File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" - # Expect success and test absence of "SMASH" unfinished file - Print_Info 'Running Hybrid-handler expecting failure' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - __static__Check_Successful_Handler_Run $? || return 1 - mv 'Afterburner' 'Afterburner-success-custom-input' - # Expect failure when using custom input while also running the sampler - printf ' - Sampler: - Executable: %s/tests/mocks/sampler_black_box.py - Afterburner: - Executable: %s/tests/mocks/smash_afterburner_black-box.py - Input_file: %s/tests/run_tests/do-Afterburner-only/test/particle_lists_2.oscar - Software_keys: - Modi: - List: - File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" - Print_Info 'Running Hybrid-handler expecting failure' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -eq 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeded.' - return 1 - fi # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' - touch 'Sampler/particle_lists.oscar' - printf ' - Afterburner: - Executable: %s/tests/mocks/smash_afterburner_black-box.py - Software_keys: - Modi: - List: - File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" terminal_output_file='Afterburner/Terminal_Output.txt' BLACK_BOX_FAIL='invalid_config'\ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -109,7 +64,7 @@ function Functional_Test__do-Afterburner-only() return 1 fi mv 'Afterburner' 'Afterburner-invalid-config' - # Expect failure and test "SMASH" unfinished/lock files + # Expect failure and test "SMASH" unfinished/lock files Print_Info 'Running Hybrid-handler expecting crash in Afterburner software' BLACK_BOX_FAIL='smash_crashes'\ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -123,11 +78,46 @@ function Functional_Test__do-Afterburner-only() return 1 fi mv 'Afterburner' 'Afterburner-software-crash' + #Test with custom input + rm 'Sampler/particle_lists.oscar' + mkdir -p test + touch 'test/particle_lists_2.oscar' + printf ' + Afterburner: + Executable: %s/mocks/smash_afterburner_black-box.py + Input_file: %s/test/particle_lists_2.oscar + Software_keys: + Modi: + List: + File_Directory: "." + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + # Expect success and test absence of "SMASH" unfinished file + Print_Info 'Running Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + __static__Check_Successful_Handler_Run $? || return 1 + mv 'Afterburner' 'Afterburner-success-custom-input' + # Expect failure when using custom input while also running the sampler + printf ' + Sampler: + Executable: %s/mocks/sampler_black_box.py + Afterburner: + Executable: %s/mocks/smash_afterburner_black-box.py + Input_file: %s/test/particle_lists_2.oscar + Software_keys: + Modi: + List: + File_Directory: "." + ' "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + Print_Info 'Running Hybrid-handler expecting failure' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 110 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeded.' + return 1 + fi # Expect success and test the add_spectator functionality Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' mkdir 'IC' - touch 'IC/config.yaml' 'IC/SMASH_IC.oscar' - touch 'Sampler/particle_lists.oscar' + touch 'IC/config.yaml' 'IC/SMASH_IC.oscar' 'Sampler/particle_lists.oscar' printf ' Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py @@ -142,21 +132,19 @@ function Functional_Test__do-Afterburner-only() mv 'Afterburner' 'Afterburner-success-with-spectators' # Expect success and test the add_spectator functionality with custom spectator input Print_Info 'Running Hybrid-handler expecting success with the custom add_spectator option' - rm -r 'IC' - mkdir 'IC' - touch 'IC/config.yaml' + rm -r "IC"/* mkdir -p test - touch 'test/SMASH_IC_2.oscar' + touch 'test/SMASH_IC_2.oscar' 'IC/config.yaml' printf ' Afterburner: - Executable: %s/tests/mocks/smash_afterburner_black-box.py + Executable: %s/mocks/smash_afterburner_black-box.py Add_spectators_from_IC: TRUE - Spectators_source: %s/tests/run_tests/do-Afterburner-only/test/SMASH_IC_2.oscar + Spectators_source: %s/test/SMASH_IC_2.oscar Software_keys: Modi: List: File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' @@ -164,19 +152,22 @@ function Functional_Test__do-Afterburner-only() Print_Info 'Running Hybrid-handler expecting failure with the add_spectator option and IC at the same time' printf ' IC: - Executable: %s/tests/mocks/smash_IC_black-box.py + Executable: %s/mocks/smash_IC_black-box.py + Hydro: + Executable: %s/mocks/smash_IC_black-box.py + Sampler: + Executable: %s/mocks/smash_IC_black-box.py Afterburner: - Executable: %s/tests/mocks/smash_afterburner_black-box.py + Executable: %s/mocks/smash_afterburner_black-box.py Add_spectators_from_IC: TRUE - Spectators_source: %s/tests/run_tests/do-Afterburner-only/test/SMASH_IC_2.oscar + Spectators_source: %s/test/SMASH_IC_2.oscar Software_keys: Modi: List: File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}"\ - > "${config_filename}" + ' "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -eq 0 ]]; then + if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler unexpectedly succeded.' return 1 fi diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 66830b2..4847cdf 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -34,12 +34,30 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-success' + # Expect failure when giving an invalid IC output + Print_Info 'Running Hybrid-handler expecting invalid IC argument' + terminal_output_file='Hydro/Terminal_Output.txt' + BLACK_BOX_FAIL='invalid_input'\ + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -eq 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input for Hydro.' + return 1 + elif [[ ! -f "${terminal_output_file}" ]]; then + Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' + return 1 + fi + failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') + if [[ "${failure_message}" != 'I/O error with '* ]]; then + Print_Error 'Unexpected failure message: ' --emph "${failure_message}" + return 1 + fi + mv 'Hydro' 'Hydro-invalid-input' #Expect sucess with custom input file name printf ' Hydro: - Executable: %s/tests/mocks/vhlle_black-box.py - Input_file: %s/tests/run_tests/do-Hydro-only/test/input - ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + Executable: %s/mocks/vhlle_black-box.py + Input_file: %s/test/input + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated rm 'IC/SMASH_IC.dat' mkdir -p test @@ -56,45 +74,6 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-succes-custom-input' - #Expect failure with custom input file name while also using IC - printf ' - IC: - Executable: %s/tests/mocks/smash_IC_black-box.py - Hydro: - Executable: %s/tests/mocks/vhlle_black-box.py - Input_file: %s/tests/run_tests/do-Hydro-only/test/input - ' "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" - # Run the hydro stage and check if freezeout is successfully generated - Print_Info 'Running Hybrid-handler expecting failure' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -eq 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeded.' - return 1 - fi - # Expect failure when giving an invalid IC output - Print_Info 'Running Hybrid-handler expecting invalid IC argument' - printf ' - Hydro: - Executable: %s/tests/mocks/vhlle_black-box.py - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" - mkdir -p 'IC' - touch 'IC/SMASH_IC.dat' - terminal_output_file='Hydro/Terminal_Output.txt' - BLACK_BOX_FAIL='invalid_input'\ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -eq 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input for Hydro.' - return 1 - elif [[ ! -f "${terminal_output_file}" ]]; then - Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' - return 1 - fi - failure_message=$(tail -n 1 "${terminal_output_file}" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') - if [[ "${failure_message}" != 'I/O error with '* ]]; then - Print_Error 'Unexpected failure message: ' --emph "${failure_message}" - return 1 - fi - mv 'Hydro' 'Hydro-invalid-input' # Expect failure when an invalid config was supplied Print_Info 'Running Hybrid-handler expecting invalid config argument' terminal_output_file='Hydro/Terminal_Output.txt' @@ -127,4 +106,18 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-crash' + #Expect failure with custom input file name while also using IC + printf ' + IC: + Executable: %s/mocks/smash_IC_black-box.py + Hydro: + Executable: %s/mocks/vhlle_black-box.py + Input_file: %s/test/input + ' "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + Print_Info 'Running Hybrid-handler expecting failure' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 110 ]]; then + Print_Error 'Hybrid-handler unexpectedly succeded.' + return 1 + fi } diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index c5cabc6..e41c782 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -31,27 +31,6 @@ function Functional_Test__do-Sampler-only() return 1 fi mv 'Sampler' 'Sampler-success' - rm 'Hydro/freezeout.dat' - touch 'Hydro/freezeout_2.dat' - printf ' - Sampler: - Executable: %s/tests/mocks/sampler_black_box.py - Software_keys: - surface: ../Hydro/freezeout_2.dat - ' "${HYBRIDT_repository_top_level_path}" > "${hybrid_handler_config}" - # Expect success and test presence of output files - Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" - if [[ $? -ne 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly failed.' - return 1 - fi - output_files=( Sampler/* ) - if [[ ${#output_files[@]} -ne 4 ]]; then - Print_Error 'Expected ' --emph '4' " output files, but ${#output_files[@]} found." - return 1 - fi - mv 'Sampler' 'Sampler-success-custom-input' # Expect failure and test terminal output local terminal_output_file error_message terminal_output_file='Sampler/Terminal_Output.txt' diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 0d80dce..8cf3318 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -7,7 +7,7 @@ # #=================================================== -function Make_Test_Preliminary_Operations__Afterburner-create-input-file() +__static__Do_Preliminary_Setup_Operations() { local file_to_be_sourced list_of_files list_of_files=( @@ -24,8 +24,15 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() HYBRID_software_base_config_file[Afterburner]='my_cool_conf.yaml' HYBRID_given_software_sections=( 'Afterburner' ) HYBRID_software_executable[Afterburner]=$(which echo) # Use command as fake executable +} + +function Make_Test_Preliminary_Operations__Afterburner-create-input-file() +{ + __static__Do_Preliminary_Setup_Operations + HYBRID_optional_feature[Add_spectators_from_IC]='FALSE' Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts + } function Unit_Test__Afterburner-create-input-file() @@ -36,7 +43,7 @@ function Unit_Test__Afterburner-create-input-file() plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${plist_Sampler}" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then Print_Error 'The input file was not properly created in the output folder.' return 1 @@ -45,16 +52,11 @@ function Unit_Test__Afterburner-create-input-file() return 1 fi Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null - if [[ $? -eq 0 ]]; then + if [[ $? -ne 110 ]]; then Print_Error 'Preparation of input with existent config succeeded.' return 1 fi rm -r "${HYBRID_output_directory}/"* - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null - if [[ $? -eq 0 ]]; then - Print_Error 'Preparation of input succeeded even though the particle_list.oscar does not exist.' - return 1 - fi } function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file() @@ -65,22 +67,7 @@ function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-fi function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-spectators() { - - local file_to_be_sourced list_of_files - list_of_files=( - 'Afterburner_functionality.bash' - 'global_variables.bash' - 'software_input_functionality.bash' - 'sanity_checks.bash' - ) - for file_to_be_sourced in "${list_of_files[@]}"; do - source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} - done - Define_Further_Global_Variables - HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Afterburner" - HYBRID_software_base_config_file[Afterburner]='my_cool_conf.yaml' - HYBRID_given_software_sections=( 'Afterburner' ) - HYBRID_software_executable[Afterburner]=$(which echo) # Use command as fake executable + __static__Do_Preliminary_Setup_Operations HYBRID_optional_feature[Add_spectators_from_IC]='TRUE' Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts @@ -97,20 +84,20 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null - if [[ $? -eq 0 ]]; then + if [[ $? -ne 110 ]]; then Print_Error 'Preparation succeeded even though the final particle list already exists.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null - if [[ $? -eq 0 ]]; then + if [[ $? -ne 110 ]]; then Print_Error 'Preparation succeeded even though the config.yaml of the IC does not exist.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* touch "${HYBRID_software_output_directory[IC]}/config.yaml" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null - if [[ $? -eq 0 ]]; then + if [[ $? -ne 11ß ]]; then Print_Error 'Preparation succeeded even though the SMASH_IC.oscar does not exist.' return 1 fi @@ -141,6 +128,7 @@ function Unit_Test__Afterburner-check-all-input() return 1 fi mkdir -p "${HYBRID_software_output_directory[Afterburner]}" + mkdir -p "${HYBRID_software_output_directory[Sampler]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing config file succeeded.' @@ -152,14 +140,16 @@ function Unit_Test__Afterburner-check-all-input() Print_Error 'Ensuring existence of auxiliary input data file succeeded.' return 1 fi - touch "${HYBRID_software_output_directory[Afterburner]}/sampling0" - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner + touch "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" + ln -s -f "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ + "${HYBRID_software_output_directory[Afterburner]}/sampling0" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -ne 0 ]]; then - Print_Error 'Ensuring existence of existing folder/file unexpectedly failed.' + Print_Error 'Ensuring existence of existing folder/file unexpectedly failed,'\ + ' although all files were provided.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/sampling0" - mkdir "${HYBRID_software_output_directory[Sampler]}" touch "${HYBRID_software_output_directory[Sampler]}/original_sampling0" ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampling0"\ "${HYBRID_software_output_directory[Afterburner]}/sampling0" diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 13fb41a..151b770 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -52,7 +52,7 @@ function Unit_Test__Hydro-create-input-file() return 1 fi Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null - if [[ $? -eq 0 ]]; then + if [[ $? -ne 110 ]]; then Print_Error 'Preparation of input with existent config succeeded.' return 1 fi @@ -120,13 +120,13 @@ function Unit_Test__Hydro-check-all-input() fi touch "${HYBRID_software_configuration_file[Hydro]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null - if [[ $? -eq 0 ]]; then + if [[$? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing link to IC file succeeded.' return 1 fi ln -s 'not-existing-target' "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null - if [[ $? -eq 0 ]]; then + if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of broken link to IC file succeeded.' return 1 fi @@ -165,8 +165,6 @@ function Unit_Test__Hydro-test-run-software() terminal_output_result=$(< "${hydro_terminal_output}") correct_result="-params ${Hydro_config_file_path} -ISinput ${IC_output_file_path} -outputDir ${HYBRID_software_output_directory[Hydro]}" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then - echo "${terminal_output_result}" - echo "${correct_result}" Print_Error 'The terminal output has not the expected content.' return 1 fi diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 2a27ed3..80742f1 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -188,10 +188,8 @@ function __static__Test_Section_Parsing_In_Subshell() if [[ ${HYBRID_software_base_config_file[${section}]} != "${config_file}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (config file).' fi - if [[ ${section} != 'IC' ]] && [[ ${section} != 'Sampler' ]]; then - if [[ ${HYBRID_software_user_custom_input_file[${section}]} != "${input_file}" ]]; then - Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (input file).' - fi + if [[ ${HYBRID_software_user_custom_input_file[${section}]} != "${input_file}" ]]; then + Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (input file).' fi if [[ ${HYBRID_software_new_input_keys[${section}]} != "${new_keys}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (software keys).' From 77e97b22cb5990b501e84c1aee18286787de9c93 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Mon, 27 Nov 2023 16:38:03 +0100 Subject: [PATCH 247/549] Cleanup after rebase --- bash/Hydro_functionality.bash | 11 ---------- bash/global_variables.bash | 3 --- bash/sanity_checks.bash | 2 +- tests/functional_tests_Afterburner_only.bash | 2 +- tests/functional_tests_Hydro_only.bash | 20 +++++++++---------- .../unit_tests_Afterburner_functionality.bash | 2 +- 6 files changed, 13 insertions(+), 27 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 4f57bbe..3db9858 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -25,7 +25,6 @@ function Prepare_Software_Input_File_Hydro() fi # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. -<<<<<<< HEAD if [[ "${HYBRID_software_user_custom_input_file[Hydro]}" = '' ]]; then if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ]]; then if [[ ! -f "${HYBRID_software_input_file[Hydro]}" ]]; then @@ -87,16 +86,6 @@ function Prepare_Software_Input_File_Hydro() else ln -s "${eos_folder}" "${link_to_eos_folder}" fi -======= - local -r target_link_name="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" - if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then - ln -s -f "${HYBRID_software_input_file[Hydro]}" "${target_link_name}" - elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file '\ - --emph "${HYBRID_software_input_file[Hydro]}" ' to be used.' -fi ->>>>>>> f60577c (Move custom input logic to sanity checks; fail when custom input is used along input stage) } function Ensure_All_Needed_Input_Exists_Hydro() diff --git a/bash/global_variables.bash b/bash/global_variables.bash index eb9c664..413fe26 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -65,7 +65,6 @@ function Define_Further_Global_Variables() [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' [Spectators_source]='HYBRID_optional_feature[Spectators_source]' ) -<<<<<<< HEAD declare -rgA HYBRID_software_default_input_file=( [IC]='' [Hydro]="SMASH_IC.dat" @@ -86,8 +85,6 @@ function Define_Further_Global_Variables() [Sampler]='sampler_config.txt' [Afterburner]='afterburner_config.yaml' ) -======= ->>>>>>> f60577c (Move custom input logic to sanity checks; fail when custom input is used along input stage) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 7095513..ee55207 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -80,7 +80,7 @@ function __static__Ensure_Executable_Exists() elif [[ "${executable}" != / ]]; then if [[ ! -f "${executable}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run was not found.' + 'The executable file for the ' --emph "${label}" ' run was not found.' elif [[ ! -x "${executable}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The executable file for the ' --emph "${label}" ' run is not executable.' diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index d7f9bea..74e22ac 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -168,7 +168,7 @@ function Functional_Test__do-Afterburner-only() ' "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeded.' + Print_Error 'Hybrid-handler unexpectedly succeeded.' return 1 fi } diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 4847cdf..3cf4599 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -52,12 +52,12 @@ function Functional_Test__do-Hydro-only() return 1 fi mv 'Hydro' 'Hydro-invalid-input' - #Expect sucess with custom input file name + #Expect success with custom input file name printf ' Hydro: - Executable: %s/mocks/vhlle_black-box.py + Executable: %s/vhlle_black-box.py Input_file: %s/test/input - ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "$(pwd)" "$(pwd)" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated rm 'IC/SMASH_IC.dat' mkdir -p test @@ -69,11 +69,11 @@ function Functional_Test__do-Hydro-only() return 1 fi output_files=( Hydro/* ) - if [[ ${#output_files[@]} -ne 4 ]]; then - Print_Error 'Expected ' --emph '4' " output files, but ${#output_files[@]} found." + if [[ ${#output_files[@]} -ne 5 ]]; then + Print_Error 'Expected ' --emph '5' " output files, but ${#output_files[@]} found." return 1 fi - mv 'Hydro' 'Hydro-succes-custom-input' + mv 'Hydro' 'Hydro-success-custom-input' # Expect failure when an invalid config was supplied Print_Info 'Running Hybrid-handler expecting invalid config argument' terminal_output_file='Hydro/Terminal_Output.txt' @@ -109,15 +109,15 @@ function Functional_Test__do-Hydro-only() #Expect failure with custom input file name while also using IC printf ' IC: - Executable: %s/mocks/smash_IC_black-box.py + Executable: echo Hydro: - Executable: %s/mocks/vhlle_black-box.py + Executable: %s/vhlle_black-box.py Input_file: %s/test/input - ' "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "$(pwd)" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeded.' + Print_Error 'Hybrid-handler unexpectedly succeeded.' return 1 fi } diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 8cf3318..d1457fc 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -97,7 +97,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() rm "${HYBRID_software_output_directory[Afterburner]}/"* touch "${HYBRID_software_output_directory[IC]}/config.yaml" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null - if [[ $? -ne 11ß ]]; then + if [[ $? -ne 110 ]]; then Print_Error 'Preparation succeeded even though the SMASH_IC.oscar does not exist.' return 1 fi From 37b39831f3ead6d772bf7aa1cf6987ba989b8731 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Mon, 27 Nov 2023 16:38:35 +0100 Subject: [PATCH 248/549] Bugfix: detection executables had wrong condition --- bash/sanity_checks.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index ee55207..0d25a19 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -77,7 +77,7 @@ function __static__Ensure_Executable_Exists() if [[ "${executable}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ 'Software executable for ' --emph "${label}" ' run was not specified.' - elif [[ "${executable}" != / ]]; then + elif [[ "${executable}" = / ]]; then if [[ ! -f "${executable}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ 'The executable file for the ' --emph "${label}" ' run was not found.' From cf8dce3057f18f0cadce308732fb170cf40fe561 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Mon, 27 Nov 2023 16:40:47 +0100 Subject: [PATCH 249/549] Replace not needed executables by echo --- tests/functional_tests_Afterburner_only.bash | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 74e22ac..3439534 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -99,7 +99,7 @@ function Functional_Test__do-Afterburner-only() # Expect failure when using custom input while also running the sampler printf ' Sampler: - Executable: %s/mocks/sampler_black_box.py + Executable: echo Afterburner: Executable: %s/mocks/smash_afterburner_black-box.py Input_file: %s/test/particle_lists_2.oscar @@ -107,7 +107,7 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then @@ -152,11 +152,11 @@ function Functional_Test__do-Afterburner-only() Print_Info 'Running Hybrid-handler expecting failure with the add_spectator option and IC at the same time' printf ' IC: - Executable: %s/mocks/smash_IC_black-box.py + Executable: echo Hydro: - Executable: %s/mocks/smash_IC_black-box.py + Executable: echo Sampler: - Executable: %s/mocks/smash_IC_black-box.py + Executable: echo Afterburner: Executable: %s/mocks/smash_afterburner_black-box.py Add_spectators_from_IC: TRUE @@ -165,7 +165,7 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded.' From f980310801037d1f0d532326d7a62c688e3bbc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 28 Nov 2023 14:08:59 +0100 Subject: [PATCH 250/549] Restore after rebase; improve error messages in tests --- bash/Afterburner_functionality.bash | 19 ++++---- bash/Hydro_functionality.bash | 46 +++++-------------- bash/global_variables.bash | 16 +------ bash/sanity_checks.bash | 4 +- tests/functional_tests_Afterburner_only.bash | 2 +- tests/functional_tests_Hydro_only.bash | 2 +- .../unit_tests_Afterburner_functionality.bash | 18 +++----- tests/unit_tests_Hydro_functionality.bash | 4 +- 8 files changed, 35 insertions(+), 76 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 5cd61a6..a1a98a1 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -24,16 +24,18 @@ function Prepare_Software_Input_File_Afterburner() 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampling0" - if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then if [[ -f "${target_link_name}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'The input file for the afterburner ' --emph "${target_link_name}"\ ' already exists.' + # Here the config.yaml file is expected to be produced by SMASH in the output folder + # during the IC run. It is used to determine the initial number of particles. elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ - ' does not exist which is needed to check number of initial nucleons.' + '\ndoes not exist, but is needed to check number of initial nucleons.' \ + 'This file is expected to be produced by the IC software run.' elif [[ ! -f "${HYBRID_software_input_file[Spectators]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'Spectator file ' --emph "${HYBRID_software_input_file[Spectators]}"\ @@ -45,13 +47,11 @@ function Prepare_Software_Input_File_Afterburner() '--output_file' "${target_link_name}"\ '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" else - # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). - # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then ln -s -f "${HYBRID_software_input_file[Afterburner]}" "${target_link_name}" elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file '\ + 'File ' --emph "${target_link_name}" ' exists but it is not the Afterburner input file '\ --emph "${HYBRID_software_input_file[Afterburner]}" ' to be used.' fi fi @@ -68,10 +68,11 @@ function Ensure_All_Needed_Input_Exists_Afterburner() 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}"\ ' does not exist.' fi - # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary - if [[ ! -e "${HYBRID_software_input_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_input_file[Afterburner]}" ' was not found.' + # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary. + if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ + ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then Print_Internal_And_Exit \ 'Something went wrong when creating the Afterburner symbolic link.' diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 3db9858..3a4c2af 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -25,38 +25,15 @@ function Prepare_Software_Input_File_Hydro() fi # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. - if [[ "${HYBRID_software_user_custom_input_file[Hydro]}" = '' ]]; then - if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ]]; then - if [[ ! -f "${HYBRID_software_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Input to Hydro' --emph "${HYBRID_software_input_file[Hydro]}"\ - ' does not exist.' - fi - ln -s -f "${HYBRID_software_input_file[Hydro]}"\ - "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" - else - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The input file for Hydro ' --emph \ - "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}"\ - ' already exists.' - fi - else - if [[ ! -f "${HYBRID_software_user_custom_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'Input to Hydro' --emph "${HYBRID_software_user_custom_input_file[Hydro]}"\ - ' does not exist.' - fi - if [[ ! -e "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" ]]; then - ln -s -f "${HYBRID_software_user_custom_input_file[Hydro]}"\ - "${HYBRID_software_output_directory[Hydro]}/${HYBRID_software_default_input_file[Hydro]}" - else - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The input file for Hydro ' --emph \ - "${HYBRID_software_output_directory[Hydro]}/${base_file}"\ - ' already exists.' - fi + local -r target_link_name="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then + ln -s -f "${HYBRID_software_input_file[Hydro]}" "${target_link_name}" + elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Hydro]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file '\ + --emph "${HYBRID_software_input_file[Hydro]}" ' to be used.' fi - # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software + # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software # folder. The user-specified software executable is guaranteed to be either a command name # or a global path and in both cases 'type -P' is expected to succeed and print a global path. local eos_folder @@ -99,11 +76,12 @@ function Ensure_All_Needed_Input_Exists_Hydro() 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi if [[ ! -e "${HYBRID_software_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_input_file[Hydro]}" ' was not found.' + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The input file ' --emph "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat"\ + ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then Print_Internal_And_Exit \ - 'Something went wrong when creating the Afterburner symbolic link.' + 'Something went wrong when creating the Hydro symbolic link.' fi } diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 413fe26..e2d6587 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -65,21 +65,7 @@ function Define_Further_Global_Variables() [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' [Spectators_source]='HYBRID_optional_feature[Spectators_source]' ) - declare -rgA HYBRID_software_default_input_file=( - [IC]='' - [Hydro]="SMASH_IC.dat" - [Sampler]="freezeout.dat" - [Spectators]="SMASH_IC.oscar" - [Afterburner_without_spectators]="particle_lists.oscar" - [Afterburner]="sampling0" - ) - declare -rgA HYBRID_relative_key=( - [Hydro]="IC" - [Sampler]="Hydro" - [Spectators]="IC" - [Afterburner]="Afterburner" - ) - declare -rgA HYBRID_software_input_filename=( + declare -rgA HYBRID_software_configuration_filename=( [IC]='IC_config.yaml' [Hydro]='hydro_config.txt' [Sampler]='sampler_config.txt' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 0d25a19..7c6127d 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -17,9 +17,7 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then __static__Ensure_Executable_Exists "${key}" printf -v HYBRID_software_configuration_file[${key}] \ - "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_input_filename[${key}]}" - base_file=$(basename "${HYBRID_software_base_config_file[${key}]}") - HYBRID_software_configuration_file[${key}]="${HYBRID_software_output_directory[${key}]}/${base_file}" + "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_configuration_filename[${key}]}" # Set here input data file of software if it was not set by user if [[ ${key} =~ ^(Hydro|Afterburner)$ ]]; then local filename relative_key diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 3439534..78125ef 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -168,7 +168,7 @@ function Functional_Test__do-Afterburner-only() ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeeded.' + Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi } diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 3cf4599..f6eb4f0 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -117,7 +117,7 @@ function Functional_Test__do-Hydro-only() Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeeded.' + Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi } diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index d1457fc..c275523 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -53,10 +53,9 @@ function Unit_Test__Afterburner-create-input-file() fi Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then - Print_Error 'Preparation of input with existent config succeeded.' + Print_Error 'Preparation of input with existent config did not fail as expected.' return 1 fi - rm -r "${HYBRID_output_directory}/"* } function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file() @@ -85,20 +84,20 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then - Print_Error 'Preparation succeeded even though the final particle list already exists.' + Print_Error 'Preparation did not fail with exit code 110 even though the final particle list already exists.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then - Print_Error 'Preparation succeeded even though the config.yaml of the IC does not exist.' + Print_Error 'Preparation did not fail with exit code 110 even though the config.yaml of the IC does not exist.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* touch "${HYBRID_software_output_directory[IC]}/config.yaml" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then - Print_Error 'Preparation succeeded even though the SMASH_IC.oscar does not exist.' + Print_Error 'Preparation did not fail with exit code 110 even though the SMASH_IC.oscar does not exist.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* @@ -127,8 +126,7 @@ function Unit_Test__Afterburner-check-all-input() Print_Error 'Ensuring existence of not-existing output directory succeeded.' return 1 fi - mkdir -p "${HYBRID_software_output_directory[Afterburner]}" - mkdir -p "${HYBRID_software_output_directory[Sampler]}" + mkdir -p "${HYBRID_software_output_directory[Afterburner]}" "${HYBRID_software_output_directory[Sampler]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing config file succeeded.' @@ -140,10 +138,8 @@ function Unit_Test__Afterburner-check-all-input() Print_Error 'Ensuring existence of auxiliary input data file succeeded.' return 1 fi - touch "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" - ln -s -f "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null + touch "${HYBRID_software_output_directory[Afterburner]}/sampling0" + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing folder/file unexpectedly failed,'\ ' although all files were provided.' diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 151b770..5864f79 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -53,7 +53,7 @@ function Unit_Test__Hydro-create-input-file() fi Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro &> /dev/null if [[ $? -ne 110 ]]; then - Print_Error 'Preparation of input with existent config succeeded.' + Print_Error 'Preparation of input with existent did not fail with exit code 110 as expected.' return 1 fi rm -r "${HYBRID_software_output_directory[Hydro]}"/* @@ -120,7 +120,7 @@ function Unit_Test__Hydro-check-all-input() fi touch "${HYBRID_software_configuration_file[Hydro]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null - if [[$? -eq 0 ]]; then + if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing link to IC file succeeded.' return 1 fi From f187195a7bbb41dbcf3700159333c87486d67564 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 29 Nov 2023 09:40:42 +0100 Subject: [PATCH 251/549] Fix some formatting and do some rephrasing --- bash/Afterburner_functionality.bash | 6 ++--- bash/Hydro_functionality.bash | 4 +-- bash/global_variables.bash | 14 +++++------ bash/sanity_checks.bash | 25 +++++++++++++------ tests/functional_tests_Afterburner_only.bash | 6 ++--- .../unit_tests_Afterburner_functionality.bash | 24 +++++++++++------- tests/unit_tests_Hydro_functionality.bash | 4 +-- 7 files changed, 48 insertions(+), 35 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index a1a98a1..e4c0dc9 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -70,9 +70,9 @@ function Ensure_All_Needed_Input_Exists_Afterburner() fi # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary. if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ - ' was not found.' + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ + ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then Print_Internal_And_Exit \ 'Something went wrong when creating the Afterburner symbolic link.' diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 3a4c2af..b013073 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -33,7 +33,7 @@ function Prepare_Software_Input_File_Hydro() 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file '\ --emph "${HYBRID_software_input_file[Hydro]}" ' to be used.' fi - # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software + # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software # folder. The user-specified software executable is guaranteed to be either a command name # or a global path and in both cases 'type -P' is expected to succeed and print a global path. local eos_folder @@ -50,7 +50,7 @@ function Prepare_Software_Input_File_Hydro() '\npointing to a different eos folder. Unlink and link again!\n' unlink "${HYBRID_software_output_directory[Hydro]}/eos" ln -s "${eos_folder}" "${link_to_eos_folder}" - else + else exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ 'A ' --emph 'eos' ' folder called already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ '.' 'Please remove it and run the hybrid handler again.' diff --git a/bash/global_variables.bash b/bash/global_variables.bash index e2d6587..932d6e7 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -34,10 +34,16 @@ function Define_Further_Global_Variables() declare -rgA HYBRID_software_default_input_filename=( [IC]='' [Hydro]="SMASH_IC.dat" - [Sampler]="freezeout.dat" # Not used at the moment for how the sampler works + [Sampler]="freezeout.dat" # Not used at the moment for how the sampler works [Spectators]="SMASH_IC.oscar" [Afterburner]="particle_lists.oscar" ) + declare -rgA HYBRID_software_configuration_filename=( + [IC]='IC_config.yaml' + [Hydro]='hydro_config.txt' + [Sampler]='sampler_config.txt' + [Afterburner]='afterburner_config.yaml' + ) # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=() @@ -65,12 +71,6 @@ function Define_Further_Global_Variables() [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' [Spectators_source]='HYBRID_optional_feature[Spectators_source]' ) - declare -rgA HYBRID_software_configuration_filename=( - [IC]='IC_config.yaml' - [Hydro]='hydro_config.txt' - [Sampler]='sampler_config.txt' - [Afterburner]='afterburner_config.yaml' - ) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 7c6127d..57fb948 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -31,7 +31,9 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var ;; esac if [[ "${filename}" = '' ]]; then - filename="${HYBRID_software_output_directory[${relative_key}]}/${HYBRID_software_default_input_filename[${key}]}" + printf -v filename '%s/%s' \ + "${HYBRID_software_output_directory[${relative_key}]}" \ + "${HYBRID_software_default_input_filename[${key}]}" else if Element_In_Array_Equals_To "${relative_key}" "${HYBRID_given_software_sections[@]}"; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ @@ -50,12 +52,17 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Requesting custom ' --emph 'Spectators' ' input file although executing ' \ --emph 'IC' ' with default output name.' - fi + fi else - HYBRID_software_input_file['Spectators']="${HYBRID_software_output_directory[IC]}/${HYBRID_software_default_input_filename[Spectators]}" + printf -v HYBRID_software_input_file[Spectators] '%s/%s' \ + "${HYBRID_software_output_directory[IC]}" \ + "${HYBRID_software_default_input_filename[Spectators]}" fi fi - readonly HYBRID_software_output_directory HYBRID_software_configuration_file HYBRID_software_input_file + readonly \ + HYBRID_software_output_directory \ + HYBRID_software_configuration_file \ + HYBRID_software_input_file } function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() @@ -65,7 +72,7 @@ function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() exit_code=${HYBRID_fatal_file_not_found} Print_Internal_And_Exit\ 'The python script ' --emph "${external_file}" ' was not found.' fi - done + done } function __static__Ensure_Executable_Exists() @@ -75,13 +82,15 @@ function __static__Ensure_Executable_Exists() if [[ "${executable}" = '' ]]; then exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ 'Software executable for ' --emph "${label}" ' run was not specified.' - elif [[ "${executable}" = / ]]; then + elif [[ "${executable}" = / ]]; then if [[ ! -f "${executable}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run was not found.' + 'The executable file ' --emph "${executable}" \ + '\nfor the ' --emph "${label}" ' run was not found.' elif [[ ! -x "${executable}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The executable file for the ' --emph "${label}" ' run is not executable.' + 'The executable file ' --emph "${executable}" \ + '\nfor the ' --emph "${label}" ' run is not executable.' fi # It is important to perform this check with 'type' and not with 'hash' because 'hash' with # paths always succeed -> https://stackoverflow.com/a/42362142/14967071 diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 78125ef..170b7dc 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -64,7 +64,7 @@ function Functional_Test__do-Afterburner-only() return 1 fi mv 'Afterburner' 'Afterburner-invalid-config' - # Expect failure and test "SMASH" unfinished/lock files + # Expect failure and test "SMASH" unfinished/lock files Print_Info 'Running Hybrid-handler expecting crash in Afterburner software' BLACK_BOX_FAIL='smash_crashes'\ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -111,7 +111,7 @@ function Functional_Test__do-Afterburner-only() Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then - Print_Error 'Hybrid-handler unexpectedly succeded.' + Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi # Expect success and test the add_spectator functionality @@ -134,7 +134,7 @@ function Functional_Test__do-Afterburner-only() Print_Info 'Running Hybrid-handler expecting success with the custom add_spectator option' rm -r "IC"/* mkdir -p test - touch 'test/SMASH_IC_2.oscar' 'IC/config.yaml' + touch 'test/SMASH_IC_2.oscar' 'IC/config.yaml' printf ' Afterburner: Executable: %s/mocks/smash_afterburner_black-box.py diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index c275523..70d8b08 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -32,7 +32,6 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() HYBRID_optional_feature[Add_spectators_from_IC]='FALSE' Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts - } function Unit_Test__Afterburner-create-input-file() @@ -43,7 +42,7 @@ function Unit_Test__Afterburner-create-input-file() plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${plist_Sampler}" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then Print_Error 'The input file was not properly created in the output folder.' return 1 @@ -84,20 +83,26 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then - Print_Error 'Preparation did not fail with exit code 110 even though the final particle list already exists.' + Print_Error \ + 'Files preparation did not fail with exit code 110' \ + 'even though the final particle list already exists.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then - Print_Error 'Preparation did not fail with exit code 110 even though the config.yaml of the IC does not exist.' + Print_Error \ + 'Files preparation did not fail with exit code 110' \ + 'even though the config.yaml of the IC does not exist.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* touch "${HYBRID_software_output_directory[IC]}/config.yaml" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then - Print_Error 'Preparation did not fail with exit code 110 even though the SMASH_IC.oscar does not exist.' + Print_Error \ + 'Files preparation did not fail with exit code 110' \ + 'even though the SMASH_IC.oscar does not exist.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* @@ -126,7 +131,7 @@ function Unit_Test__Afterburner-check-all-input() Print_Error 'Ensuring existence of not-existing output directory succeeded.' return 1 fi - mkdir -p "${HYBRID_software_output_directory[Afterburner]}" "${HYBRID_software_output_directory[Sampler]}" + mkdir -p "${HYBRID_software_output_directory[Afterburner]}" "${HYBRID_software_output_directory[Sampler]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing config file succeeded.' @@ -139,10 +144,11 @@ function Unit_Test__Afterburner-check-all-input() return 1 fi touch "${HYBRID_software_output_directory[Afterburner]}/sampling0" - Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner + Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then - Print_Error 'Ensuring existence of existing folder/file unexpectedly failed,'\ - ' although all files were provided.' + Print_Error \ + 'Ensuring existence of existing folder/file unexpectedly failed,'\ + ' although all files were provided.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/sampling0" diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 5864f79..6e7d9e1 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -34,8 +34,6 @@ function Unit_Test__Hydro-create-input-file() ln -s "$(which ls)" dummy_exec HYBRID_software_executable[Hydro]="${HYBRIDT_folder_to_run_tests}/dummy_exec" mkdir 'eos' - mkdir -p "${HYBRID_software_output_directory[IC]}" - touch "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then Print_Error 'The config was not properly created in the output folder.' @@ -126,7 +124,7 @@ function Unit_Test__Hydro-check-all-input() fi ln -s 'not-existing-target' "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null - if [[ $? -eq 0 ]]; then + if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of broken link to IC file succeeded.' return 1 fi From e6366611d75a628e124765cd72e3d24be883c93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Sa=C3=9F?= Date: Wed, 29 Nov 2023 14:36:10 +0100 Subject: [PATCH 252/549] Add sqrtsnn key to all IC configs and correct ordering --- configs/smash_initial_conditions_AuAu.yaml | 2 +- configs/smash_initial_conditions_PbPb.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/configs/smash_initial_conditions_AuAu.yaml b/configs/smash_initial_conditions_AuAu.yaml index d87de7a..fdd8af3 100644 --- a/configs/smash_initial_conditions_AuAu.yaml +++ b/configs/smash_initial_conditions_AuAu.yaml @@ -22,7 +22,7 @@ Modi: Target: Particles: {2212: 79, 2112: 118} #Gold197 + Sqrtsnn: 200.0 Fermi_Motion: frozen - Sqrtsnn: 7.7 Impact: Max: 2.3 diff --git a/configs/smash_initial_conditions_PbPb.yaml b/configs/smash_initial_conditions_PbPb.yaml index 8288009..1cc1fe7 100644 --- a/configs/smash_initial_conditions_PbPb.yaml +++ b/configs/smash_initial_conditions_PbPb.yaml @@ -22,6 +22,7 @@ Modi: Target: Particles: {2212: 82, 2112: 126} #Lead208 + Sqrtsnn: 200.0 Fermi_Motion: frozen Impact: Max: 2.3 From 2908bd0739066c461329df39cc5cf2410feb9dcc Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 15 Nov 2023 10:37:29 +0100 Subject: [PATCH 253/549] Rephrase information about failure in error message --- bash/sanity_checks.bash | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 57fb948..bd05cf8 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -85,12 +85,12 @@ function __static__Ensure_Executable_Exists() elif [[ "${executable}" = / ]]; then if [[ ! -f "${executable}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ - 'The executable file ' --emph "${executable}" \ - '\nfor the ' --emph "${label}" ' run was not found.' + 'The executable file for the ' --emph "${label}" ' run was not found.'\ + 'Not existing path: ' --emph "${file_path}" elif [[ ! -x "${executable}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ - 'The executable file ' --emph "${executable}" \ - '\nfor the ' --emph "${label}" ' run is not executable.' + 'The executable file for the ' --emph "${label}" ' run is not executable.'\ + 'File path: ' --emph "${file_path}" fi # It is important to perform this check with 'type' and not with 'hash' because 'hash' with # paths always succeed -> https://stackoverflow.com/a/42362142/14967071 From 677b928c49306b12d4070a3b492b2cc5fa12b84f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 15 Nov 2023 10:39:29 +0100 Subject: [PATCH 254/549] Rename sampler black-box file consistently with the others --- tests/functional_tests_Sampler_only.bash | 4 +- ...pler_black_box.py => sampler_black-box.py} | 64 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) rename tests/mocks/{sampler_black_box.py => sampler_black-box.py} (94%) diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index e41c782..d3dbd1e 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -16,7 +16,7 @@ function Functional_Test__do-Sampler-only() touch 'Hydro/freezeout.dat' printf ' Sampler: - Executable: %s/tests/mocks/sampler_black_box.py + Executable: %s/tests/mocks/sampler_black-box.py ' "${HYBRIDT_repository_top_level_path}" > "${hybrid_handler_config}" # Expect success and test presence of output files Print_Info 'Running Hybrid-handler expecting success' @@ -58,7 +58,7 @@ function Functional_Test__do-Sampler-only() touch "${invalid_sampler_config}" printf ' Sampler: - Executable: %s/tests/mocks/sampler_black_box.py + Executable: %s/tests/mocks/sampler_black-box.py Config_file: %s ' "${HYBRIDT_repository_top_level_path}"\ "${invalid_sampler_config}" > "${hybrid_handler_config}" diff --git a/tests/mocks/sampler_black_box.py b/tests/mocks/sampler_black-box.py similarity index 94% rename from tests/mocks/sampler_black_box.py rename to tests/mocks/sampler_black-box.py index c9b2cf4..48a67e4 100755 --- a/tests/mocks/sampler_black_box.py +++ b/tests/mocks/sampler_black-box.py @@ -12,7 +12,7 @@ # How it works: # The sampler black box starts by checking if the sampler config exists. -# If it is the case, it gets the path to the freezeout hypersurface and the output +# If it is the case, it gets the path to the freezeout hypersurface and the output # directory from the config and checks that also the freezeout surface exists. # # Use the BLACK_BOX_FAIL environment variable set to "true" @@ -34,7 +34,7 @@ def check_input_arguments(): '- events: This is not a variable. This must be the string "events"\n' +\ '- num: random number set by the user. In hybrid num=1\n' +\ '- path_to_config_file: Path to the sampler configuration\n' - + if len(sys.argv) < 4 or len(sys.argv) > 5: err_msg = 'Invalid number of arguments!\n' + calling_instruction print(err_msg) @@ -44,20 +44,20 @@ def check_input_arguments(): 'The first argument must be the string "events"\n' + calling_instruction print(err_msg) sys.exit(1) - - + + def check_if_file_exists(path): if not os.path.isfile(path): err_msg = 'File not found at given path: ' + path print(err_msg) sys.exit(1) - + def check_if_directory_exists(path): if not os.path.isdir(path): err_msg = path + ' is not a valid directory or does not exist!' print(err_msg) sys.exit(1) - + def get_first_two_fields_in_line(line_in_config): #deleting the last character to omit the newline character '\n' line_in_config = line_in_config[:-1].split(' ') @@ -65,12 +65,12 @@ def get_first_two_fields_in_line(line_in_config): splitted_line_in_config[0]=str(splitted_line_in_config[0]) splitted_line_in_config[1]=str(splitted_line_in_config[1]) return splitted_line_in_config - + def get_value_as_string_from_config_by_keyword(path_to_config, keyword): - #Valid keywords: + #Valid keywords: #surface, spectra_dir, number_of_events, weakContribution, shear, ecrit file = open(path_to_config) - + while True: line_in_config = file.readline() if not line_in_config: @@ -81,7 +81,7 @@ def get_value_as_string_from_config_by_keyword(path_to_config, keyword): splitted_line_in_config = get_first_two_fields_in_line(line_in_config) key_in_config_line = splitted_line_in_config[0] value_of_key_in_config_line = splitted_line_in_config[1] - + if key_in_config_line == str(keyword): return value_of_key_in_config_line @@ -104,25 +104,25 @@ def make_fake_run(): for i in range(11): print('Fake computational progress: ', int(10*i), '%') time.sleep(0.1) - + def create_output_file(output_dir): header = '#!OSCAR2013 particle_lists t x y z mass p0 px py pz pdg ID charge\n' +\ '# Units: fm fm fm fm GeV GeV GeV GeV GeV none none e\n' +\ - '# SMASH-3.0-1-g0985d6b3\n' + '# SMASH-3.0-1-g0985d6b3\n' format_oscar2013 = '%g %g %g %g %g %g %g %g %g %d %d %d' - - output_file = output_dir+'/particle_lists_test.oscar' - + + output_file = output_dir+'/particle_lists.oscar' + file = open(output_file, 'w') file.write(header) file.close() - + with open(output_file, "a") as f_out: for counter_event in range(4): num_output_of_event = random.randint(10, 20) output_event = [] - + for counter_output in range(num_output_of_event): t = 200.0 x = 0.1*random.randint(-1000, 1000) @@ -136,36 +136,36 @@ def create_output_file(output_dir): pdg = random.randint(0, 10000) ID = random.randint(0, 100) charge = random.randint(-2, 2) - + output_line = [t, x, y, z, mass, p0, px, py, pz, pdg, ID, charge] output_event.append(output_line) - + output_event = np.asarray(output_event) - + f_out.write('# event '+ str(counter_event)+' out '+ str(num_output_of_event)+'\n') np.savetxt(f_out, output_event, delimiter=' ', newline='\n', fmt=format_oscar2013) f_out.write('# event '+ str(counter_event)+' end 0 impact 0.000 scattering_projectile_target yes\n') - - -#################### End Definitions #################### - + + +#################### End Definitions #################### + if __name__=='__main__': - + sampler_finishes = os.environ.get('BLACK_BOX_FAIL') != 'true' - - check_input_arguments() - + + check_input_arguments() + path_to_config = sys.argv[3] - + check_if_file_exists(path_to_config) - + path_to_freezeout = get_value_as_string_from_config_by_keyword(path_to_config, 'surface') output_dir = get_value_as_string_from_config_by_keyword(path_to_config, 'spectra_dir') - + check_if_file_exists(path_to_freezeout) check_if_directory_exists(output_dir) - + if sampler_finishes: make_fake_run() create_output_file(output_dir) From 569134bef4ae03ed587b96048e430ba53d82bfc3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 15 Nov 2023 10:41:24 +0100 Subject: [PATCH 255/549] Remove repeated comment about how functional tests work --- tests/functional_tests_Afterburner_only.bash | 2 -- tests/functional_tests_IC_only.bash | 2 -- tests/functional_tests_help.bash | 2 -- 3 files changed, 6 deletions(-) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 170b7dc..66eeae3 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -7,8 +7,6 @@ # #=================================================== -# NOTE: These functional tests just require code to run and finish with zero exit code. - function __static__Check_Successful_Handler_Run() { if [[ $1 -ne 0 ]]; then diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index 24d9ca4..8af0977 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -7,8 +7,6 @@ # #=================================================== -# NOTE: These functional tests just require code to run and finish with zero exit code. - function Functional_Test__do-IC-only() { shopt -s nullglob diff --git a/tests/functional_tests_help.bash b/tests/functional_tests_help.bash index a07cbcd..3f53535 100644 --- a/tests/functional_tests_help.bash +++ b/tests/functional_tests_help.bash @@ -7,8 +7,6 @@ # #=================================================== -# NOTE: These functional tests just require code to run and finish with zero exit code. - function Functional_Test__help-general() { Run_Hybrid_Handler_With_Given_Options_In_Subshell 'help' From 2349ead38c6898b2ec51b7e512257e278aebb5e1 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 15 Nov 2023 10:44:46 +0100 Subject: [PATCH 256/549] Extract common check to all functional test to function --- tests/functional_tests.bash | 1 + tests/functional_tests_Afterburner_only.bash | 17 +++----- tests/functional_tests_Hydro_only.bash | 12 +----- tests/functional_tests_IC_only.bash | 10 +---- tests/functional_tests_Sampler_only.bash | 6 +-- tests/functional_tests_utility_functions.bash | 41 +++++++++++++++++++ 6 files changed, 51 insertions(+), 36 deletions(-) create mode 100644 tests/functional_tests_utility_functions.bash diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index 1878741..3c48a74 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -17,6 +17,7 @@ function Make_Test_Preliminary_Operations() { # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning functional test \"%s\"\n\n" "${test_name}" + source "${HYBRIDT_tests_folder}/functional_tests_utility_functions.bash" || exit ${HYBRID_fatal_builtin} mkdir "$1" || exit ${HYBRID_fatal_builtin} cd "$1" || exit ${HYBRID_fatal_builtin} Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 66eeae3..547b90b 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -10,18 +10,11 @@ function __static__Check_Successful_Handler_Run() { if [[ $1 -ne 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly failed.' - return 1 - fi - unfinished_files=( Afterburner/*.unfinished ) - output_files=( Afterburner/* ) - if [[ ${#unfinished_files[@]} -lt 0 ]]; then - Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' - return 1 - elif [[ ${#output_files[@]} -ne 6 ]]; then - Print_Error 'Expected ' --emph '6' " output files, but ${#output_files[@]} found." + exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit\ + 'Hybrid-handler unexpectedly failed.' return 1 fi + Check_If_Software_Produced_Expected_Output 'Afterburner' "$(pwd)/Afterburner" } function Functional_Test__do-Afterburner-only() @@ -42,7 +35,7 @@ function Functional_Test__do-Afterburner-only() # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - __static__Check_Successful_Handler_Run $? || return 1 + __static__Check_Successful_Handler_Run $? mv 'Afterburner' 'Afterburner-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' @@ -126,7 +119,7 @@ function Functional_Test__do-Afterburner-only() File_Directory: "." ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - __static__Check_Successful_Handler_Run $? || return 1 + __static__Check_Successful_Handler_Run $? mv 'Afterburner' 'Afterburner-success-with-spectators' # Expect success and test the add_spectator functionality with custom spectator input Print_Info 'Running Hybrid-handler expecting success with the custom add_spectator option' diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index f6eb4f0..6fe3f31 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -28,11 +28,7 @@ function Functional_Test__do-Hydro-only() Print_Error 'Hybrid-handler unexpectedly failed.' return 1 fi - output_files=( Hydro/* ) - if [[ ${#output_files[@]} -ne 5 ]]; then - Print_Error 'Expected ' --emph '5' " output files, but ${#output_files[@]} found." - return 1 - fi + Check_If_Software_Produced_Expected_Output 'Hydro' "$(pwd)/Hydro" mv 'Hydro' 'Hydro-success' # Expect failure when giving an invalid IC output Print_Info 'Running Hybrid-handler expecting invalid IC argument' @@ -68,11 +64,7 @@ function Functional_Test__do-Hydro-only() Print_Error 'Hybrid-handler unexpectedly failed.' return 1 fi - output_files=( Hydro/* ) - if [[ ${#output_files[@]} -ne 5 ]]; then - Print_Error 'Expected ' --emph '5' " output files, but ${#output_files[@]} found." - return 1 - fi + Check_If_Software_Produced_Expected_Output 'Hydro' "$(pwd)/Hydro" mv 'Hydro' 'Hydro-success-custom-input' # Expect failure when an invalid config was supplied Print_Info 'Running Hybrid-handler expecting invalid config argument' diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index 8af0977..e8374f0 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -23,15 +23,7 @@ function Functional_Test__do-IC-only() Print_Error 'Hybrid-handler unexpectedly failed.' return 1 fi - unfinished_files=( IC/*.unfinished ) - output_files=( IC/* ) - if [[ ${#unfinished_files[@]} -gt 0 ]]; then - Print_Error 'Some unexpected ' --emph '.unfinished' ' output file remained.' - return 1 - elif [[ ${#output_files[@]} -ne 5 ]]; then - Print_Error 'Expected ' --emph '5' " output files, but ${#output_files[@]} found." - return 1 - fi + Check_If_Software_Produced_Expected_Output 'IC' "$(pwd)/IC" mv 'IC' 'IC-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid IC input file failure' diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index d3dbd1e..a7664cd 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -25,11 +25,7 @@ function Functional_Test__do-Sampler-only() Print_Error 'Hybrid-handler unexpectedly failed.' return 1 fi - output_files=( Sampler/* ) - if [[ ${#output_files[@]} -ne 4 ]]; then - Print_Error 'Expected ' --emph '4' " output files, but ${#output_files[@]} found." - return 1 - fi + Check_If_Software_Produced_Expected_Output 'Sampler' "$(pwd)/Sampler" mv 'Sampler' 'Sampler-success' # Expect failure and test terminal output local terminal_output_file error_message diff --git a/tests/functional_tests_utility_functions.bash b/tests/functional_tests_utility_functions.bash new file mode 100644 index 0000000..9992483 --- /dev/null +++ b/tests/functional_tests_utility_functions.bash @@ -0,0 +1,41 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Check_If_Software_Produced_Expected_Output() +{ + local -r block="$1" folder="$2" + local expected_output_files + case "${block}" in + IC | Hydro) + expected_output_files=5 + ;; + Sampler) + expected_output_files=4 + ;; + Afterburner ) + expected_output_files=6 + ;; + * ) + Print_Internal_And_Exit 'Invalid case branch entered in ' --emph "${FUNCNAME}." + ;; + esac + unfinished_files=( "${folder}"/*.{unfinished,lock} ) + output_files=( "${folder}"/* ) + if [[ ${#unfinished_files[@]} -gt 0 ]]; then + exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit\ + 'Some unexpected ' --emph '.{unfinished,lock}' ' output file remained'\ + 'in ' --emph "${folder}" + return 1 + elif [[ ${#output_files[@]} -ne ${expected_output_files} ]]; then + exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit\ + 'Expected ' --emph "${expected_output_files}" ' output files in '\ + --emph "${block}" " folder, but ${#output_files[@]} found." + return 1 + fi +} From 2b6340d2a71b483a03bd654bec89065672673432 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 15 Nov 2023 10:45:31 +0100 Subject: [PATCH 257/549] Add functional test to run full workflow --- tests/functional_tests_Sampler_only.bash | 2 +- tests/functional_tests_full_workflow.bash | 41 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/functional_tests_full_workflow.bash diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index a7664cd..d76c38d 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -58,7 +58,7 @@ function Functional_Test__do-Sampler-only() Config_file: %s ' "${HYBRIDT_repository_top_level_path}"\ "${invalid_sampler_config}" > "${hybrid_handler_config}" - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Sampler.' return 1 diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash new file mode 100644 index 0000000..ccfea8b --- /dev/null +++ b/tests/functional_tests_full_workflow.bash @@ -0,0 +1,41 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Functional_Test__do-everything() +{ + shopt -s nullglob + local -r config_filename='Handler_config.yaml'\ + mocks_folder="${HYBRIDT_repository_top_level_path}/tests/mocks" + printf ' + IC: + Executable: %s/smash_IC_black-box.py + Hydro: + Executable: %s/vhlle_black-box.py + Sampler: + Executable: %s/sampler_black-box.py + Afterburner: + Executable: %s/smash_afterburner_black-box.py + Add_spectators_from_IC: FALSE + Software_keys: + Modi: + List: + File_Directory: "." + ' "${mocks_folder}" "${mocks_folder}" "${mocks_folder}" "${mocks_folder}" > "${config_filename}" + # Expect success and test absence of "SMASH" unfinished file + Print_Info 'Running full workflow with Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + local block + for block in IC Sampler Hydro Afterburner; do + Check_If_Software_Produced_Expected_Output "${block}" "$(pwd)/${block}" + done +} From ed40f3a10069c2902da3c200016ca23ea9f0da1a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 15 Nov 2023 14:40:41 +0100 Subject: [PATCH 258/549] Rename and source utilities for functional tests differently --- tests/command_line_parser_for_tests.bash | 5 +++++ tests/functional_tests.bash | 1 - ...lity_functions.bash => utility_functions_functional.bash} | 0 3 files changed, 5 insertions(+), 1 deletion(-) rename tests/{functional_tests_utility_functions.bash => utility_functions_functional.bash} (100%) diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 4e740d1..184491d 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -24,6 +24,11 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() else source "${code_filename}" || exit ${HYBRID_fatal_builtin} fi + # If existing, source utility functions of given type of tests + code_filename="${HYBRIDT_tests_folder}/utility_functions_${suite_name}.bash" + if [[ -f "${code_filename}" ]]; then + source "${code_filename}" || exit ${HYBRID_fatal_builtin} + fi } function Parse_Tests_Command_Line_Options() diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index 3c48a74..1878741 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -17,7 +17,6 @@ function Make_Test_Preliminary_Operations() { # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning functional test \"%s\"\n\n" "${test_name}" - source "${HYBRIDT_tests_folder}/functional_tests_utility_functions.bash" || exit ${HYBRID_fatal_builtin} mkdir "$1" || exit ${HYBRID_fatal_builtin} cd "$1" || exit ${HYBRID_fatal_builtin} Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 diff --git a/tests/functional_tests_utility_functions.bash b/tests/utility_functions_functional.bash similarity index 100% rename from tests/functional_tests_utility_functions.bash rename to tests/utility_functions_functional.bash From 3726c2a205e1e58250d83cc22ce0f8041c3726c5 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 29 Nov 2023 14:44:18 +0100 Subject: [PATCH 259/549] Remove fancy printing from black-boxes and fix test Python 3.12+ gives a warning when uncorrect escaped sequences are used and this is annoying in our tests. I decided to remove the nice but irrelevant fancy headers from the black-box mock script. I know it is sad, but double escaping all the literal backslashes or using raw strings and then fix endlines was not something I'd have preferred to do... --- tests/functional_tests_full_workflow.bash | 8 +- tests/mocks/sampler_black-box.py | 15 +-- tests/mocks/smash_IC_black-box.py | 5 +- tests/mocks/smash_afterburner_black-box.py | 28 +----- tests/mocks/vhlle_black-box.py | 110 ++++++++------------- 5 files changed, 52 insertions(+), 114 deletions(-) diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash index ccfea8b..353bf2c 100644 --- a/tests/functional_tests_full_workflow.bash +++ b/tests/functional_tests_full_workflow.bash @@ -11,7 +11,11 @@ function Functional_Test__do-everything() { shopt -s nullglob local -r config_filename='Handler_config.yaml'\ - mocks_folder="${HYBRIDT_repository_top_level_path}/tests/mocks" + mocks_folder="${HYBRIDT_tests_folder}/mocks" + # Make a symlink to the python mock such that the eos folder doesn't have to be created in the mock folder + ln -s "${mocks_folder}/vhlle_black-box.py" "vhlle_black-box.py" + mkdir 'eos' + # Prepare handler file printf ' IC: Executable: %s/smash_IC_black-box.py @@ -26,7 +30,7 @@ function Functional_Test__do-everything() Modi: List: File_Directory: "." - ' "${mocks_folder}" "${mocks_folder}" "${mocks_folder}" "${mocks_folder}" > "${config_filename}" + ' "${mocks_folder}" "$(pwd)" "${mocks_folder}" "${mocks_folder}" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running full workflow with Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" diff --git a/tests/mocks/sampler_black-box.py b/tests/mocks/sampler_black-box.py index 48a67e4..ea83d30 100755 --- a/tests/mocks/sampler_black-box.py +++ b/tests/mocks/sampler_black-box.py @@ -87,20 +87,7 @@ def get_value_as_string_from_config_by_keyword(path_to_config, keyword): def make_fake_run(): - header ="\n"+\ - " _____ __ __ _____ _ ______ _____ ____ _ _____ _ __ ____ ______ __\n"+\ - " / ____| /\ | \/ | __ \| | | ____| __ \ | _ \| | /\ / ____| |/ / | _ \ / __ \ \ / /\n"+\ - " | (___ / \ | \ / | |__) | | | |__ | |__) | | |_) | | / \ | | | ' / | |_) | | | \ V / \n"+\ - " \___ \ / /\ \ | |\/| | ___/| | | __| | _ / | _ <| | / /\ \| | | < | _ <| | | |> < \n"+\ - " ____) / ____ \| | | | | | |____| |____| | \ \ | |_) | |____ / ____ \ |____| . \ | |_) | |__| / . \ \n"+\ - " |_____/_/ \_\_| |_|_| |______|______|_| \_\ |____/|______/_/ \_\_____|_|\_\ |____/ \____/_/ \_\ \n"+\ - " | | | | / _| | | | | | | \n"+\ - " | |_| |__ ___ | |_ _ _| |_ _ _ _ __ ___ ___| |_ __ _ _ __| |_ ___ _ __ _____ __ \n"+\ - " | __| '_ \ / _ \ | _| | | | __| | | | '__/ _ \ / __| __/ _` | '__| __/ __| | '_ \ / _ \ \ /\ / / \n"+\ - " | |_| | | | __/ | | | |_| | |_| |_| | | | __/ \__ \ || (_| | | | |_\__ \ | | | | (_) \ V V / \n"+\ - " \__|_| |_|\___| |_| \__,_|\__|\__,_|_| \___| |___/\__\__,_|_| \__|___/ |_| |_|\___/ \_/\_/ \n" - - print(header) + print("\nRunning Sampler:\n") for i in range(11): print('Fake computational progress: ', int(10*i), '%') time.sleep(0.1) diff --git a/tests/mocks/smash_IC_black-box.py b/tests/mocks/smash_IC_black-box.py index d716d3d..4a18d9a 100755 --- a/tests/mocks/smash_IC_black-box.py +++ b/tests/mocks/smash_IC_black-box.py @@ -18,10 +18,7 @@ def check_config(valid_config): return def print_terminal_start(): - # generated with https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 - Terminal_Out = "###########################################################################################################\n _________ _____ _____ _________ ___ ___ \n / _____/ / \ / _ \ / _____// | \ \n \_____ \ / \ / \ / /_\ \ \_____ \/ ~ \ \n / \/ Y \/ | \/ \ Y / \n /_______ /\____|__ /\____|__ /_______ /\___|_ / \n \/ \/ \/ \/ \/ \n .__ ___. .__ .___ \n ____ ______ _ __ | |__ ___.__.\_ |_________|__| __| _/ ________________ \n / \_/ __ \ \/ \/ / ______ | | < | | | __ \_ __ \ |/ __ | ______ _/ __ \_ __ \__ \ \n | | \ ___/\ / /_____/ | Y \___ | | \_\ \ | \/ / /_/ | /_____/ \ ___/| | \// __ \_ \n |___| /\___ >\/\_/ |___| / ____| |___ /__| |__\____ | \___ >__| (____ / \n \/ \/ \/\/ \/ \/ \/ \/ \n ___ .____________ ___. .__ __ ___. ___ \n / / | \_ ___ \ \_ |__ | | _____ ____ | | _\_ |__ _______ ___ \ \ \n / / | / \ \/ ______ | __ \| | \__ \ _/ ___\| |/ /| __ \ / _ \ \/ / \ \ \n ( ( | \ \____ /_____/ | \_\ \ |__/ __ \\ \___| < | \_\ ( <_> > < ) ) \n \ \ |___|\______ / |___ /____(____ /\___ >__|_ \|___ /\____/__/\_ \ / / \n \__\ \/ \/ \/ \/ \/ \/ \/ /__/ \n ###########################################################################################################\n" - print(Terminal_Out) - print("running smash IC") + print("\nRunning SMASH IC:\n") return def create_folders_structure(): diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index cff104f..622fdae 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -20,33 +20,7 @@ def check_config(valid_config): return def print_terminal_start(): - # generated with https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 - Terminal_Out="""###########################################################################################################\n -_________ _____ _____ _________ ___ ___\n - / _____/ / \ / _ \ / _____// | \ \n - \_____ \ / \ / \ / /_\ \ \_____ \/ ~ \\n - / \/ Y \/ | \/ \ Y /\n - /_______ /\____|__ /\____|__ /_______ /\___|_ /\n - \/ \/ \/ \/ \/\n - \n ___ ___ ___. .__ .___ _____________________ _____ - ____ ______ _ __ / | \ ___.__.\_ |_________|__| __| _/ \_ _____/\______ \ / _ \\n - / \_/ __ \ \/ \/ / ______ / ~ < | | | __ \_ __ \ |/ __ | ______ | __)_ | _/ / /_\ \\n - | | \ ___/\ / /_____/ \ Y /\___ | | \_\ \ | \/ / /_/ | /_____/ | \ | | \/ | \\n - |___| /\___ >\/\_/ \___|_ / / ____| |___ /__| |__\____ | /_______ / |____|_ /\____|__ /\n - \/ \/ \/ \/ \/ \/ \/ \/ \/\n - \n _____ _____________________________________________________ ____ _____________ _______ _____________________ __________.____ _____ _________ ____ __. __________\n ________ ____ ___\n - / _ \ \_ _____/\__ ___/\_ _____/\______ \______ \ | \______ \ \ \ \_ _____/\______ \ \______ \ | / _ \ \_ ___ \| |/ _| \______ \\n\_____ \ \ \/ / - / /_\ \ | __) | | | __)_ | _/| | _/ | /| _/ / | \ | __)_ | _/ | | _/ | / /_\ \/ \ \/| < ______ | | _/ /\n | \ \ / - / | \| \ | | | \ | | \| | \ | / | | \/ | \| \ | | \ | | \ |___/ | \ \___| | \ /_____/ | | \/\n | \/ \ - \____|__ /\___ / |____| /_______ / |____|_ /|______ /______/ |____|_ /\____|__ /_______ / |____|_ / |______ /_______ \____|__ /\______ /____|__ \ |______ /\n\_______ /___/\ \ - \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/\n \/ \_/\n###########################################################################################################\n""" - - - - - - print(Terminal_Out) - print("running SMASH Afterburner") + print("\nRunning SMASH Afterburner:\n") return def create_folders_structure(): diff --git a/tests/mocks/vhlle_black-box.py b/tests/mocks/vhlle_black-box.py index d9c561d..2d4b89e 100755 --- a/tests/mocks/vhlle_black-box.py +++ b/tests/mocks/vhlle_black-box.py @@ -9,61 +9,37 @@ import textwrap def print_terminal_start(): - # generated with https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20 - Terminal_Out = """###########################################################################################################\n - _________ _____ _____ _________ ___ ___ - / _____/ / \ / _ \ / _____// | \ \n - \_____ \ / \ / \ / /_\ \ \_____ \/ ~ \ \n - / \/ Y \/ | \/ \ Y / \n - /_______ /\____|__ /\____|__ /_______ /\___|_ / \n - \/ \/ \/ \/ \/ \n - .__ ___. .__ .___ \n - ____ ______ _ __ | |__ ___.__.\_ |_________|__| __| _/ ________________ \n - / \_/ __ \ \/ \/ / ______ | | < | | | __ \_ __ \ |/ __ | ______ _/ __ \_ __ \__ \ \n - | | \ ___/\ / /_____/ | Y \___ | | \_\ \ | \/ / /_/ | /_____/ \ ___/| | \// __ \_ \n - |___| /\___ >\/\_/ |___| / ____| |___ /__| |__\____ | \___ >__| (____ / \n - \/ \/ \/\/ \/ \/ \/ \/ \n - __ ___ ___ .____ .____ ___________ __ \n - / / ___ __/ | \| | | | \_ _____/ \ \ \n - / / \ \/ / ~ \ | | | | __)_ \ \ \n - \ \ \ /\ Y / |___| |___ | \ / / \n - \_\ \_/ \___|_ /|_______ \_______ \/_______ / /_/ \n - \/ \/ \/ \/ \n - - \n###########################################################################################################\n""" - - print(Terminal_Out) - print("running hydro") + print("\nRunning Hydro:\n") return def read_parameters(configFile): # list of possible parameters # parameter name, string to write, default value (0 where none) parameters = { - "freezeoutOnly": ["freezeoutOnly", 0], - "eosType": ["eosType", 0], - "eosTypeHadron": ["eosTypeHadron", 0], - "nx": ["nx", 0], - "ny": ["ny", 0], - "nz": ["nz", 0], - "icModel": ["icModel", 0], - "glauberVar": ["glauberVar", 1], - "xmin": ["xmin", 0], - "xmax": ["xmax", 0], - "ymin": ["ymin", 0], - "ymax": ["ymax", 0], - "etamin": ["etamin", 0], + "freezeoutOnly": ["freezeoutOnly", 0], + "eosType": ["eosType", 0], + "eosTypeHadron": ["eosTypeHadron", 0], + "nx": ["nx", 0], + "ny": ["ny", 0], + "nz": ["nz", 0], + "icModel": ["icModel", 0], + "glauberVar": ["glauberVar", 1], + "xmin": ["xmin", 0], + "xmax": ["xmax", 0], + "ymin": ["ymin", 0], + "ymax": ["ymax", 0], + "etamin": ["etamin", 0], "etamax": ["etamax", 0], - "tau0": ["tau0", 0], - "tauMax": ["tauMax", 0], + "tau0": ["tau0", 0], + "tauMax": ["tauMax", 0], "tauGridResize": ["tauGridResize", 4.0], "dtau": ["dtau", 0], - "e_crit": ["e_crit", 0], + "e_crit": ["e_crit", 0], "zetaSparam": ["zeta/s param", 0], - "etaSparam": ["etaSparam", 0], + "etaSparam": ["etaSparam", 0], "etaS": ["eta/s", 0], - "al": ["al", 0], - "ah": ["ah", 0], + "al": ["al", 0], + "ah": ["ah", 0], "aRho": ["aRho", 0], "etaSMin": ["etaSMin", 0], "T0": ["T0", 0], @@ -79,7 +55,7 @@ def read_parameters(configFile): "VTK_output_values": ["VTK output values", " "], "VTK_cartesian": ["VTK cartesian", 0] } - + if os.path.isfile(configFile): for line in open(configFile, "r"): line = line.split() @@ -90,7 +66,7 @@ def read_parameters(configFile): def print_parameters(): print("vhlle: reading parameters from ", args.params) print("vhlle: command line parameters are:") - print("collision system:") + print("collision system:") print("ini.state input: ", args.ISinput) print("output directory: ", args.outputDir) parameters = read_parameters(args.params) @@ -121,7 +97,7 @@ def check_command_line(): sys.exit(1) # check if config file exists if not args.params == "": - if not os.path.isfile(args.params) or not config_is_valid: + if not os.path.isfile(args.params) or not config_is_valid: print("cannot open parameters file ", args.params) sys.exit(1) return @@ -129,7 +105,7 @@ def check_command_line(): def check_eos(): # no real check at this point we assume the eos folder exists # where hlle_visc executable is - # to be implemented later, override with True now + # to be implemented later, override with True now eosPath = "" eosExists = os.path.exists(eosPath) eosExists = True @@ -137,17 +113,17 @@ def check_eos(): print("EoSaux: table eos/chiraleos.dat read, [emin,emax,nmin,nmax] = 0 146 0 6") print("EoSaux: table eos/chiralsmall.dat read, [emin,emax,nmin,nmax] = 0 1.46 0 0.3") print("EoSSMASH: table eos/hadgas_eos_SMASH.dat read, [emin,emax,nbmin,nbmax,qmin,qmax] = 0 1 0 0.5 -0.1 0.4") - else: + else: print("I/O error with eos/chiraleos.dat") sys.exit(1) return def exit_without_config(outputDirSpecified): if args.params == "": - print("""EoHadron: table eos/eosHadronLog.dat read, [emin,emax,nmin,nmax] = 0.00336897 74.2066 -44.5239 44.5239 -44.5239 44.5239" -fluid allocation done -icModel = 0 not implemented -IC done + print("""EoHadron: table eos/eosHadronLog.dat read, [emin,emax,nmin,nmax] = 0.00336897 74.2066 -44.5239 44.5239 -44.5239 44.5239" +fluid allocation done +icModel = 0 not implemented +IC done Init time = 6 [sec]""") create_folder(outputDirSpecified) variableList = ["tau", "E", "Efull", "Nb", "Sfull", "EtotSurf", "elements", "susp.", "%cut"] @@ -156,16 +132,16 @@ def exit_without_config(outputDirSpecified): print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*valueList)) sys.exit(1) return - + def read_initial_state(): - messageExample = """particle E = 1442.11 Nbar = 367 Ncharge = 148 Ns = 0 -IC SMASH, center: 0 0 4.36586 1.18914 -hydrodynamic E = 1442.1 Pz = 36.5687 Nbar = 367 Ncharge = 148.001 Ns = 0 -Px = -0.463514 Py = 0.556379 -initial_entropy S_ini = 7880.36 -IC done + messageExample = """particle E = 1442.11 Nbar = 367 Ncharge = 148 Ns = 0 +IC SMASH, center: 0 0 4.36586 1.18914 +hydrodynamic E = 1442.1 Pz = 36.5687 Nbar = 367 Ncharge = 148.001 Ns = 0 +Px = -0.463514 Py = 0.556379 +initial_entropy S_ini = 7880.36 +IC done Init time = 9 [sec]""" - + print("fluid allocation done") if os.path.exists(args.ISinput) and input_is_valid: print(messageExample) @@ -182,8 +158,8 @@ def print_timestep(timestep): randomList[8] = "-nan" print("{: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10} {: >10}".format(*randomList)) return - - + + def run_hydro(outputDirSpecified): # create freezout hypersurface file # only if output directory is specified @@ -203,11 +179,11 @@ def run_hydro(outputDirSpecified): return -if __name__ == '__main__': +if __name__ == '__main__': parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, epilog=textwrap.dedent(''' Use the BLACK_BOX_FAIL environment variable set to either "invalid_config", - "invalid_input" or to "crash" to mimic a particular failure in the + "invalid_input" or to "crash" to mimic a particular failure in the black box. ''')) parser.add_argument("-params", required=False, @@ -225,7 +201,7 @@ def run_hydro(outputDirSpecified): crash = os.environ.get('BLACK_BOX_FAIL') == "crash" args = parser.parse_args() outputDirGiven = not args.outputDir == "" - + print_terminal_start() check_command_line() print_parameters() @@ -233,4 +209,4 @@ def run_hydro(outputDirSpecified): check_eos() read_initial_state() create_folder(outputDirGiven) - run_hydro(outputDirGiven) \ No newline at end of file + run_hydro(outputDirGiven) From 0827ca3531adab124d38ca0f103e8570ce31aad9 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 30 Nov 2023 09:06:34 +0100 Subject: [PATCH 260/549] Refactor full workflow test and add one with spectators --- tests/functional_tests_full_workflow.bash | 66 ++++++++++++++++------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash index 353bf2c..72007e1 100644 --- a/tests/functional_tests_full_workflow.bash +++ b/tests/functional_tests_full_workflow.bash @@ -7,34 +7,60 @@ # #=================================================== -function Functional_Test__do-everything() +function Functional_Test__do-everything-without-spectators() +{ + __static__Test_Full_Workflow 'FALSE' +} + +function Functional_Test__do-everything-with-spectators() +{ + __static__Test_Full_Workflow 'TRUE' +} + +function __static__Test_Full_Workflow() { shopt -s nullglob local -r config_filename='Handler_config.yaml'\ mocks_folder="${HYBRIDT_tests_folder}/mocks" + __static__Prepare_Full_Handler_Configuration_File "$1" + __static__Create_Auxiliaries_For_Hydro + # Expect success and test absence of "SMASH" unfinished file + Print_Info 'Running full workflow with Hybrid-handler expecting success' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + __static__Check_Outcome_Of_Full_Run $? +} + +function __static__Create_Auxiliaries_For_Hydro() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty mocks_folder # Make a symlink to the python mock such that the eos folder doesn't have to be created in the mock folder ln -s "${mocks_folder}/vhlle_black-box.py" "vhlle_black-box.py" mkdir 'eos' - # Prepare handler file +} + +function __static__Prepare_Full_Handler_Configuration_File() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty config_filename mocks_folder printf ' - IC: - Executable: %s/smash_IC_black-box.py - Hydro: - Executable: %s/vhlle_black-box.py - Sampler: - Executable: %s/sampler_black-box.py - Afterburner: - Executable: %s/smash_afterburner_black-box.py - Add_spectators_from_IC: FALSE - Software_keys: - Modi: - List: - File_Directory: "." - ' "${mocks_folder}" "$(pwd)" "${mocks_folder}" "${mocks_folder}" > "${config_filename}" - # Expect success and test absence of "SMASH" unfinished file - Print_Info 'Running full workflow with Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -ne 0 ]]; then + IC: + Executable: %s/smash_IC_black-box.py + Hydro: + Executable: %s/vhlle_black-box.py + Sampler: + Executable: %s/sampler_black-box.py + Afterburner: + Executable: %s/smash_afterburner_black-box.py + Add_spectators_from_IC: %s + Software_keys: + Modi: + List: + File_Directory: "." + ' "${mocks_folder}" "$(pwd)" "${mocks_folder}" "${mocks_folder}" "$1" > "${config_filename}" +} + +function __static__Check_Outcome_Of_Full_Run() +{ + if [[ $1 -ne 0 ]]; then Print_Error 'Hybrid-handler unexpectedly failed.' return 1 fi From a9d1cf526d081e5fe919bcb1629f51603c35d249 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 22 Nov 2023 10:42:20 +0100 Subject: [PATCH 261/549] Include more recent logger improvements in this project --- bash/logger.bash | 182 +++++++++++++++++++++++++++++++---------------- 1 file changed, 119 insertions(+), 63 deletions(-) diff --git a/bash/logger.bash b/bash/logger.bash index f3b58bd..7196aba 100644 --- a/bash/logger.bash +++ b/bash/logger.bash @@ -50,8 +50,8 @@ if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then BSHLGGR_defaultExitCode=1 while [[ $# -gt 0 ]]; do case "$1" in - --fd ) - if [[ ! $2 =~ ^[1-9][0-9]*$ ]] || (( $2>254 )); then + --fd) + if [[ ! $2 =~ ^[1-9][0-9]*$ ]] || (($2 > 254)); then printf "Error sourcing BashLogger. '$1' option needs an integer value between 1 and 254.\n" return 1 else @@ -59,8 +59,8 @@ if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then shift 2 fi ;; - --default-exit-code ) - if [[ ! $2 =~ ^[0-9]+$ ]] || (( $2>255 )); then + --default-exit-code) + if [[ ! $2 =~ ^[0-9]+$ ]] || (($2 > 255)); then printf "Error sourcing BashLogger. '$1' option needs an integer value between 0 and 255.\n" return 1 else @@ -166,7 +166,8 @@ function __static__Logger() finalEndline='\n' restoreDefault='\e[0m' labelLength=10 - label="$1"; shift + label="$1" + shift labelToBePrinted=$(printf "%${labelLength}s" "${label}:") if [[ ! ${label} =~ ^(INTERNAL|FATAL|ERROR|WARNING|ATTENTION|INFO|DEBUG|TRACE)$ ]]; then __static__Logger 'INTERNAL' "${FUNCNAME} called with unknown label '${label}'!" @@ -174,57 +175,81 @@ function __static__Logger() __static__IsLevelOn "${label}" || return 0 exec 4>&1 # duplicate fd 1 to restore it later case "${label}" in - ERROR|FATAL ) + ERROR | FATAL) # ;;& means go on in case matching following patterns - color='\e[91m' ;;& - INTERNAL ) - color='\e[38;5;202m' ;;& - ERROR|FATAL|INTERNAL ) + color='\e[91m' + ;;& + INTERNAL) + color='\e[38;5;202m' + ;;& + ERROR | FATAL | INTERNAL) emphColor='\e[93m' - exec 1>&2 ;; # here stdout to stderr! - INFO ) + exec 1>&2 # here stdout to stderr! + ;; + INFO) color='\e[92m' - emphColor='\e[96m' ;;& - ATTENTION ) + emphColor='\e[96m' + ;;& + ATTENTION) color='\e[38;5;200m' - emphColor='\e[38;5;141m' ;;& - WARNING ) + emphColor='\e[38;5;141m' + ;;& + WARNING) color='\e[93m' - emphColor='\e[38;5;202m' ;;& - DEBUG ) + emphColor='\e[38;5;202m' + ;;& + DEBUG) color='\e[38;5;38m' - emphColor='\e[38;5;48m' ;;& - TRACE ) + emphColor='\e[38;5;48m' + ;;& + TRACE) color='\e[38;5;247m' - emphColor='\e[38;5;256m' ;;& - * ) - exec 1>&"${BSHLGGR_outputFd}" ;; # here stdout to chosen fd + emphColor='\e[38;5;256m' + ;;& + *) + exec 1>&"${BSHLGGR_outputFd}" # here stdout to chosen fd + ;; esac if __static__IsElementInArray '--' "$@"; then while [[ "$1" != '--' ]]; do case "$1" in - -n ) + -n) finalEndline='' - shift ;; - -l ) + shift + ;; + -l) labelToBePrinted="$(printf "%${labelLength}s" '')" - shift ;; - -d ) + shift + ;; + -d) restoreDefault='' - shift ;; - * ) - __static__Logger 'INTERNAL' "${FUNCNAME} called with unknown option \"$1\"!" ;; + shift + ;; + *) + __static__Logger 'INTERNAL' "${FUNCNAME} called with unknown option \"$1\"!" + ;; esac done shift fi # Print out initial new-lines before label suppressing first argument if it was endlines only - while [[ $1 =~ ^\\n ]]; do + # Note that endlines could be either '\n' or $'\n' and we want to deal with both. + local new_first_argument + while [[ $1 =~ ^\\n || "${1:0:1}" = $'\n' ]]; do printf '\n' - if [[ "${1/#\\n/}" = '' ]]; then - shift + if [[ $1 =~ ^\\n ]]; then + new_first_argument="${1/#\\n/}" + elif [[ "${1:0:1}" = $'\n' ]]; then + new_first_argument="${1/#$'\n'/}" + fi + if [[ "${new_first_argument}" = '' ]]; then + if [[ $# -eq 1 ]]; then + return + else + shift + fi else - set -- "${1/#\\n/}" "${@:2}" + set -- "${new_first_argument}" "${@:2}" fi done # Ensure something to print was given @@ -232,14 +257,15 @@ function __static__Logger() __static__Logger 'INTERNAL' "${FUNCNAME} called without message (or with new lines only)!" fi # Parse all arguments and save messages for later, possibly modified - local messagesToBePrinted emphNextString lastStringWasEmph indentation index + local messagesToBePrinted emphNextString lastStringWasEmph indentation message + local -r const_indentation="${labelToBePrinted//?/ } " messagesToBePrinted=() emphNextString='FALSE' lastStringWasEmph='FALSE' indentation='' while [[ $# -gt 0 ]]; do case "$1" in - --emph ) + --emph) # Here two cases should be handled: either '--emph' is an option or a string # -> it is literal if the last command line option or if following the option # -> it is an option otherwise @@ -259,7 +285,8 @@ function __static__Logger() fi # Set color and replace % by %% for later printf if [[ ${emphNextString} = 'TRUE' ]]; then - messagesToBePrinted+=( "${emphColor}${1//%/%%}" ) + message=$(__static__ReplaceEndlinesInMessage "$1") + messagesToBePrinted+=("${emphColor}${message//%/%%}") lastStringWasEmph='TRUE' else if [[ ${lastStringWasEmph} = 'FALSE' ]]; then @@ -269,7 +296,8 @@ function __static__Logger() else indentation='' fi - messagesToBePrinted+=( "${indentation}${color}${1//%/%%}" ) + message=$(__static__ReplaceEndlinesInMessage "$1") + messagesToBePrinted+=("${indentation}${color}${message//%/%%}") lastStringWasEmph='FALSE' fi emphNextString='FALSE' @@ -298,6 +326,34 @@ function __static__Logger() fi } +function __static__ReplaceEndlinesInMessage() +{ + # Go through the message to be added character by character and prepend indentation + # to literal '\n' -> Do this with pure bash not to rely on external tools. Using the + # BASH_REMATCH array would make it tougher to iterate "left to right" hitting the replaced + # patterns only once in a general fashion. + # + # NOTE: It is assumed here that the caller has defined the 'const_indentation' variable. + local index oneChar twoChars + message='' + for ((index = 0; index < ${#1}; index++)); do + oneChar="${1:${index}:1}" # one chatacter from index + twoChars="${1:${index}:2}" # two chatacters from index + if [[ "${twoChars}" = '\n' ]]; then + if [[ ${index} -eq 0 || "${1:$((index - 1)):1}" != '\' ]]; then + message+="\n${const_indentation}" + ((index++)) + continue + fi + elif [[ "${oneChar}" = $'\n' ]]; then + message+="\n${const_indentation}" + continue + fi + message+="${oneChar}" + done + printf '%s' "${message}" +} + function __static__IsLevelOn() { local label @@ -311,21 +367,21 @@ function __static__IsLevelOn() # - numeric -> till that level (1=ERROR, 2=WARNING, ...) # - string -> till that level local loggerLevels loggerLevelsOn level index - loggerLevels=( [1]='ERROR' [2]='WARNING' [3]='ATTENTION' [4]='INFO' [5]='DEBUG' [6]='TRACE' ) + loggerLevels=([1]='ERROR' [2]='WARNING' [3]='ATTENTION' [4]='INFO' [5]='DEBUG' [6]='TRACE') loggerLevelsOn=() if [[ ${VERBOSE-} =~ ^[0-9]+$ ]]; then - loggerLevelsOn=( "${loggerLevels[@]:1:VERBOSE}" ) + loggerLevelsOn=("${loggerLevels[@]:1:VERBOSE}") elif [[ ${VERBOSE-} =~ ^(ERROR|WARNING|ATTENTION|INFO|DEBUG|TRACE)$ ]]; then for level in "${loggerLevels[@]}"; do - loggerLevelsOn+=( "${level}" ) + loggerLevelsOn+=("${level}") if [[ ${VERBOSE-} = "${level}" ]]; then break fi done elif [[ ${VERBOSE-} =~ ^(FATAL|INTERNAL)$ ]]; then - loggerLevelsOn=( 'FATAL' ) + loggerLevelsOn=('FATAL') else - loggerLevelsOn=( 'FATAL' 'ERROR' 'WARNING' 'ATTENTION' 'INFO' ) + loggerLevelsOn=('FATAL' 'ERROR' 'WARNING' 'ATTENTION' 'INFO') fi for level in "${loggerLevelsOn[@]}"; do if [[ ${label} = "${level}" ]]; then @@ -352,22 +408,22 @@ function __static__IsElementInArray() #-----------------------------------------------------------------# #Set functions readonly to avoid possible redefinitions elsewhere readonly -f \ - PrintTrace \ - PrintDebug \ - PrintInfo \ - PrintAttention \ - PrintWarning \ - PrintError \ - PrintFatalAndExit \ - PrintInternalAndExit \ - Print_Trace \ - Print_Debug \ - Print_Info \ - Print_Attention \ - Print_Warning \ - Print_Error \ - Print_Fatal_And_Exit \ - Print_Internal_And_Exit \ - __static__Logger \ - __static__IsLevelOn \ - __static__IsElementInArray + PrintTrace \ + PrintDebug \ + PrintInfo \ + PrintAttention \ + PrintWarning \ + PrintError \ + PrintFatalAndExit \ + PrintInternalAndExit \ + Print_Trace \ + Print_Debug \ + Print_Info \ + Print_Attention \ + Print_Warning \ + Print_Error \ + Print_Fatal_And_Exit \ + Print_Internal_And_Exit \ + __static__Logger \ + __static__IsLevelOn \ + __static__IsElementInArray From 88a0b2717bfeca1f7dd89e4ca2a3cf900c480851 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 22 Nov 2023 10:45:47 +0100 Subject: [PATCH 262/549] Add unit test to check codebase formatting --- tests/unit_tests_formatting.bash | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/unit_tests_formatting.bash diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash new file mode 100644 index 0000000..7adf740 --- /dev/null +++ b/tests/unit_tests_formatting.bash @@ -0,0 +1,67 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Unit_Test__codebase-formatting() +{ + local formatter_found='FALSE' + if hash shfmt &> /dev/null; then + formatter_found='TRUE' + else + Print_Error 'Command ' --emph 'beautysh'\ + ' not available, unable to fully check codebase formatting.' + fi + local -r max_length=120 + local list_of_source_files files_with_too_long_lines files_with_wrong_formatting file + list_of_source_files=( + "${HYBRIDT_repository_top_level_path}"/Hybrid-handler + "${HYBRIDT_repository_top_level_path}"/**/*.bash + ) + files_with_too_long_lines=() + for file in "${list_of_source_files[@]}"; do + if [[ $(wc -L < "${file}") -gt ${max_length} ]]; then + files_with_too_long_lines+=( "${file}" ) + continue + fi + done + if [[ ${formatter_found} = 'FALSE' ]]; then + continue + else + # Quoting shfmt manual: "If a given path is a directory, all shell + # scripts found under that directory will be used." + files_with_wrong_formatting=( + $(shfmt -l -ln bash -i 4 -bn -ci -sr -kp -fn "${HYBRIDT_repository_top_level_path}") + ) + fi + if [[ ${#files_with_too_long_lines[@]} -gt 0 ]]; then + Print_Error\ + 'There are ' --emph "${#files_with_too_long_lines[@]}" ' file(s) with lines longer than '\ + --emph "${max_length}" ' characters:' + for file in "${files_with_too_long_lines[@]}"; do + Print_Error -l -- ' - '\ + --emph "$(realpath --relative-base="${HYBRIDT_repository_top_level_path}" "${file}")" + done + Print_Info '\nPlease adjust too long lines in the above mentioned files.' + fi + if [[ ${#files_with_wrong_formatting[@]} -gt 0 ]]; then + if [[ ${#files_with_too_long_lines[@]} -gt 0 ]]; then + printf '\n' + fi + Print_Error\ + 'There are ' --emph "${#files_with_wrong_formatting[@]}" ' file(s) wrongly formatted:' + for file in "${files_with_wrong_formatting[@]}"; do + Print_Error -l -- ' - '\ + --emph "$(realpath --relative-base="${HYBRIDT_repository_top_level_path}" "${file}")" + done + Print_Info '\nTo format all bash files correctly run:\n'\ + --emph "shfmt -w -ln bash -i 4 -bn -ci -sr -kp -fn \"${HYBRIDT_repository_top_level_path}\"" + fi + if (( ${#files_with_too_long_lines[@]} + ${#files_with_wrong_formatting[@]} > 0 )); then + return 1 + fi +} From 8fa2e42157ce426020ffa55f3794ae27e3313034 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 22 Nov 2023 12:32:18 +0100 Subject: [PATCH 263/549] Add space before backslash in some multi-line commands These are those that would be wrongly reformatted by shfmt, which I plan to use next to reformat the full codebase. --- Hybrid-handler | 2 +- bash/Afterburner_functionality.bash | 22 ++++---- bash/Hydro_functionality.bash | 22 ++++---- bash/IC_functionality.bash | 10 ++-- bash/Sampler_functionality.bash | 18 +++--- bash/command_line_parsers/helper.bash | 30 +++++----- bash/command_line_parsers/main_parser.bash | 6 +- bash/configuration_parser.bash | 20 +++---- bash/sanity_checks.bash | 10 ++-- bash/software_input_functionality.bash | 16 +++--- bash/system_requirements.bash | 55 +++++++++++-------- bash/utility_functions.bash | 14 ++--- tests/command_line_parser_for_tests.bash | 22 ++++---- tests/functional_tests_Afterburner_only.bash | 2 +- tests/functional_tests_full_workflow.bash | 5 +- .../unit_tests_Afterburner_functionality.bash | 11 ++-- tests/unit_tests_Hydro_functionality.bash | 17 +++--- tests/unit_tests_configuration.bash | 8 +-- tests/unit_tests_formatting.bash | 11 ++-- ...it_tests_software_input_functionality.bash | 40 ++++++++------ tests/unit_tests_system_requirements.bash | 8 +-- tests/utility_functions_functional.bash | 4 +- 22 files changed, 188 insertions(+), 165 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index cddbc37..2cd8d5c 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -58,7 +58,7 @@ function Define_Repository_Global_Path() # symbolic links. Note that we require 'readlink' to exist as this was introduced in # OpenBSD and GNU around 2000, see comments to https://unix.stackexchange.com/a/136527/370049. if ! hash readlink &> /dev/null; then - exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_builtin} Print_Fatal_And_Exit \ "No 'readlink' command found. Please install it and run the Hybrid-handler again." fi local source directory diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index e4c0dc9..cba2c0d 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -11,37 +11,37 @@ function Prepare_Software_Input_File_Afterburner() { mkdir -p "${HYBRID_software_output_directory[Afterburner]}" || exit ${HYBRID_fatal_builtin} if [[ -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" ' is already existing.' elif [[ ! -f "${HYBRID_software_base_config_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Afterburner]}" ' was not found.' fi cp "${HYBRID_software_base_config_file[Afterburner]}"\ "${HYBRID_software_configuration_file[Afterburner]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Afterburner]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" fi local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampling0" if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then if [[ -f "${target_link_name}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'The input file for the afterburner ' --emph "${target_link_name}"\ ' already exists.' # Here the config.yaml file is expected to be produced by SMASH in the output folder # during the IC run. It is used to determine the initial number of particles. elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ '\ndoes not exist, but is needed to check number of initial nucleons.' \ 'This file is expected to be produced by the IC software run.' elif [[ ! -f "${HYBRID_software_input_file[Spectators]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Spectator file ' --emph "${HYBRID_software_input_file[Spectators]}"\ ' does not exist.' fi - "${HYBRID_external_python_scripts[Add_spectators_from_IC]}"\ + "${HYBRID_external_python_scripts[Add_spectators_from_IC]}" \ '--sampled_particle_list' "${HYBRID_software_input_file[Afterburner]}" \ '--initial_particle_list' "${HYBRID_software_input_file[Spectators]}"\ '--output_file' "${target_link_name}"\ @@ -50,7 +50,7 @@ function Prepare_Software_Input_File_Afterburner() if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then ln -s -f "${HYBRID_software_input_file[Afterburner]}" "${target_link_name}" elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'File ' --emph "${target_link_name}" ' exists but it is not the Afterburner input file '\ --emph "${HYBRID_software_input_file[Afterburner]}" ' to be used.' fi @@ -60,17 +60,17 @@ function Prepare_Software_Input_File_Afterburner() function Ensure_All_Needed_Input_Exists_Afterburner() { if [[ ! -d "${HYBRID_software_output_directory[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Folder ' --emph "${HYBRID_software_output_directory[Afterburner]}" ' does not exist.' fi if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}"\ ' does not exist.' fi # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary. if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index b013073..e74af2d 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -11,16 +11,16 @@ function Prepare_Software_Input_File_Hydro() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" || exit ${HYBRID_fatal_builtin} if [[ -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' is already existing.' elif [[ ! -f "${HYBRID_software_base_config_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Hydro]}" ' was not found.' fi cp "${HYBRID_software_base_config_file[Hydro]}"\ "${HYBRID_software_configuration_file[Hydro]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Hydro]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'TXT' "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_new_input_keys[Hydro]}" fi # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). @@ -29,7 +29,7 @@ function Prepare_Software_Input_File_Hydro() if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then ln -s -f "${HYBRID_software_input_file[Hydro]}" "${target_link_name}" elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file '\ --emph "${HYBRID_software_input_file[Hydro]}" ' to be used.' fi @@ -39,7 +39,7 @@ function Prepare_Software_Input_File_Hydro() local eos_folder eos_folder="$(dirname $(type -P "${HYBRID_software_executable[Hydro]}"))/eos" if [[ ! -d "${eos_folder}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The folder ' --emph "${eos_folder}" ' does not exist.' fi local -r link_to_eos_folder="${HYBRID_software_output_directory[Hydro]}/eos" @@ -51,13 +51,13 @@ function Prepare_Software_Input_File_Hydro() unlink "${HYBRID_software_output_directory[Hydro]}/eos" ln -s "${eos_folder}" "${link_to_eos_folder}" else - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'A ' --emph 'eos' ' folder called already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ '.' 'Please remove it and run the hybrid handler again.' fi fi elif [[ -e "${link_to_eos_folder}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'A ' --emph 'eos' ' file already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ '.' 'Please remove it and run the hybrid handler again.' else @@ -68,15 +68,15 @@ function Prepare_Software_Input_File_Hydro() function Ensure_All_Needed_Input_Exists_Hydro() { if [[ ! -d "${HYBRID_software_output_directory[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Folder ' --emph "${HYBRID_software_output_directory[Hydro]}" ' does not exist.' fi if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi if [[ ! -e "${HYBRID_software_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The input file ' --emph "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat"\ ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then @@ -88,7 +88,7 @@ function Ensure_All_Needed_Input_Exists_Hydro() function Run_Software_Hydro() { cd "${HYBRID_software_output_directory[Hydro]}" - local -r\ + local -r \ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat"\ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 25abdfb..e6a1732 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -11,16 +11,16 @@ function Prepare_Software_Input_File_IC() { mkdir -p "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} if [[ -f "${HYBRID_software_configuration_file[IC]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Configuration file ' --emph "${HYBRID_software_configuration_file[IC]}" ' is already existing.' elif [[ ! -f "${HYBRID_software_base_config_file[IC]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[IC]}" ' was not found.' fi cp "${HYBRID_software_base_config_file[IC]}"\ "${HYBRID_software_configuration_file[IC]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[IC]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'YAML' "${HYBRID_software_configuration_file[IC]}" "${HYBRID_software_new_input_keys[IC]}" fi } @@ -28,11 +28,11 @@ function Prepare_Software_Input_File_IC() function Ensure_All_Needed_Input_Exists_IC() { if [[ ! -d "${HYBRID_software_output_directory[IC]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Folder ' --emph "${HYBRID_software_output_directory[IC]}" ' does not exist.' fi if [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The configuration file ' --emph "${HYBRID_software_configuration_file[IC]}" ' was not found.' fi } diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 968b03f..de1833f 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -11,30 +11,30 @@ function Prepare_Software_Input_File_Sampler() { mkdir -p "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} if [[ -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}"\ ' is already existing.' elif [[ ! -f "${HYBRID_software_base_config_file[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Sampler]}"\ ' was not found.' fi cp "${HYBRID_software_base_config_file[Sampler]}"\ "${HYBRID_software_configuration_file[Sampler]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Sampler]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'TXT' "${HYBRID_software_configuration_file[Sampler]}"\ "${HYBRID_software_new_input_keys[Sampler]}" fi if ! __static__Is_Sampler_Config_Valid; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ "Sampler configuration file invalid." fi # Replace potentially relative paths in Sampler config with absolute paths local freezeout_path output_directory freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') output_directory=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'spectra_dir') - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File\ + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'TXT' "${HYBRID_software_configuration_file[Sampler]}"\ "$(printf "%s: %s\n"\ 'surface' "${freezeout_path}"\ @@ -51,19 +51,19 @@ function Prepare_Software_Input_File_Sampler() function Ensure_All_Needed_Input_Exists_Sampler() { if [[ ! -d "${HYBRID_software_output_directory[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}"\ ' does not exist.' fi if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}"\ ' was not found.' fi # This is already done preparing the input file, but it's logically belonging here, too. # Therefore, we repeat the validation, as its cost is substantially negligible. if ! __static__Is_Sampler_Config_Valid; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ "Sampler configuration file validation failed when ensuring existence of all input." fi } @@ -88,7 +88,7 @@ function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() cd "${HYBRID_software_output_directory[Sampler]}" # If realpath succeeds, it prints the path that is the result of the function if ! realpath "${value}" 2> /dev/null; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Unable to transform relative path ' --emph "${value}" ' into global one.' fi cd - > /dev/null diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index a68a6dd..c9f46d2 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -17,7 +17,7 @@ function Give_Required_Help() __static__Print_Do_Help_Message ;; * ) - Print_Internal_And_Exit\ + Print_Internal_And_Exit \ 'Unexpected value of ' --emph "HYBRID_execution_mode=${HYBRID_execution_mode}"\ ' in ' --emph "${FUNCNAME}" ;; @@ -50,10 +50,10 @@ function __static__Print_Do_Help_Message() { printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n'\ 'You can specify the following command line options to the' 'do' 'execution mode:' - __static__Print_Command_Line_Option_Help\ + __static__Print_Command_Line_Option_Help \ '-o | --output-directory' "${HYBRID_output_directory}"\ "Directory where the run folder(s) will be created." - __static__Print_Command_Line_Option_Help\ + __static__Print_Command_Line_Option_Help \ '-c | --configuration-file' "${HYBRID_configuration_file}"\ "YAML configuration file to be used by the handler." } @@ -89,9 +89,12 @@ function __static__Print_Modes_Description() section_string='' for mode in "${!list_of_modes[@]}"; do # Remember that $(...) strip trailing '\n' -> Add new-line manually to the string - section_string+="$(printf '\e[38;5;85m%15s \e[96m%s\e[0m'\ - "${mode}"\ - "${list_of_modes[${mode}]}")"$'\n' + section_string+="$( + printf \ + '\e[38;5;85m%15s \e[96m%s\e[0m'\ + "${mode}"\ + "${list_of_modes[${mode}]}" + )"$'\n' done if hash sort &> /dev/null; then # Remember that the 'here-string' adds a newline to the string when @@ -107,13 +110,14 @@ function __static__Print_Modes_Description() function __static__Print_Command_Line_Option_Help() { - local -r length_option=30\ - indentation=' '\ - column_sep=' '\ - options_color='\e[38;5;85m'\ - text_color='\e[96m'\ - default_value_color='\e[93m'\ - default_text='\e[0m' + local -r \ + length_option=30\ + indentation=' '\ + column_sep=' '\ + options_color='\e[38;5;85m'\ + text_color='\e[96m'\ + default_value_color='\e[93m'\ + default_text='\e[0m' local name default_value description left_column left_column_length name=$1 default_value=$2 diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 022d59d..c25ef5d 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -25,7 +25,7 @@ function Parse_Execution_Mode() HYBRID_execution_mode='do' ;; * ) - exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ 'Specified mode ' --emph "$1" ' not valid! Run '\ --emph 'Hybrid-handler help' ' to get further information.' esac @@ -54,7 +54,7 @@ function Parse_Command_Line_Options() Print_Option_Specification_Error_And_Exit "$1" else if ! realpath "$2" &> /dev/null; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Specified output directory ' --emph "$2" ' not found' fi readonly HYBRID_output_directory="$(realpath "$2")" @@ -70,7 +70,7 @@ function Parse_Command_Line_Options() shift 2 ;; * ) - exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ 'Invalid option ' --emph "$1" ' specified in ' --emph "${HYBRID_execution_mode}"\ ' execution mode!' 'Use the ' --emph '--help' ' option to get further information.' ;; diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index e69dc78..9f13e20 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -13,7 +13,7 @@ function Validate_And_Parse_Configuration_File() { if [[ ! -f "${HYBRID_configuration_file}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Handler configuration file ' --emph "${filename}" ' not found.' fi __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File @@ -29,7 +29,7 @@ function Validate_And_Parse_Configuration_File() function __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File() { if ! yq "${HYBRID_configuration_file}" &> /dev/null; then - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'The handler configuration file does not contain valid YAML syntax.' fi } @@ -51,29 +51,29 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() continue 2 fi done - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'Invalid section ' --emph "${label}" ' found in the handler configuration file.' fi done # Here all given sections are valid. Check possible duplicates/holes and ordering using the stored indices if [[ ${#software_sections_indices[@]} -eq 0 ]]; then - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'No software section was specified in the handler configuration file.' elif [[ ${#software_sections_indices[@]} -gt 1 ]]; then if [[ $(sort -u <(printf '%d\n' "${software_sections_indices[@]}") | wc -l)\ -ne ${#software_sections_indices[@]} ]]; then - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'The same software section in the handler configuration file cannot be repeated.' fi if ! sort -C <(printf '%d\n' "${software_sections_indices[@]}"); then - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'Software sections in the handler configuration file are out of order.' fi local gaps_between_indices gaps_between_indices=$(awk 'NR>1{print $1-x}{x=$1}'\ <(printf '%d\n' "${software_sections_indices[@]}") | sort -u) if [[ "${gaps_between_indices}" != '1' ]]; then - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'Missing software section(s) in the handler configuration file.' fi fi @@ -101,7 +101,7 @@ function __static__Abort_If_Invalid_Keys_Were_Used() __static__Validate_Keys_Of_Section 'Afterburner' # Abort if some validation failed if [[ "${#invalid_report[@]}" -ne 0 ]]; then - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'Following invalid keys found in the handler configuration file:'\ '---------------------------------------------------------------'\ "${invalid_report[@]/%/:}"\ @@ -141,7 +141,7 @@ function __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string() function __static__Parse_Section() { - local -r\ + local -r \ section_label=$1\ yaml_config="$(< "${HYBRID_configuration_file}")" local yaml_section valid_key @@ -174,7 +174,7 @@ function __static__YAML_section_must_be_empty() { local yaml_section=$1 if [[ "${yaml_section}" != '{}' ]]; then - Print_Internal_And_Exit\ + Print_Internal_And_Exit \ 'Not all keys in ' --emph "${2:-some}" ' section have been parsed. Remaining:'\ --emph "\n${yaml_section}\n" fi diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index bd05cf8..b224381 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -69,7 +69,7 @@ function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() { for external_file in "${HYBRID_external_python_scripts[@]}"; do if [[ ! -f "${external_file}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Internal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Internal_And_Exit \ 'The python script ' --emph "${external_file}" ' was not found.' fi done @@ -80,15 +80,15 @@ function __static__Ensure_Executable_Exists() local label=$1 executable executable="${HYBRID_software_executable[${label}]}" if [[ "${executable}" = '' ]]; then - exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_variable_unset} Print_Fatal_And_Exit \ 'Software executable for ' --emph "${label}" ' run was not specified.' elif [[ "${executable}" = / ]]; then if [[ ! -f "${executable}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The executable file for the ' --emph "${label}" ' run was not found.'\ 'Not existing path: ' --emph "${file_path}" elif [[ ! -x "${executable}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'The executable file for the ' --emph "${label}" ' run is not executable.'\ 'File path: ' --emph "${file_path}" fi @@ -96,7 +96,7 @@ function __static__Ensure_Executable_Exists() # paths always succeed -> https://stackoverflow.com/a/42362142/14967071 # This will be entered if the user gives something stupid as '~' as executable. elif ! type -P "${executable}" &> /dev/null; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'The command ' --emph "${executable}" ' specified for the '\ --emph "${label}" ' run was not located by the shell.'\ 'Please check your ' --emph 'PATH' ' environment variable and make sure'\ diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 7942197..9688070 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -33,12 +33,12 @@ function __static__Replace_Keys_Into_YAML_File() Ensure_That_Given_Variables_Are_Set_And_Not_Empty base_input_file keys_to_be_replaced # Use yq -P to bring all YAML to same format (crucial for later check on number of lines) if ! yq -P --inplace "${base_input_file}" 2> /dev/null; then - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'File ' --emph "${base_input_file}" ' does not seem to contain valid YAML syntax. Run'\ --emph " yq -P --inplace \"${base_input_file}\""\ "\nto have more information about the problem." elif ! keys_to_be_replaced=$(yq -P <(printf "${keys_to_be_replaced}\n") 2> /dev/null); then - exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ 'Keys to be replaced do not seem to contain valid YAML syntax.' fi local initial_number_of_lines @@ -50,7 +50,7 @@ function __static__Replace_Keys_Into_YAML_File() # The merge must not have changed the number of lines of input file. If it did, # it means that some key was not present and has been appended => Error! if [[ $(wc -l < "${base_input_file}") -ne ${initial_number_of_lines} ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'One or more provided software input keys were not found to be replaced'\ 'in the ' --emph "${base_input_file}" ' file.' 'Please, check your configuration file.' fi @@ -66,14 +66,14 @@ function __static__Replace_Keys_Into_Txt_File() keys_to_be_replaced="$(sed '/^[[:space:]]*$/d' <(printf "${keys_to_be_replaced}\n"))" # Impose that both file and new keys have two entries per line if ! awk 'NF!=2 {exit 1}' "${base_input_file}"; then - exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'File ' --emph "${base_input_file}" ' does not seem to contain two columns per line only!' elif ! awk 'NF!=2 {exit 1}' <(printf "${keys_to_be_replaced}\n"); then - exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ 'Keys to be replaced do not seem to contain valid key-value syntax.' fi if ! awk '$1 ~ /:$/ {exit 1}' "${base_input_file}"; then - exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ 'File ' --emph "${base_input_file}" ' should not have a colon at the end of keys!' fi local number_of_fields_per_line @@ -81,10 +81,10 @@ function __static__Replace_Keys_Into_Txt_File() $(awk 'BEGIN{FS=":"}{print NF}' <(printf "${keys_to_be_replaced}\n") | sort -u) ) if [[ ${#number_of_fields_per_line[@]} -gt 1 ]]; then - exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ 'Keys to be replaced do not have consistent colon-terminated keys syntax.' elif [[ ${number_of_fields_per_line[0]} -gt 2 ]]; then - exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ 'Keys to be replaced seem to use more than a colon after key(s).' fi # NOTE: Since the YAML implementation is very general, here we can take advantage of it diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 9d5d764..4f0be44 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -123,11 +123,14 @@ function __static__Analyze_System_Properties() __static__Is_Gnu_Version_In_Use "${program}" || return_code=$? case "${return_code}" in 0) - system_information["GNU-${program}"]+='OK' ;; + system_information["GNU-${program}"]+='OK' + ;; 2) - system_information["GNU-${program}"]+='?' ;; + system_information["GNU-${program}"]+='?' + ;; *) - system_information["GNU-${program}"]+='---' ;; + system_information["GNU-${program}"]+='---' + ;; esac done for name in "${HYBRID_env_variables_required[@]}"; do @@ -239,7 +242,7 @@ function __static__Prepare_Binary_Report_Array() Ensure_That_Given_Variables_Are_Set_And_Not_Empty single_field_length for name in "${HYBRID_programs_just_required[@]}"; do system_report+=( - "$(__static__Get_Single_Tick_Cross_Requirement_Report\ + "$(__static__Get_Single_Tick_Cross_Requirement_Report \ "PROG ${name}"\ "${system_information[${name}]}" )" @@ -248,7 +251,7 @@ function __static__Prepare_Binary_Report_Array() for program in "${HYBRID_gnu_programs_required[@]}"; do is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) system_report+=( - "$(__static__Get_Single_Tick_Cross_Requirement_Report\ + "$(__static__Get_Single_Tick_Cross_Requirement_Report \ "GNU ${program}"\ "${is_gnu}" )" @@ -256,7 +259,7 @@ function __static__Prepare_Binary_Report_Array() done for name in "${HYBRID_env_variables_required[@]}"; do system_report+=( - "$(__static__Get_Single_Tick_Cross_Requirement_Report\ + "$(__static__Get_Single_Tick_Cross_Requirement_Report \ "ENV ${name}"\ "${system_information[${name}]}" )" @@ -382,13 +385,15 @@ function __static__Get_Larger_Version() dots_of_v1=${v1//[^.]/} dots_of_v2=${v2//[^.]/} if [[ ${#dots_of_v1} -lt ${#dots_of_v2} ]]; then - declare -n shorter_version=v1\ - dots_of_shorter_version=dots_of_v1\ - dots_of_longer_version=dots_of_v2 + declare -n \ + shorter_version=v1\ + dots_of_shorter_version=dots_of_v1\ + dots_of_longer_version=dots_of_v2 else - declare -n shorter_version=v2\ - dots_of_shorter_version=dots_of_v2\ - dots_of_longer_version=dots_of_v1 + declare -n \ + shorter_version=v2\ + dots_of_shorter_version=dots_of_v2\ + dots_of_longer_version=dots_of_v1 fi while [[ ${#dots_of_shorter_version} -ne ${#dots_of_longer_version} ]]; do shorter_version+='.0' # Add zeroes to shorter string @@ -420,12 +425,13 @@ function __static__Get_Larger_Version() function __static__Print_Requirement_Version_Report_Line() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" - local -r emph_color='\e[96m'\ - red='\e[91m'\ - green='\e[92m'\ - yellow='\e[93m'\ - text_color='\e[38;5;38m'\ - default='\e[0m' + local -r \ + emph_color='\e[96m'\ + red='\e[91m'\ + green='\e[92m'\ + yellow='\e[93m'\ + text_color='\e[38;5;38m'\ + default='\e[0m' local line found version_found version_ok tmp_array program=$1 tmp_array=( ${system_information[${program}]//|/ } ) # Unquoted to let word splitting act found=${tmp_array[0]} @@ -459,12 +465,13 @@ function __static__Print_Requirement_Version_Report_Line() function __static__Get_Single_Tick_Cross_Requirement_Report() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty single_field_length - local -r emph_color='\e[96m'\ - red='\e[91m'\ - green='\e[92m'\ - yellow='\e[93m'\ - text_color='\e[38;5;38m'\ - default='\e[0m' + local -r \ + emph_color='\e[96m'\ + red='\e[91m'\ + green='\e[92m'\ + yellow='\e[93m'\ + text_color='\e[38;5;38m'\ + default='\e[0m' local line name="$1" status=$2 name_string printf -v name_string "%s ${emph_color}%s" "${name% *}" "${name#* }" printf -v line " %*s${text_color}: ${default}" "${single_field_length}" "${name_string}" diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index f7f8d29..e476f15 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -95,7 +95,7 @@ function Print_Line_of_Equals() function Print_Centered_Line() { - local input_string output_total_width indentation padding_character\ + local input_string output_total_width indentation padding_character \ postfix real_length padding_utility input_string="$1" output_total_width="${2:-$(tput cols)}" # Input arg. or full width of terminal @@ -122,7 +122,7 @@ function Print_Centered_Line() function Print_Option_Specification_Error_And_Exit() { - exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ 'The value of the option ' --emph "$1" ' was not correctly specified (either forgotten or invalid)!' } @@ -142,7 +142,7 @@ function Remove_Comments_In_File() filename=$1 comment_character=${2:-#} if [[ ! -f "${filename}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'File ' --emph "${filename}" ' not found.' elif [[ ${#comment_character} -ne 1 ]]; then Print_Internal_And_Exit 'Comment character ' --emph "${comment_character}" ' invalid!' @@ -164,7 +164,7 @@ function Call_Function_If_Existing_Or_Exit() if [[ "$(type -t ${name_of_the_function})" = 'function' ]]; then ${name_of_the_function} "$@" else - exit_code=${HYBRID_fatal_missing_feature} Print_Internal_And_Exit\ + exit_code=${HYBRID_fatal_missing_feature} Print_Internal_And_Exit \ '\nFunction ' --emph "${name_of_the_function}" ' not found!'\ 'Please provide an implementation following the in-code documentation.' fi @@ -196,7 +196,7 @@ function Ensure_That_Given_Variables_Are_Set() { if [[ ${variable_name} =~ \]$ && -v ${variable_name} ]]; then continue fi - Print_Internal_And_Exit\ + Print_Internal_And_Exit \ 'Variable ' --emph "${variable_name}" ' not set in function ' --emph "${FUNCNAME[1]}" '.' fi done @@ -226,7 +226,7 @@ function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { fi set -u fi - Print_Internal_And_Exit\ + Print_Internal_And_Exit \ 'Variable ' --emph "${variable_name}" ' unset or empty in function ' --emph "${FUNCNAME[1]}" '.' done } @@ -253,7 +253,7 @@ function Make_Functions_Defined_In_This_File_Readonly() sed -E 's/^[[:space:]]*function[[:space:]]+([^(]+)\(\)[[:space:]]*$/\1/') ) if [[ ${#declared_functions[@]} -eq 0 ]]; then - Print_Internal_And_Exit\ + Print_Internal_And_Exit \ 'Function ' --emph "${FUNCNAME}" ' called, but no function found in file\n file '\ --emph "${BASH_SOURCE[1]}" '.' else diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 184491d..55230ce 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -12,14 +12,14 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() local suite_name code_filename suite_name="$1" if [[ ! ${suite_name} =~ ^(functional|unit)$ ]]; then - exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ 'Invalid tests type ' --emph "${suite_name:-}" '. Valid values: '\ --emph 'unit' ' or ' --emph 'functional' '.'\ 'Use the ' --emph '--help' ' option to get more information.' fi code_filename="${HYBRIDT_tests_folder}/${suite_name}_tests.bash" if [[ ! -f "${code_filename}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'File ' --emph "${code_filename}" ' not found.' else source "${code_filename}" || exit ${HYBRID_fatal_builtin} @@ -67,7 +67,7 @@ function Parse_Tests_Command_Line_Options() HYBRIDT_clean_test_folder='FALSE' shift ;; * ) - exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ 'Invalid option ' --emph "$1" ' specified! Use the '\ --emph '--help' ' option to get further information.' ;; @@ -101,7 +101,7 @@ function __static__Print_Helper() "Without this option all existing tests are run." __static__Add_Option_To_Helper "-k | --keep-tests-folder"\ "Leave all the created folders and files in the test folder." - Print_Warning\ + Print_Warning \ " Values from options must be separated by space and short options cannot be combined." } @@ -130,10 +130,12 @@ function __static__Set_Tests_To_Be_Run_Using_Numbers() local selection_string numeric_list number selected_tests selection_string=$1 numeric_list=( - $(awk\ - 'BEGIN{RS=","} - /\-/{split($0, res, "-"); for(i=res[1]; i<=res[2]; i++){printf "%d\n", i}; next} - {printf "%d\n", $0}' <<< "${selection_string}") + $( + awk \ + 'BEGIN{RS=","} + /\-/{split($0, res, "-"); for(i=res[1]; i<=res[2]; i++){printf "%d\n", i}; next} + {printf "%d\n", $0}' <<< "${selection_string}" + ) ) Print_Debug 'Selected tests indices: ' --emph "( ${numeric_list[*]} )" selected_tests=() @@ -143,7 +145,7 @@ function __static__Set_Tests_To_Be_Run_Using_Numbers() if [[ ${number} -lt ${#HYBRIDT_tests_to_be_run[@]} ]]; then selected_tests+=( "${HYBRIDT_tests_to_be_run[number]}" ) else - exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ 'Some specified test number within ' --emph "$1" ' is not valid! Use'\ 'the ' --emph '-t' ' option without value to get a list of available tests.' fi @@ -165,7 +167,7 @@ function __static__Set_Tests_To_Be_Run_Using_Globbing() done HYBRIDT_tests_to_be_run=( "${selected_tests[@]}" ) if [[ ${#HYBRIDT_tests_to_be_run[@]} -eq 0 ]]; then - exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit\ + exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ "No test name found matching \"$1\" globbing pattern! Use"\ "the '-t' option without value to get a list of available tests." fi diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 547b90b..d70966a 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -10,7 +10,7 @@ function __static__Check_Successful_Handler_Run() { if [[ $1 -ne 0 ]]; then - exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit\ + exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit \ 'Hybrid-handler unexpectedly failed.' return 1 fi diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash index 72007e1..20baa11 100644 --- a/tests/functional_tests_full_workflow.bash +++ b/tests/functional_tests_full_workflow.bash @@ -20,8 +20,9 @@ function Functional_Test__do-everything-with-spectators() function __static__Test_Full_Workflow() { shopt -s nullglob - local -r config_filename='Handler_config.yaml'\ - mocks_folder="${HYBRIDT_tests_folder}/mocks" + local -r \ + config_filename='Handler_config.yaml'\ + mocks_folder="${HYBRIDT_tests_folder}/mocks" __static__Prepare_Full_Handler_Configuration_File "$1" __static__Create_Auxiliaries_For_Hydro # Expect success and test absence of "SMASH" unfinished file diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 70d8b08..2333410 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -38,7 +38,7 @@ function Unit_Test__Afterburner-create-input-file() { touch "${HYBRID_software_base_config_file[Afterburner]}" mkdir -p "${HYBRID_software_output_directory[Sampler]}" - local -r\ + local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${plist_Sampler}" @@ -73,10 +73,11 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-sp function Unit_Test__Afterburner-create-input-file-with-spectators() { - mkdir -p "${HYBRID_software_output_directory[Sampler]}"\ - "${HYBRID_software_output_directory[IC]}"\ - "${HYBRID_software_output_directory[Afterburner]}" - local -r\ + mkdir -p \ + "${HYBRID_software_output_directory[Sampler]}"\ + "${HYBRID_software_output_directory[IC]}"\ + "${HYBRID_software_output_directory[Afterburner]}" + local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 6e7d9e1..cc22407 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -109,8 +109,9 @@ function Unit_Test__Hydro-check-all-input() Print_Error 'Ensuring existence of not-existing output directory succeeded.' return 1 fi - mkdir -p "${HYBRID_software_output_directory[Hydro]}"\ - "${HYBRID_software_output_directory[IC]}" + mkdir -p \ + "${HYBRID_software_output_directory[Hydro]}" \ + "${HYBRID_software_output_directory[IC]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of not-existing config file succeeded.' @@ -129,8 +130,9 @@ function Unit_Test__Hydro-check-all-input() return 1 fi touch "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" - ln -s -f "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat"\ - "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + ln -s -f \ + "${HYBRID_software_output_directory[IC]}/SMASH_IC.dat" \ + "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Hydro &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing folder/file failed.' @@ -151,9 +153,10 @@ function Make_Test_Preliminary_Operations__Hydro-test-run-software() function Unit_Test__Hydro-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" - local -r hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt"\ - Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - IC_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" + local -r \ + hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" \ + Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ + IC_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Hydro if [[ ! -f "${hydro_terminal_output}" ]]; then diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 80742f1..11de20d 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -212,7 +212,7 @@ function Unit_Test__configuration-parse-IC-section() General: Randomseed: 12345 ' > "${HYBRID_configuration_file}" - Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ + Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell \ 'IC' 'foo' 'bar' '' $'General:\n Randomseed: 12345' if [[ $? -ne 0 ]]; then return 1 @@ -238,7 +238,7 @@ function Unit_Test__configuration-parse-Hydro-section() Software_keys: etaS: 0.12345 ' > "${HYBRID_configuration_file}" - Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ + Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell \ 'Hydro' 'foo' 'bar' 'ket' 'etaS: 0.12345' if [[ $? -ne 0 ]]; then return 1 @@ -263,7 +263,7 @@ function Unit_Test__configuration-parse-Sampler-section() Software_keys: shear: 1.2345 ' > "${HYBRID_configuration_file}" - Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ + Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell \ 'Sampler' 'foo' 'bar' '' 'shear: 1.2345' if [[ $? -ne 0 ]]; then return 1 @@ -290,7 +290,7 @@ function Unit_Test__configuration-parse-Afterburner-section() General: End_Time: 42000 ' > "${HYBRID_configuration_file}" - Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell\ + Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell \ 'Afterburner' 'foo' 'bar' 'ket' $'General:\n End_Time: 42000' if [[ $? -ne 0 ]]; then return 1 diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash index 7adf740..cc4142a 100644 --- a/tests/unit_tests_formatting.bash +++ b/tests/unit_tests_formatting.bash @@ -39,7 +39,7 @@ function Unit_Test__codebase-formatting() ) fi if [[ ${#files_with_too_long_lines[@]} -gt 0 ]]; then - Print_Error\ + Print_Error \ 'There are ' --emph "${#files_with_too_long_lines[@]}" ' file(s) with lines longer than '\ --emph "${max_length}" ' characters:' for file in "${files_with_too_long_lines[@]}"; do @@ -52,14 +52,15 @@ function Unit_Test__codebase-formatting() if [[ ${#files_with_too_long_lines[@]} -gt 0 ]]; then printf '\n' fi - Print_Error\ + Print_Error \ 'There are ' --emph "${#files_with_wrong_formatting[@]}" ' file(s) wrongly formatted:' for file in "${files_with_wrong_formatting[@]}"; do - Print_Error -l -- ' - '\ + Print_Error -l -- ' - ' \ --emph "$(realpath --relative-base="${HYBRIDT_repository_top_level_path}" "${file}")" done - Print_Info '\nTo format all bash files correctly run:\n'\ - --emph "shfmt -w -ln bash -i 4 -bn -ci -sr -kp -fn \"${HYBRIDT_repository_top_level_path}\"" + Print_Info \ + '\nTo format all bash files correctly run:\n'\ + --emph "shfmt -w -ln bash -i 4 -bn -ci -sr -kp -fn \"${HYBRIDT_repository_top_level_path}\"" fi if (( ${#files_with_too_long_lines[@]} + ${#files_with_wrong_formatting[@]} > 0 )); then return 1 diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index f23353c..0c8e260 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -9,8 +9,8 @@ function Make_Test_Preliminary_Operations__replace-in-software-input-YAML() { - source "${HYBRIDT_repository_top_level_path}"/bash/software_input_functionality.bash\ - || exit "${HYBRID_fatal_builtin}" + source "${HYBRIDT_repository_top_level_path}"/bash/software_input_functionality.bash \ + || exit "${HYBRID_fatal_builtin}" } function Unit_Test__replace-in-software-input-YAML() @@ -70,10 +70,11 @@ Foo: BarBar' # so it is important here to have no leading empty lines, otherwise # this test would succeed/fail depending on yq version available! if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then - Print_Error "YAML replacement failed!"\ - '---- OBTAINED: ----' "$(< "${base_input_file}")"\ - '---- EXPECTED: ----' "${expected_result}"\ - '-------------------' + Print_Error \ + "YAML replacement failed!" \ + '---- OBTAINED: ----' "$(< "${base_input_file}")" \ + '---- EXPECTED: ----' "${expected_result}" \ + '-------------------' return 1 fi rm "${base_input_file}" @@ -144,10 +145,11 @@ function Unit_Test__replace-in-software-input-TXT() expected_result=${expected_result%?} # Get rid of trailing endline Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then - Print_Error "YAML replacement failed!"\ - "---- OBTAINED: ----\n$(< "${base_input_file}")"\ - "---- EXPECTED: ----\n${expected_result}"\ - '-------------------' + Print_Error \ + "YAML replacement failed!" \ + "---- OBTAINED: ----\n$(< "${base_input_file}")" \ + "---- EXPECTED: ----\n${expected_result}" \ + '-------------------' return 1 fi #--------------------------------------------------------------------------- @@ -157,10 +159,11 @@ function Unit_Test__replace-in-software-input-TXT() expected_result=${expected_result%?} # Get rid of trailing endline Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then - Print_Error "YAML replacement failed!"\ - "---- OBTAINED: ----\n$(< "${base_input_file}")"\ - "---- EXPECTED: ----\n${expected_result}"\ - '-------------------' + Print_Error \ + "YAML replacement failed!" \ + "---- OBTAINED: ----\n$(< "${base_input_file}")" \ + "---- EXPECTED: ----\n${expected_result}" \ + '-------------------' return 1 fi #--------------------------------------------------------------------------- @@ -170,10 +173,11 @@ function Unit_Test__replace-in-software-input-TXT() expected_result=${expected_result%?} # Get rid of trailing endline Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File if [[ "$(< "${base_input_file}")" != "${expected_result}" ]]; then - Print_Error "YAML replacement failed!"\ - "---- OBTAINED: ----\n$(< "${base_input_file}")"\ - "---- EXPECTED: ----\n${expected_result}"\ - '-------------------' + Print_Error \ + "YAML replacement failed!" \ + "---- OBTAINED: ----\n$(< "${base_input_file}")" \ + "---- EXPECTED: ----\n${expected_result}" \ + '-------------------' return 1 fi rm "${base_input_file}" diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 0650318..d1c2211 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -27,22 +27,22 @@ function __static__Inhibit_Commands_Version() { function awk() { - __static__Fake_Command_Version\ + __static__Fake_Command_Version \ '--version' "${gnu} Awk ${awk_version}, API: 3.0 (${gnu} MPFR 4.1.0, ${gnu} MP 6.2.1)" "$@" } function sed() { - __static__Fake_Command_Version\ + __static__Fake_Command_Version \ '--version' "sed (${gnu} sed) ${sed_version} Packaged by Debian" "$@" } function tput() { - __static__Fake_Command_Version\ + __static__Fake_Command_Version \ '-V' "ncurses ${tput_version}" "$@" } function yq() { - __static__Fake_Command_Version\ + __static__Fake_Command_Version \ '--version' "yq (https://github.com/mikefarah/yq/) version v${yq_version}" "$@" } } diff --git a/tests/utility_functions_functional.bash b/tests/utility_functions_functional.bash index 9992483..82f4a52 100644 --- a/tests/utility_functions_functional.bash +++ b/tests/utility_functions_functional.bash @@ -28,12 +28,12 @@ function Check_If_Software_Produced_Expected_Output() unfinished_files=( "${folder}"/*.{unfinished,lock} ) output_files=( "${folder}"/* ) if [[ ${#unfinished_files[@]} -gt 0 ]]; then - exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit\ + exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit \ 'Some unexpected ' --emph '.{unfinished,lock}' ' output file remained'\ 'in ' --emph "${folder}" return 1 elif [[ ${#output_files[@]} -ne ${expected_output_files} ]]; then - exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit\ + exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit \ 'Expected ' --emph "${expected_output_files}" ' output files in '\ --emph "${block}" " folder, but ${#output_files[@]} found." return 1 From 6bd2dac8f5479baa3a9e918654400db280d6390e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 30 Nov 2023 09:44:42 +0100 Subject: [PATCH 264/549] Format all codebase using shfmt with -kp option enabled --- Hybrid-handler | 12 +- bash/Afterburner_functionality.bash | 29 +++-- bash/Hydro_functionality.bash | 21 ++-- bash/IC_functionality.bash | 13 +- bash/Sampler_functionality.bash | 53 ++++---- bash/command_line_parsers/helper.bash | 68 +++++------ bash/command_line_parsers/main_parser.bash | 22 ++-- bash/command_line_parsers/sub_parser.bash | 1 - bash/configuration_parser.bash | 40 +++--- bash/dispatch_functions.bash | 1 - bash/global_variables.bash | 113 +++++++++-------- bash/sanity_checks.bash | 19 ++- bash/software_input_functionality.bash | 17 ++- bash/source_codebase_files.bash | 2 +- bash/system_requirements.bash | 114 +++++++++--------- bash/utility_functions.bash | 44 ++++--- bash/version.bash | 25 ++-- tests/command_line_parser_for_tests.bash | 79 ++++++------ tests/functional_tests.bash | 2 +- tests/functional_tests_Afterburner_only.bash | 6 +- tests/functional_tests_Hydro_only.bash | 8 +- tests/functional_tests_IC_only.bash | 6 +- tests/functional_tests_Sampler_only.bash | 8 +- tests/functional_tests_full_workflow.bash | 2 +- tests/functional_tests_version.bash | 5 +- tests/tests_runner | 40 +++--- .../unit_tests_Afterburner_functionality.bash | 24 ++-- tests/unit_tests_Hydro_functionality.bash | 2 +- tests/unit_tests_IC_functionality.bash | 2 +- tests/unit_tests_Sampler_functionality.bash | 24 ++-- tests/unit_tests_command_line_parser.bash | 42 +++---- tests/unit_tests_configuration.bash | 4 +- tests/unit_tests_formatting.bash | 12 +- tests/unit_tests_system_requirements.bash | 5 +- tests/unit_tests_utility_functions.bash | 42 +++---- tests/unit_tests_version.bash | 11 +- tests/utility_functions.bash | 14 +-- tests/utility_functions_functional.bash | 12 +- 38 files changed, 472 insertions(+), 472 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index 2cd8d5c..078e78d 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -80,7 +80,7 @@ function Define_Repository_Global_Path() function Store_Command_Line_Options_Into_Global_Variable() { - HYBRID_command_line_options_to_parse=( "$@" ) + HYBRID_command_line_options_to_parse=("$@") } function Source_Codebase_Files() @@ -91,13 +91,13 @@ function Source_Codebase_Files() function Act_And_Exit_If_User_Ran_Auxiliary_Modes() { case "${HYBRID_execution_mode}" in - *help ) + *help) Give_Required_Help ;;& - version ) + version) Print_Software_Version ;;& - *help | version ) + *help | version) exit ${HYBRID_success_exit_code} ;; esac @@ -106,7 +106,7 @@ function Act_And_Exit_If_User_Ran_Auxiliary_Modes() function Make_Needed_Operations_Depending_On_Execution_Mode() { case "${HYBRID_execution_mode}" in - do ) + do) local software_section Validate_And_Parse_Configuration_File Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables @@ -116,7 +116,7 @@ function Make_Needed_Operations_Depending_On_Execution_Mode() Run_Software "${software_section}" done ;; - * ) + *) Print_Internal_And_Exit "Unexpected execution mode at top-level." ;; esac diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index cba2c0d..544b4f2 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -17,8 +17,8 @@ function Prepare_Software_Input_File_Afterburner() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Afterburner]}" ' was not found.' fi - cp "${HYBRID_software_base_config_file[Afterburner]}"\ - "${HYBRID_software_configuration_file[Afterburner]}" || exit ${HYBRID_fatal_builtin} + cp "${HYBRID_software_base_config_file[Afterburner]}" \ + "${HYBRID_software_configuration_file[Afterburner]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Afterburner]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" @@ -27,31 +27,31 @@ function Prepare_Software_Input_File_Afterburner() if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then if [[ -f "${target_link_name}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The input file for the afterburner ' --emph "${target_link_name}"\ + 'The input file for the afterburner ' --emph "${target_link_name}" \ ' already exists.' # Here the config.yaml file is expected to be produced by SMASH in the output folder # during the IC run. It is used to determine the initial number of particles. elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml"\ + 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml" \ '\ndoes not exist, but is needed to check number of initial nucleons.' \ 'This file is expected to be produced by the IC software run.' elif [[ ! -f "${HYBRID_software_input_file[Spectators]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Spectator file ' --emph "${HYBRID_software_input_file[Spectators]}"\ + 'Spectator file ' --emph "${HYBRID_software_input_file[Spectators]}" \ ' does not exist.' fi "${HYBRID_external_python_scripts[Add_spectators_from_IC]}" \ '--sampled_particle_list' "${HYBRID_software_input_file[Afterburner]}" \ - '--initial_particle_list' "${HYBRID_software_input_file[Spectators]}"\ - '--output_file' "${target_link_name}"\ + '--initial_particle_list' "${HYBRID_software_input_file[Spectators]}" \ + '--output_file' "${target_link_name}" \ '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" else if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then ln -s -f "${HYBRID_software_input_file[Afterburner]}" "${target_link_name}" elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'File ' --emph "${target_link_name}" ' exists but it is not the Afterburner input file '\ + 'File ' --emph "${target_link_name}" ' exists but it is not the Afterburner input file ' \ --emph "${HYBRID_software_input_file[Afterburner]}" ' to be used.' fi fi @@ -65,13 +65,13 @@ function Ensure_All_Needed_Input_Exists_Afterburner() fi if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}"\ + 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" \ ' does not exist.' fi # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary. if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0"\ + 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0" \ ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then Print_Internal_And_Exit \ @@ -84,11 +84,10 @@ function Run_Software_Afterburner() cd "${HYBRID_software_output_directory[Afterburner]}" local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" "${HYBRID_software_executable[Afterburner]}" \ - '-i' "${HYBRID_software_configuration_file[Afterburner]}" \ - '-o' "${HYBRID_software_output_directory[Afterburner]}" \ - '-n' \ - >> "${afterburner_terminal_output}" + '-i' "${HYBRID_software_configuration_file[Afterburner]}" \ + '-o' "${HYBRID_software_output_directory[Afterburner]}" \ + '-n' \ + >> "${afterburner_terminal_output}" } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index e74af2d..4bb1f53 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -17,8 +17,8 @@ function Prepare_Software_Input_File_Hydro() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Hydro]}" ' was not found.' fi - cp "${HYBRID_software_base_config_file[Hydro]}"\ - "${HYBRID_software_configuration_file[Hydro]}" || exit ${HYBRID_fatal_builtin} + cp "${HYBRID_software_base_config_file[Hydro]}" \ + "${HYBRID_software_configuration_file[Hydro]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Hydro]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'TXT' "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_new_input_keys[Hydro]}" @@ -30,7 +30,7 @@ function Prepare_Software_Input_File_Hydro() ln -s -f "${HYBRID_software_input_file[Hydro]}" "${target_link_name}" elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Hydro]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file '\ + 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file ' \ --emph "${HYBRID_software_input_file[Hydro]}" ' to be used.' fi # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software @@ -46,19 +46,19 @@ function Prepare_Software_Input_File_Hydro() if [[ -d "${link_to_eos_folder}" ]]; then if [[ ! "${link_to_eos_folder}" -ef "${eos_folder}" ]]; then if [[ -L "${link_to_eos_folder}" ]]; then - Print_Warning 'Found a symlink ' --emph "${HYBRID_software_output_directory[Hydro]}/eos"\ + Print_Warning 'Found a symlink ' --emph "${HYBRID_software_output_directory[Hydro]}/eos" \ '\npointing to a different eos folder. Unlink and link again!\n' unlink "${HYBRID_software_output_directory[Hydro]}/eos" ln -s "${eos_folder}" "${link_to_eos_folder}" else exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'A ' --emph 'eos' ' folder called already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ + 'A ' --emph 'eos' ' folder called already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}" \ '.' 'Please remove it and run the hybrid handler again.' fi fi elif [[ -e "${link_to_eos_folder}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'A ' --emph 'eos' ' file already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}"\ + 'A ' --emph 'eos' ' file already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}" \ '.' 'Please remove it and run the hybrid handler again.' else ln -s "${eos_folder}" "${link_to_eos_folder}" @@ -77,7 +77,7 @@ function Ensure_All_Needed_Input_Exists_Hydro() fi if [[ ! -e "${HYBRID_software_input_file[Hydro]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The input file ' --emph "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat"\ + 'The input file ' --emph "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then Print_Internal_And_Exit \ @@ -89,12 +89,11 @@ function Run_Software_Hydro() { cd "${HYBRID_software_output_directory[Hydro]}" local -r \ - hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}"\ - ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat"\ + hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ + ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" - "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}"\ + "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}" \ "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index e6a1732..d36db90 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -17,8 +17,8 @@ function Prepare_Software_Input_File_IC() exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'Base configuration file ' --emph "${HYBRID_software_base_config_file[IC]}" ' was not found.' fi - cp "${HYBRID_software_base_config_file[IC]}"\ - "${HYBRID_software_configuration_file[IC]}" || exit ${HYBRID_fatal_builtin} + cp "${HYBRID_software_base_config_file[IC]}" \ + "${HYBRID_software_configuration_file[IC]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[IC]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'YAML' "${HYBRID_software_configuration_file[IC]}" "${HYBRID_software_new_input_keys[IC]}" @@ -42,11 +42,10 @@ function Run_Software_IC() cd "${HYBRID_software_output_directory[IC]}" local ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" "${HYBRID_software_executable[IC]}" \ - '-i' "${HYBRID_software_configuration_file[IC]}" \ - '-o' "${HYBRID_software_output_directory[IC]}" \ - '-n' \ - >> "${ic_terminal_output}" + '-i' "${HYBRID_software_configuration_file[IC]}" \ + '-o' "${HYBRID_software_output_directory[IC]}" \ + '-n' \ + >> "${ic_terminal_output}" } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index de1833f..25fc273 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -12,18 +12,18 @@ function Prepare_Software_Input_File_Sampler() mkdir -p "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} if [[ -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}"\ + 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}" \ ' is already existing.' elif [[ ! -f "${HYBRID_software_base_config_file[Sampler]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Sampler]}"\ + 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Sampler]}" \ ' was not found.' fi - cp "${HYBRID_software_base_config_file[Sampler]}"\ - "${HYBRID_software_configuration_file[Sampler]}" || exit ${HYBRID_fatal_builtin} + cp "${HYBRID_software_base_config_file[Sampler]}" \ + "${HYBRID_software_configuration_file[Sampler]}" || exit ${HYBRID_fatal_builtin} if [[ "${HYBRID_software_new_input_keys[Sampler]}" != '' ]]; then Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ - 'TXT' "${HYBRID_software_configuration_file[Sampler]}"\ + 'TXT' "${HYBRID_software_configuration_file[Sampler]}" \ "${HYBRID_software_new_input_keys[Sampler]}" fi if ! __static__Is_Sampler_Config_Valid; then @@ -35,15 +35,15 @@ function Prepare_Software_Input_File_Sampler() freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') output_directory=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'spectra_dir') Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ - 'TXT' "${HYBRID_software_configuration_file[Sampler]}"\ - "$(printf "%s: %s\n"\ - 'surface' "${freezeout_path}"\ + 'TXT' "${HYBRID_software_configuration_file[Sampler]}" \ + "$(printf "%s: %s\n" \ + 'surface' "${freezeout_path}" \ 'spectra_dir' "${output_directory}")" # The following symbolic link is not needed by the sampler, as the sampler only refers to information # specified in its input file. However, we want to have all input for a software in the output folder # for future easier reproducibility (and we do so for all software handled in the codebase). - if [[ "$( dirname "${freezeout_path}" )" != "${HYBRID_software_output_directory[Sampler]}" ]]; then - ln -s "${freezeout_path}"\ + if [[ "$( dirname "${freezeout_path}")" != "${HYBRID_software_output_directory[Sampler]}" ]]; then + ln -s "${freezeout_path}" \ "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" fi } @@ -52,12 +52,12 @@ function Ensure_All_Needed_Input_Exists_Sampler() { if [[ ! -d "${HYBRID_software_output_directory[Sampler]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}"\ + 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}" \ ' does not exist.' fi if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}"\ + 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}" \ ' was not found.' fi # This is already done preparing the input file, but it's logically belonging here, too. @@ -77,13 +77,12 @@ function Run_Software_Sampler() "${sampler_config_file_path}" >> "${sampler_terminal_output}" } - function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() { local field value field="$1" # We assume here that the configuration file is fine as it was validated before - value=$(awk -v name="${field}" '$1 == name {print $2; exit}'\ + value=$(awk -v name="${field}" '$1 == name {print $2; exit}' \ "${HYBRID_software_configuration_file[Sampler]}") cd "${HYBRID_software_output_directory[Sampler]}" # If realpath succeeds, it prints the path that is the result of the function @@ -94,7 +93,8 @@ function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() cd - > /dev/null } -function __static__Is_Sampler_Config_Valid() { +function __static__Is_Sampler_Config_Valid() + { local -r config_file="${HYBRID_software_configuration_file[Sampler]}" # Remove empty lines from configuration file if ! sed -i '/^[[:space:]]*$/d' "${config_file}"; then @@ -134,41 +134,41 @@ function __static__Is_Sampler_Config_Valid() { return 1 fi case "${key}" in - surface ) + surface) cd "${HYBRID_software_output_directory[Sampler]}" if [[ ! -f "${value}" ]]; then cd - > /dev/null Print_Error 'Freeze-out surface file ' --emph "${value}" ' not found!' return 1 fi - (( keys_to_be_found-- )) + ((keys_to_be_found--)) ;; - spectra_dir ) + spectra_dir) cd "${HYBRID_software_output_directory[Sampler]}" if [[ ! -d "${value}" ]]; then cd - > /dev/null Print_Error 'Sampler output folder ' --emph "${value}" ' not found!' return 1 fi - (( keys_to_be_found-- )) + ((keys_to_be_found--)) ;; - rescatter | weakContribution | shear ) + rescatter | weakContribution | shear) if [[ ! "${value}" =~ ^[01]$ ]]; then - Print_Error 'Key ' --emph "${key}" ' must be either '\ + Print_Error 'Key ' --emph "${key}" ' must be either ' \ --emph '0' ' or ' --emph '1' '.' return 1 fi ;; - number_of_events | Nbins ) + number_of_events | Nbins) if [[ ! "${value}" =~ ^[1-9][0-9]*$ ]]; then - Print_Error 'Found not-integer value ' --emph "${value}"\ + Print_Error 'Found not-integer value ' --emph "${value}" \ ' for ' --emph "${key}" ' key.' return 1 fi ;; - * ) + *) if [[ ! "${value}" =~ ^[+-]?[0-9]+(\.[0-9]*)?$ ]]; then - Print_Error 'Found invalid value ' --emph "${value}"\ + Print_Error 'Found invalid value ' --emph "${value}" \ ' for ' --emph "${key}" ' key.' return 1 fi @@ -177,11 +177,10 @@ function __static__Is_Sampler_Config_Valid() { done < "${config_file}" # Check that all required keys were found if [[ ${keys_to_be_found} -gt 0 ]]; then - Print_Error 'Either ' --emph 'surface' ' or ' --emph 'spectra_dir'\ + Print_Error 'Either ' --emph 'surface' ' or ' --emph 'spectra_dir' \ ' key is missing in sampler configuration file.' return 1 fi } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index c9f46d2..a0148e4 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -10,15 +10,15 @@ function Give_Required_Help() { case "${HYBRID_execution_mode}" in - help ) + help) __static__Print_Main_Help_Message ;; - do-help ) + do-help) __static__Print_Do_Help_Message ;; - * ) + *) Print_Internal_And_Exit \ - 'Unexpected value of ' --emph "HYBRID_execution_mode=${HYBRID_execution_mode}"\ + 'Unexpected value of ' --emph "HYBRID_execution_mode=${HYBRID_execution_mode}" \ ' in ' --emph "${FUNCNAME}" ;; esac @@ -31,15 +31,15 @@ function __static__Print_Main_Help_Message() # that handler prerequisites are missing. Hence, possibly only bash should be used. declare -A section_headers auxiliary_modes_description execution_modes_description section_headers=( - [auxiliary_modes_description]='Auxiliary modes for help, information or setup' - [execution_modes_description]='Prepare needed files and folders and/or submit/run new simulation(s)' + [auxiliary_modes_description]='Auxiliary modes for help, information or setup' + [execution_modes_description]='Prepare needed files and folders and/or submit/run new simulation(s)' ) auxiliary_modes_description=( - [help]='Display this help message' - [version]='Get information about the version in use' + [help]='Display this help message' + [version]='Get information about the version in use' ) execution_modes_description=( - [do]='Do everything is necessary to run the workflow given in the configuration file' + [do]='Do everything is necessary to run the workflow given in the configuration file' ) __static__Print_Handler_Header_And_Usage_Synopsis __static__Print_Modes_Description @@ -48,31 +48,31 @@ function __static__Print_Main_Help_Message() function __static__Print_Do_Help_Message() { - printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n'\ + printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n' \ 'You can specify the following command line options to the' 'do' 'execution mode:' __static__Print_Command_Line_Option_Help \ - '-o | --output-directory' "${HYBRID_output_directory}"\ + '-o | --output-directory' "${HYBRID_output_directory}" \ "Directory where the run folder(s) will be created." __static__Print_Command_Line_Option_Help \ - '-c | --configuration-file' "${HYBRID_configuration_file}"\ + '-c | --configuration-file' "${HYBRID_configuration_file}" \ "YAML configuration file to be used by the handler." } function __static__Print_Handler_Header_And_Usage_Synopsis() { - printf '\e[96m%s\e[0m\n'\ - ' #----------------------------------------------------------------------------#'\ - ' # __ __ __ _ __ __ __ ____ #'\ - ' # / / / /_ __/ /_ _____(_)___/ / / / / /___ _____ ____/ / /__ _____ #'\ - ' # / /_/ / / / / __ \/ ___/ / __ / / /_/ / __ `/ __ \/ __ / / _ \/ ___/ #'\ - ' # / __ / /_/ / /_/ / / / / /_/ / / __ / /_/ / / / / /_/ / / __/ / #'\ - ' # /_/ /_/\__, /_.___/_/ /_/\__,_/ /_/ /_/\__,_/_/ /_/\__,_/_/\___/_/ #'\ - ' # /____/ #'\ - ' # #'\ + printf '\e[96m%s\e[0m\n' \ + ' #----------------------------------------------------------------------------#' \ + ' # __ __ __ _ __ __ __ ____ #' \ + ' # / / / /_ __/ /_ _____(_)___/ / / / / /___ _____ ____/ / /__ _____ #' \ + ' # / /_/ / / / / __ \/ ___/ / __ / / /_/ / __ `/ __ \/ __ / / _ \/ ___/ #' \ + ' # / __ / /_/ / /_/ / / / / /_/ / / __ / /_/ / / / / /_/ / / __/ / #' \ + ' # /_/ /_/\__, /_.___/_/ /_/\__,_/ /_/ /_/\__,_/_/ /_/\__,_/_/\___/_/ #' \ + ' # /____/ #' \ + ' # #' \ ' #----------------------------------------------------------------------------#' printf '\n' - printf '\e[38;5;85m%s\e[0m\n'\ - ' USAGE: Hybrid-handler [--help] [--version]'\ + printf '\e[38;5;85m%s\e[0m\n' \ + ' USAGE: Hybrid-handler [--help] [--version]' \ ' [...]' printf '\n' } @@ -81,7 +81,7 @@ function __static__Print_Modes_Description() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty section_headers "${!section_headers[@]}" local section mode section_string - printf '\e[38;5;38m %s\e[0m\n'\ + printf '\e[38;5;38m %s\e[0m\n' \ 'Here in the following you find an overview of the existing execution modes.' for section in "${!section_headers[@]}"; do printf "\n \e[93m${section_headers[${section}]}\e[0m\n" @@ -91,8 +91,8 @@ function __static__Print_Modes_Description() # Remember that $(...) strip trailing '\n' -> Add new-line manually to the string section_string+="$( printf \ - '\e[38;5;85m%15s \e[96m%s\e[0m'\ - "${mode}"\ + '\e[38;5;85m%15s \e[96m%s\e[0m' \ + "${mode}" \ "${list_of_modes[${mode}]}" )"$'\n' done @@ -104,19 +104,18 @@ function __static__Print_Modes_Description() printf "%s" "${section_string}" fi done - printf '\n\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n\n'\ + printf '\n\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n\n' \ 'Use' '--help' 'after each non auxiliary mode to get further information about it.' } function __static__Print_Command_Line_Option_Help() { local -r \ - length_option=30\ - indentation=' '\ - column_sep=' '\ - options_color='\e[38;5;85m'\ - text_color='\e[96m'\ - default_value_color='\e[93m'\ + length_option=30 indentation=' ' \ + column_sep=' ' \ + options_color='\e[38;5;85m' \ + text_color='\e[96m' \ + default_value_color='\e[93m' \ default_text='\e[0m' local name default_value description left_column left_column_length name=$1 @@ -124,7 +123,7 @@ function __static__Print_Command_Line_Option_Help() description=$3 shift 3 printf -v left_column "${indentation}${options_color}%*s${column_sep}" ${length_option} "${name}" - left_column_length=$(( ${#indentation} + ${length_option} + ${#column_sep} )) + left_column_length=$((${#indentation} + ${length_option} + ${#column_sep})) printf "\n${left_column}${text_color}%s${default_text}\n" "${description}" while [[ $# -gt 0 ]]; do printf "%${left_column_length}s${text_color}%s${default_text}\n" '' "$1" @@ -133,5 +132,4 @@ function __static__Print_Command_Line_Option_Help() printf "%${left_column_length}sDefault: ${default_value_color}${default_value}${default_text}\n" '' } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index c25ef5d..1bbb3cf 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -15,23 +15,24 @@ function Parse_Execution_Mode() #Locally set function arguments to take advantage of shift set -- "${HYBRID_command_line_options_to_parse[@]}" case "$1" in - help | --help ) + help | --help) HYBRID_execution_mode='help' ;; - version | --version ) + version | --version) HYBRID_execution_mode='version' ;; - do ) + do) HYBRID_execution_mode='do' ;; - * ) + *) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ - 'Specified mode ' --emph "$1" ' not valid! Run '\ + 'Specified mode ' --emph "$1" ' not valid! Run ' \ --emph 'Hybrid-handler help' ' to get further information.' + ;; esac shift # Update the global array with remaining options to be parsed - HYBRID_command_line_options_to_parse=( "$@" ) + HYBRID_command_line_options_to_parse=("$@") # Ignore any command line option in some specific cases if [[ ${HYBRID_execution_mode} =~ ^(help|version)$ ]]; then HYBRID_command_line_options_to_parse=() @@ -49,7 +50,7 @@ function Parse_Command_Line_Options() set -- "${HYBRID_command_line_options_to_parse[@]}" while [[ $# -gt 0 ]]; do case "$1" in - -o | --output-directory ) + -o | --output-directory) if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else @@ -61,7 +62,7 @@ function Parse_Command_Line_Options() fi shift 2 ;; - -c | --configuration-file ) + -c | --configuration-file) if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else @@ -69,14 +70,13 @@ function Parse_Command_Line_Options() fi shift 2 ;; - * ) + *) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ - 'Invalid option ' --emph "$1" ' specified in ' --emph "${HYBRID_execution_mode}"\ + 'Invalid option ' --emph "$1" ' specified in ' --emph "${HYBRID_execution_mode}" \ ' execution mode!' 'Use the ' --emph '--help' ' option to get further information.' ;; esac done } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/command_line_parsers/sub_parser.bash b/bash/command_line_parsers/sub_parser.bash index 8c4660d..105d8d8 100644 --- a/bash/command_line_parsers/sub_parser.bash +++ b/bash/command_line_parsers/sub_parser.bash @@ -6,4 +6,3 @@ # GNU General Public License (GPLv3 or later) # #=================================================== - diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 9f13e20..25629a2 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -38,7 +38,7 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() { local input_section_labels label index valid_software_label software_sections_indices # Here word splitting separates keys into array entries, hence we assume keys do not contain spaces! - input_section_labels=( $(yq 'keys | .[]' "${HYBRID_configuration_file}") ) + input_section_labels=($( yq 'keys | .[]' "${HYBRID_configuration_file}")) software_sections_indices=() for label in "${input_section_labels[@]}"; do if Element_In_Array_Equals_To "${label}" "${HYBRID_valid_auxiliary_configuration_sections[@]}"; then @@ -47,7 +47,7 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() for index in "${!HYBRID_valid_software_configuration_sections[@]}"; do valid_software_label=${HYBRID_valid_software_configuration_sections[index]} if [[ "${valid_software_label}" = "${label}" ]]; then - software_sections_indices+=( ${index} ) + software_sections_indices+=(${index}) continue 2 fi done @@ -60,8 +60,7 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'No software section was specified in the handler configuration file.' elif [[ ${#software_sections_indices[@]} -gt 1 ]]; then - if [[ $(sort -u <(printf '%d\n' "${software_sections_indices[@]}") | wc -l)\ - -ne ${#software_sections_indices[@]} ]]; then + if [[ $(sort -u <(printf '%d\n' "${software_sections_indices[@]}") | wc -l) -ne ${#software_sections_indices[@]} ]]; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'The same software section in the handler configuration file cannot be repeated.' fi @@ -70,7 +69,7 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() 'Software sections in the handler configuration file are out of order.' fi local gaps_between_indices - gaps_between_indices=$(awk 'NR>1{print $1-x}{x=$1}'\ + gaps_between_indices=$(awk 'NR>1{print $1-x}{x=$1}' \ <(printf '%d\n' "${software_sections_indices[@]}") | sort -u) if [[ "${gaps_between_indices}" != '1' ]]; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ @@ -85,26 +84,26 @@ function __static__Abort_If_Invalid_Keys_Were_Used() local valid_keys invalid_report invalid_report=() # Use array entries to split lines to feed into logger # Hybrid_handler section - valid_keys=( "${!HYBRID_hybrid_handler_valid_keys[@]}" ) + valid_keys=("${!HYBRID_hybrid_handler_valid_keys[@]}") __static__Validate_Keys_Of_Section 'Hybrid_handler' # IC section - valid_keys=( "${!HYBRID_ic_valid_keys[@]}" ) + valid_keys=("${!HYBRID_ic_valid_keys[@]}") __static__Validate_Keys_Of_Section 'IC' # Hydro section - valid_keys=( "${!HYBRID_hydro_valid_keys[@]}" ) + valid_keys=("${!HYBRID_hydro_valid_keys[@]}") __static__Validate_Keys_Of_Section 'Hydro' # Sampler section - valid_keys=( "${!HYBRID_sampler_valid_keys[@]}" ) + valid_keys=("${!HYBRID_sampler_valid_keys[@]}") __static__Validate_Keys_Of_Section 'Sampler' # Afterburner section - valid_keys=( "${!HYBRID_afterburner_valid_keys[@]}" ) + valid_keys=("${!HYBRID_afterburner_valid_keys[@]}") __static__Validate_Keys_Of_Section 'Afterburner' # Abort if some validation failed if [[ "${#invalid_report[@]}" -ne 0 ]]; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ - 'Following invalid keys found in the handler configuration file:'\ - '---------------------------------------------------------------'\ - "${invalid_report[@]/%/:}"\ + 'Following invalid keys found in the handler configuration file:' \ + '---------------------------------------------------------------' \ + "${invalid_report[@]/%/:}" \ '---------------------------------------------------------------' fi } @@ -117,9 +116,9 @@ function __static__Validate_Keys_Of_Section() local invalid_keys yaml_section if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" - invalid_keys=( $(__static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string "${yaml_section}") ) + invalid_keys=($( __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string "${yaml_section}")) if [[ ${#invalid_keys[@]} -ne 0 ]]; then - invalid_report+=( " ${section_label}" "${invalid_keys[@]/#/ }") + invalid_report+=(" ${section_label}" "${invalid_keys[@]/#/ }") fi fi } @@ -129,11 +128,11 @@ function __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string() { Ensure_That_Given_Variables_Are_Set valid_keys local input_keys key invalid_keys - input_keys=( $(yq 'keys | .[]' <<< "$1") ) + input_keys=($( yq 'keys | .[]' <<< "$1")) invalid_keys=() for key in "${input_keys[@]}"; do if ! Element_In_Array_Equals_To "${key}" "${valid_keys[@]}"; then - invalid_keys+=( "${key}" ) + invalid_keys+=("${key}") fi done printf '%s ' "${invalid_keys[@]}" @@ -142,13 +141,13 @@ function __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string() function __static__Parse_Section() { local -r \ - section_label=$1\ + section_label=$1 \ yaml_config="$(< "${HYBRID_configuration_file}")" local yaml_section valid_key if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" if [[ ${section_label} != 'Hybrid_handler' ]]; then - HYBRID_given_software_sections+=( "${section_label}" ) + HYBRID_given_software_sections+=("${section_label}") fi declare -n reference_to_map="HYBRID_${section_label,,}_valid_keys" for valid_key in "${!reference_to_map[@]}"; do @@ -175,10 +174,9 @@ function __static__YAML_section_must_be_empty() local yaml_section=$1 if [[ "${yaml_section}" != '{}' ]]; then Print_Internal_And_Exit \ - 'Not all keys in ' --emph "${2:-some}" ' section have been parsed. Remaining:'\ + 'Not all keys in ' --emph "${2:-some}" ' section have been parsed. Remaining:' \ --emph "\n${yaml_section}\n" fi } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/dispatch_functions.bash b/bash/dispatch_functions.bash index fffb47b..ffcc256 100644 --- a/bash/dispatch_functions.bash +++ b/bash/dispatch_functions.bash @@ -22,5 +22,4 @@ function Run_Software() Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 932d6e7..16a4210 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -29,47 +29,47 @@ function Define_Further_Global_Variables() readonly HYBRID_default_configurations_folder="${HYBRID_top_level_path}/configs" readonly HYBRID_python_folder="${HYBRID_top_level_path}/python" declare -rgA HYBRID_external_python_scripts=( - [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" + [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" ) declare -rgA HYBRID_software_default_input_filename=( - [IC]='' - [Hydro]="SMASH_IC.dat" - [Sampler]="freezeout.dat" # Not used at the moment for how the sampler works - [Spectators]="SMASH_IC.oscar" - [Afterburner]="particle_lists.oscar" + [IC]='' + [Hydro]="SMASH_IC.dat" + [Sampler]="freezeout.dat" # Not used at the moment for how the sampler works + [Spectators]="SMASH_IC.oscar" + [Afterburner]="particle_lists.oscar" ) declare -rgA HYBRID_software_configuration_filename=( - [IC]='IC_config.yaml' - [Hydro]='hydro_config.txt' - [Sampler]='sampler_config.txt' - [Afterburner]='afterburner_config.yaml' + [IC]='IC_config.yaml' + [Hydro]='hydro_config.txt' + [Sampler]='sampler_config.txt' + [Afterburner]='afterburner_config.yaml' ) # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=() declare -rgA HYBRID_ic_valid_keys=( - [Executable]='HYBRID_software_executable[IC]' - [Config_file]='HYBRID_software_base_config_file[IC]' - [Software_keys]='HYBRID_software_new_input_keys[IC]' + [Executable]='HYBRID_software_executable[IC]' + [Config_file]='HYBRID_software_base_config_file[IC]' + [Software_keys]='HYBRID_software_new_input_keys[IC]' ) declare -rgA HYBRID_hydro_valid_keys=( - [Executable]='HYBRID_software_executable[Hydro]' - [Config_file]='HYBRID_software_base_config_file[Hydro]' - [Input_file]='HYBRID_software_user_custom_input_file[Hydro]' - [Software_keys]='HYBRID_software_new_input_keys[Hydro]' + [Executable]='HYBRID_software_executable[Hydro]' + [Config_file]='HYBRID_software_base_config_file[Hydro]' + [Input_file]='HYBRID_software_user_custom_input_file[Hydro]' + [Software_keys]='HYBRID_software_new_input_keys[Hydro]' ) declare -rgA HYBRID_sampler_valid_keys=( - [Executable]='HYBRID_software_executable[Sampler]' - [Config_file]='HYBRID_software_base_config_file[Sampler]' - [Software_keys]='HYBRID_software_new_input_keys[Sampler]' + [Executable]='HYBRID_software_executable[Sampler]' + [Config_file]='HYBRID_software_base_config_file[Sampler]' + [Software_keys]='HYBRID_software_new_input_keys[Sampler]' ) declare -rgA HYBRID_afterburner_valid_keys=( - [Executable]='HYBRID_software_executable[Afterburner]' - [Config_file]='HYBRID_software_base_config_file[Afterburner]' - [Input_file]='HYBRID_software_user_custom_input_file[Afterburner]' - [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' - [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' - [Spectators_source]='HYBRID_optional_feature[Spectators_source]' + [Executable]='HYBRID_software_executable[Afterburner]' + [Config_file]='HYBRID_software_base_config_file[Afterburner]' + [Input_file]='HYBRID_software_user_custom_input_file[Afterburner]' + [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' + [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' + [Spectators_source]='HYBRID_optional_feature[Spectators_source]' ) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' @@ -78,53 +78,52 @@ function Define_Further_Global_Variables() # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_given_software_sections=() declare -gA HYBRID_software_executable=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) declare -gA HYBRID_software_user_custom_input_file=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Spectators]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Spectators]='' + [Afterburner]='' ) declare -gA HYBRID_software_base_config_file=( - [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" - [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" - [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" - [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" + [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" + [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" + [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" + [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" ) declare -gA HYBRID_software_new_input_keys=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) declare -gA HYBRID_optional_feature=( - [Add_spectators_from_IC]='FALSE' - [Spectators_source]='' + [Add_spectators_from_IC]='FALSE' + [Spectators_source]='' ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) declare -gA HYBRID_software_configuration_file=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) declare -gA HYBRID_software_input_file=( - [Hydro]='' - [Spectators]='' - [Afterburner]='' + [Hydro]='' + [Spectators]='' + [Afterburner]='' ) } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index b224381..6028c79 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -17,16 +17,16 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then __static__Ensure_Executable_Exists "${key}" printf -v HYBRID_software_configuration_file[${key}] \ - "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_configuration_filename[${key}]}" + "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_configuration_filename[${key}]}" # Set here input data file of software if it was not set by user if [[ ${key} =~ ^(Hydro|Afterburner)$ ]]; then local filename relative_key filename="${HYBRID_software_user_custom_input_file[${key}]}" case "${key}" in - Hydro ) + Hydro) relative_key='IC' ;; - Afterburner ) + Afterburner) relative_key='Sampler' ;; esac @@ -45,7 +45,7 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var fi fi done - if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]];then + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then if [[ "${HYBRID_optional_feature[Spectators_source]}" != '' ]]; then HYBRID_software_input_file['Spectators']="${HYBRID_optional_feature[Spectators_source]}" if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then @@ -85,11 +85,11 @@ function __static__Ensure_Executable_Exists() elif [[ "${executable}" = / ]]; then if [[ ! -f "${executable}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The executable file for the ' --emph "${label}" ' run was not found.'\ + 'The executable file for the ' --emph "${label}" ' run was not found.' \ 'Not existing path: ' --emph "${file_path}" elif [[ ! -x "${executable}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The executable file for the ' --emph "${label}" ' run is not executable.'\ + 'The executable file for the ' --emph "${label}" ' run is not executable.' \ 'File path: ' --emph "${file_path}" fi # It is important to perform this check with 'type' and not with 'hash' because 'hash' with @@ -97,12 +97,11 @@ function __static__Ensure_Executable_Exists() # This will be entered if the user gives something stupid as '~' as executable. elif ! type -P "${executable}" &> /dev/null; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The command ' --emph "${executable}" ' specified for the '\ - --emph "${label}" ' run was not located by the shell.'\ - 'Please check your ' --emph 'PATH' ' environment variable and make sure'\ + 'The command ' --emph "${executable}" ' specified for the ' \ + --emph "${label}" ' run was not located by the shell.' \ + 'Please check your ' --emph 'PATH' ' environment variable and make sure' \ 'that '--emph "type -P \"${executable}\"" ' succeeds in your terminal.' fi } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 9688070..15c19eb 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -16,13 +16,13 @@ function Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File() keys_to_be_replaced=$3 Remove_Comments_In_File "${base_input_file}" # this checks for existence, too case "${style}" in - YAML ) + YAML) __static__Replace_Keys_Into_YAML_File ;; - TXT ) + TXT) __static__Replace_Keys_Into_Txt_File ;; - * ) + *) Print_Internal_And_Exit "Wrong first argument passed to \"${FUNCNAME}\"." ;; esac @@ -34,8 +34,8 @@ function __static__Replace_Keys_Into_YAML_File() # Use yq -P to bring all YAML to same format (crucial for later check on number of lines) if ! yq -P --inplace "${base_input_file}" 2> /dev/null; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ - 'File ' --emph "${base_input_file}" ' does not seem to contain valid YAML syntax. Run'\ - --emph " yq -P --inplace \"${base_input_file}\""\ + 'File ' --emph "${base_input_file}" ' does not seem to contain valid YAML syntax. Run' \ + --emph " yq -P --inplace \"${base_input_file}\"" \ "\nto have more information about the problem." elif ! keys_to_be_replaced=$(yq -P <(printf "${keys_to_be_replaced}\n") 2> /dev/null); then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ @@ -44,14 +44,14 @@ function __static__Replace_Keys_Into_YAML_File() local initial_number_of_lines initial_number_of_lines=$(wc -l < "${base_input_file}") # Use yq to merge the two "files" into the first one - yq --inplace eval-all '. as $item ireduce ({}; . * $item)'\ - "${base_input_file}"\ + yq --inplace eval-all '. as $item ireduce ({}; . * $item)' \ + "${base_input_file}" \ <(printf "${keys_to_be_replaced}\n") # The merge must not have changed the number of lines of input file. If it did, # it means that some key was not present and has been appended => Error! if [[ $(wc -l < "${base_input_file}") -ne ${initial_number_of_lines} ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'One or more provided software input keys were not found to be replaced'\ + 'One or more provided software input keys were not found to be replaced' \ 'in the ' --emph "${base_input_file}" ' file.' 'Please, check your configuration file.' fi } @@ -99,5 +99,4 @@ function __static__Replace_Keys_Into_Txt_File() awk -i inplace 'BEGIN{FS=":"}{printf("%-20s%s\n", $1, $2)}' "${base_input_file}" } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 85251ee..bc35a91 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -13,7 +13,7 @@ function __static__Source_Codebase_Files() # Source error codes and fail with hard-coded generic error source "${HYBRID_top_level_path}/bash/error_codes.bash" || exit 1 # Source logger using fd 9 (not too small and still smaller than 10 as bash manual suggests) - source "${HYBRID_top_level_path}/bash/logger.bash"\ + source "${HYBRID_top_level_path}/bash/logger.bash" \ --fd 9 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} # Source utility functions to be able to mark all functions as readonly # when sourcing the codebase files (otherwise this would not work) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 4f0be44..434b4c5 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -19,11 +19,11 @@ function __static__Declare_System_Requirements() if ! declare -p HYBRID_version_regex &> /dev/null; then readonly HYBRID_version_regex='[0-9](.[0-9]+)*' declare -rgA HYBRID_versions_requirements=( - [awk]='4.1' - [bash]='4.4' - [sed]='4.2.1' - [tput]='5.7' - [yq]='4.18.1' + [awk]='4.1' + [bash]='4.4' + [sed]='4.2.1' + [tput]='5.7' + [yq]='4.18.1' ) declare -rga HYBRID_programs_just_required=( cat @@ -34,8 +34,8 @@ function __static__Declare_System_Requirements() realpath tail ) - declare -rga HYBRID_gnu_programs_required=( awk sed sort wc ) - declare -rga HYBRID_env_variables_required=( TERM ) + declare -rga HYBRID_gnu_programs_required=(awk sed sort wc) + declare -rga HYBRID_env_variables_required=(TERM) fi } @@ -156,9 +156,9 @@ function __static__Exit_If_Some_GNU_Requirement_Is_Missing() Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[GNU-${program}]" is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) if [[ ${is_gnu} = '---' ]]; then - Print_Error --emph "${program}" ' either not found or non-GNU version in use.'\ + Print_Error --emph "${program}" ' either not found or non-GNU version in use.' \ 'Please, ensure that ' --emph "${program}" ' is installed and in use.' - (( errors++ )) || true + ((errors++)) || true fi done if [[ ${errors} -ne 0 ]]; then @@ -176,20 +176,20 @@ function __static__Exit_If_Minimum_Versions_Are_Not_Available() version_found=$(__static__Get_Field_In_System_Information_String "${program}" 1) version_ok=$( __static__Get_Field_In_System_Information_String "${program}" 2) if [[ "${program_found}" = '---' ]]; then - Print_Error --emph "${program}" ' command not found! Minimum version '\ + Print_Error --emph "${program}" ' command not found! Minimum version ' \ --emph "${min_version}" ' is required.' - (( errors++ )) || true + ((errors++)) || true continue fi if [[ ${version_found} = '---' ]]; then - Print_Warning 'Unable to find version of ' --emph "${program}" ', skipping version check!'\ + Print_Warning 'Unable to find version of ' --emph "${program}" ', skipping version check!' \ 'Please ensure that current version is at least ' --emph "${min_version}" '.' continue fi if [[ "${version_ok}" = '---' ]]; then - Print_Error --emph "${program}" ' version ' --emph "${version_found}"\ + Print_Error --emph "${program}" ' version ' --emph "${version_found}" \ ' found, but version ' --emph "${min_version}" ' is required.' - (( errors++ )) || true + ((errors++)) || true fi done if [[ ${errors} -ne 0 ]]; then @@ -203,9 +203,9 @@ function __static__Exit_If_Some_Needed_Environment_Variable_Is_Missing() for name in "${HYBRID_env_variables_required[@]}"; do Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[${name}]" if [[ ${system_information[${name}]} = '---' ]]; then - Print_Error --emph "${name}" ' environment variable either unset or empty.'\ + Print_Error --emph "${name}" ' environment variable either unset or empty.' \ 'Please, ensure that ' --emph "${name}" ' is properly set.' - (( errors++ )) || true + ((errors++)) || true fi done if [[ ${errors} -ne 0 ]]; then @@ -242,26 +242,29 @@ function __static__Prepare_Binary_Report_Array() Ensure_That_Given_Variables_Are_Set_And_Not_Empty single_field_length for name in "${HYBRID_programs_just_required[@]}"; do system_report+=( - "$(__static__Get_Single_Tick_Cross_Requirement_Report \ - "PROG ${name}"\ - "${system_information[${name}]}" + "$( + __static__Get_Single_Tick_Cross_Requirement_Report \ + "PROG ${name}" \ + "${system_information[${name}]}" )" ) done for program in "${HYBRID_gnu_programs_required[@]}"; do is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) system_report+=( - "$(__static__Get_Single_Tick_Cross_Requirement_Report \ - "GNU ${program}"\ - "${is_gnu}" + "$( + __static__Get_Single_Tick_Cross_Requirement_Report \ + "GNU ${program}" \ + "${is_gnu}" )" ) done for name in "${HYBRID_env_variables_required[@]}"; do system_report+=( - "$(__static__Get_Single_Tick_Cross_Requirement_Report \ - "ENV ${name}"\ - "${system_information[${name}]}" + "$( + __static__Get_Single_Tick_Cross_Requirement_Report \ + "ENV ${name}" \ + "${system_information[${name}]}" )" ) done @@ -277,10 +280,10 @@ function __static__Print_Formatted_Binary_Report() # here (see https://stackoverflow.com/a/48016366/14967071 for more information). shopt -s checkwinsize # Do not assume it is on (as it usually is) ( : ) # Refresh LINES and COLUMNS, this happens when a child process exits - local -r num_cols=$(( ${COLUMNS-100} / 2 / single_field_length )) + local -r num_cols=$((${COLUMNS-100} / 2 / single_field_length)) local index printf_descriptor printf_descriptor="%${single_field_length}s" # At least one column - for ((index=1; index/dev/null; then + if hash "$1" 2> /dev/null; then return 0 else return 1 @@ -307,26 +310,26 @@ function __static__Try_Find_Version() fi local found_version case "$1" in - awk | sed ) + awk | sed) found_version=$($1 --version) found_version=$(__static__Get_First_Line_From_String "${found_version}") found_version=$(grep -oE "${HYBRID_version_regex}" <<< "${found_version}") found_version=$(__static__Get_First_Line_From_String "${found_version}") ;; - bash ) + bash) found_version="${BASH_VERSINFO[@]:0:3}" found_version="${found_version// /.}" ;; - tput ) + tput) found_version=$(tput -V | grep -oE "${HYBRID_version_regex}") - found_version=( ${found_version//./ } ) # Use word split to separate version numbers + found_version=(${found_version//./ }) # Use word split to separate version numbers found_version="${found_version[0]-}.${found_version[1]-}" # Use empty variables if 'tput -V' failed ;; - yq ) + yq) # Versions before v4.30.3 do not have the 'v' prefix - found_version=$(yq --version |\ - grep -oE "version [v]?${HYBRID_version_regex}" |\ - grep -oE "${HYBRID_version_regex}") + found_version=$(yq --version \ + | grep -oE "version [v]?${HYBRID_version_regex}" \ + | grep -oE "${HYBRID_version_regex}") ;; *) Print_Internal_And_Exit 'Version finding for ' --emph "$1" ' to be added!' @@ -386,14 +389,10 @@ function __static__Get_Larger_Version() dots_of_v2=${v2//[^.]/} if [[ ${#dots_of_v1} -lt ${#dots_of_v2} ]]; then declare -n \ - shorter_version=v1\ - dots_of_shorter_version=dots_of_v1\ - dots_of_longer_version=dots_of_v2 + shorter_version=v1 dots_of_shorter_version=dots_of_v1 dots_of_longer_version=dots_of_v2 else declare -n \ - shorter_version=v2\ - dots_of_shorter_version=dots_of_v2\ - dots_of_longer_version=dots_of_v1 + shorter_version=v2 dots_of_shorter_version=dots_of_v2 dots_of_longer_version=dots_of_v1 fi while [[ ${#dots_of_shorter_version} -ne ${#dots_of_longer_version} ]]; do shorter_version+='.0' # Add zeroes to shorter string @@ -406,8 +405,8 @@ function __static__Get_Larger_Version() fi # Split version strings into array of numbers replacing '.' by ' ' and let word splitting do the split local v{1,2}_array index - v1_array=( ${v1//./ } ) - v2_array=( ${v2//./ } ) + v1_array=(${v1//./ }) + v2_array=(${v2//./ }) # Now version arrays have same number of entries, compare them for index in ${!v1_array[@]}; do if [[ "${v1_array[index]}" -eq "${v2_array[index]}" ]]; then @@ -426,14 +425,14 @@ function __static__Print_Requirement_Version_Report_Line() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" local -r \ - emph_color='\e[96m'\ - red='\e[91m'\ - green='\e[92m'\ - yellow='\e[93m'\ - text_color='\e[38;5;38m'\ + emph_color='\e[96m' \ + red='\e[91m' \ + green='\e[92m' \ + yellow='\e[93m' \ + text_color='\e[38;5;38m' \ default='\e[0m' local line found version_found version_ok tmp_array program=$1 - tmp_array=( ${system_information[${program}]//|/ } ) # Unquoted to let word splitting act + tmp_array=(${system_information[${program}]//|/ }) # Unquoted to let word splitting act found=${tmp_array[0]} version_found=${tmp_array[1]} version_ok=${tmp_array[2]} @@ -443,7 +442,7 @@ function __static__Print_Requirement_Version_Report_Line() else line+="${green} " fi - line+=$(printf "found ${text_color}Required version: ${emph_color}%6s${default}"\ + line+=$(printf "found ${text_color}Required version: ${emph_color}%6s${default}" \ "${HYBRID_versions_requirements[${program}]}") if [[ ${found} != '---' ]]; then line+=" ${text_color}System version:${default} " @@ -466,11 +465,11 @@ function __static__Get_Single_Tick_Cross_Requirement_Report() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty single_field_length local -r \ - emph_color='\e[96m'\ - red='\e[91m'\ - green='\e[92m'\ - yellow='\e[93m'\ - text_color='\e[38;5;38m'\ + emph_color='\e[96m' \ + red='\e[91m' \ + green='\e[92m' \ + yellow='\e[93m' \ + text_color='\e[38;5;38m' \ default='\e[0m' local line name="$1" status=$2 name_string printf -v name_string "%s ${emph_color}%s" "${name% *}" "${name#* }" @@ -488,7 +487,7 @@ function __static__Get_Single_Tick_Cross_Requirement_Report() function __static__Get_Field_In_System_Information_String() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" - local tmp_array=( ${system_information[$1]//|/ } ) # Unquoted to let word splitting act + local tmp_array=(${system_information[$1]//|/ }) # Unquoted to let word splitting act printf '%s' "${tmp_array[$2]}" } @@ -503,5 +502,4 @@ function __static__Get_First_Line_From_String() # The \n in printf is important to avoid skipping the last line (which might be the only input) } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index e476f15..7ec81f5 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -34,7 +34,8 @@ function Has_YAML_String_Given_Key() if [[ $# -lt 2 ]]; then Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with less than 2 arguments.' fi - yaml_string=$1; shift + yaml_string=$1 + shift if ! yq <<< "${yaml_string}" &> /dev/null; then Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with invalid YAML string.' fi @@ -58,7 +59,8 @@ function Read_From_YAML_String_Given_Key() elif ! Has_YAML_String_Given_Key "$@"; then Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with YAML string not containing given key.' fi - yaml_string=$1; shift + yaml_string=$1 + shift key="$(printf '.%s' "$@")" yq "${key}" <<< "${yaml_string}" } @@ -74,7 +76,8 @@ function Print_YAML_String_Without_Given_Key() elif ! Has_YAML_String_Given_Key "$@"; then Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with YAML string not containing given key.' fi - yaml_string=$1; shift + yaml_string=$1 + shift key="$(printf '.%s' "$@")" yq 'del('"${key}"')' <<< "${yaml_string}" } @@ -96,7 +99,7 @@ function Print_Line_of_Equals() function Print_Centered_Line() { local input_string output_total_width indentation padding_character \ - postfix real_length padding_utility + postfix real_length padding_utility input_string="$1" output_total_width="${2:-$(tput cols)}" # Input arg. or full width of terminal indentation="${3-}" # Input arg. or empty string @@ -104,7 +107,7 @@ function Print_Centered_Line() postfix="${5-\n}" # Input arg. or endline # Determine length of input at net of formatting codes (color, face) real_length=$(printf '%s' "${input_string}" | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]//g" | wc -c) - if (( output_total_width - 2 - real_length < 0 )); then + if ((output_total_width - 2 - real_length < 0)); then Print_Fatal_And_Exit 'Error in ' --emph "${FUNCNAME}" ': specify larger total width!' fi # In the following we build a very long string of padding characters that @@ -112,11 +115,11 @@ function Print_Centered_Line() # here 500 characters. The * in a string format descriptor of printf means # that the number to be used there is passed to printf as argument. padding_utility="$(printf '%0.1s' "${padding_character}"{1..500})" - printf "${indentation}%0.*s %s %0.*s${postfix}"\ - "$(( (output_total_width - 2 - real_length)/2 ))"\ - "${padding_utility}"\ - "${input_string}"\ - "$(( (output_total_width - 2 - real_length)/2 ))"\ + printf "${indentation}%0.*s %s %0.*s${postfix}" \ + "$(((output_total_width - 2 - real_length) / 2))" \ + "${padding_utility}" \ + "${input_string}" \ + "$(((output_total_width - 2 - real_length) / 2))" \ "${padding_utility}" } @@ -165,7 +168,7 @@ function Call_Function_If_Existing_Or_Exit() ${name_of_the_function} "$@" else exit_code=${HYBRID_fatal_missing_feature} Print_Internal_And_Exit \ - '\nFunction ' --emph "${name_of_the_function}" ' not found!'\ + '\nFunction ' --emph "${name_of_the_function}" ' not found!' \ 'Please provide an implementation following the in-code documentation.' fi } @@ -189,11 +192,12 @@ function Call_Function_If_Existing_Or_No_Op() # if this function is used to test existence of an entry of an array, then # 'declare -p array[0]' would fail even if array[0] existed, while the test # [[ -v array[0] ]] would succeed. Hence we treat this case separately. -function Ensure_That_Given_Variables_Are_Set() { +function Ensure_That_Given_Variables_Are_Set() + { local variable_name for variable_name in "$@"; do - if ! declare -p "${variable_name}" &>/dev/null; then - if [[ ${variable_name} =~ \]$ && -v ${variable_name} ]]; then + if ! declare -p "${variable_name}" &> /dev/null; then + if [[ ${variable_name} =~ \]$ && -v ${variable_name} ]]; then continue fi Print_Internal_And_Exit \ @@ -208,12 +212,13 @@ function Ensure_That_Given_Variables_Are_Set() { # variable is 1 (as accessing an array without index returns the first entry). # Hence, for 'foo=""', ${#foo[@]} would return 1 and a non zero length is not # synonym of a non-empty variable. -function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() { +function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() + { local variable_name for variable_name in "$@"; do # The following can be done using the "${ref@A}" bash-5 expansion which # would return the variable declared attributes (e.g. 'a' for arrays). - if [[ $(declare -p "${variable_name}" 2>/dev/null ) =~ ^declare\ -[aA] ]]; then + if [[ $(declare -p "${variable_name}" 2> /dev/null) =~ ^declare\ -[aA] ]]; then declare -n ref=${variable_name} if [[ ${#ref[@]} -ne 0 ]]; then continue @@ -249,17 +254,16 @@ function Make_Functions_Defined_In_This_File_Readonly() # NOTE: The file from which this function is called is ${BASH_SOURCE[1]} local declared_functions declared_functions=( # Here word splitting can split names, no space allowed in function name! - $(grep -E '^[[:space:]]*function[[:space:]]+[-[:alnum:]_:]+\(\)[[:space:]]*$' "${BASH_SOURCE[1]}" |\ - sed -E 's/^[[:space:]]*function[[:space:]]+([^(]+)\(\)[[:space:]]*$/\1/') + $(grep -E '^[[:space:]]*function[[:space:]]+[-[:alnum:]_:]+\(\)[[:space:]]*$' "${BASH_SOURCE[1]}" \ + | sed -E 's/^[[:space:]]*function[[:space:]]+([^(]+)\(\)[[:space:]]*$/\1/') ) if [[ ${#declared_functions[@]} -eq 0 ]]; then Print_Internal_And_Exit \ - 'Function ' --emph "${FUNCNAME}" ' called, but no function found in file\n file '\ + 'Function ' --emph "${FUNCNAME}" ' called, but no function found in file\n file ' \ --emph "${BASH_SOURCE[1]}" '.' else readonly -f "${declared_functions[@]}" fi } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/version.bash b/bash/version.bash index 3e412f3..ec27044 100644 --- a/bash/version.bash +++ b/bash/version.bash @@ -12,16 +12,16 @@ function Print_Software_Version() Ensure_That_Given_Variables_Are_Set HYBRID_codebase_version # First handle cases where git is not available, or codebase downloaded as archive and not cloned # NOTE: Git introduced -C option in version 1.8.5 - if ! hash git &> /dev/null ||\ - __static__Is_Git_Version_Older_Than '1.8.3' ||\ - ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null; then + if ! hash git &> /dev/null \ + || __static__Is_Git_Version_Older_Than '1.8.3' \ + || ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null; then __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" return 0 fi local git_tag_short git_tag_long tag_date if ! git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags 2> /dev/null); then - Print_Warning 'It was not possible to obtain the version in use!'\ - 'This probably (but not necessarily) means that you are'\ + Print_Warning 'It was not possible to obtain the version in use!' \ + 'This probably (but not necessarily) means that you are' \ 'behind any release in the Hybrid-handler history.\n' __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" return 0 @@ -32,15 +32,15 @@ function Print_Software_Version() tag_date=$(date -d "$(git -C "${HYBRID_top_level_path}" log -1 --format=%ai "${git_tag_short}")" +'%d %B %Y') if [[ "${git_tag_short}" != "${git_tag_long}" ]]; then if __static__Is_Git_Version_Older_Than '2.13'; then - git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags --dirty 2>/dev/null) + git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags --dirty 2> /dev/null) else - git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags --dirty --broken 2>/dev/null) + git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags --dirty --broken 2> /dev/null) fi - Print_Warning 'You are not using an official release of the Hybrid-handler.'\ - 'Unless you have a reason not to do so, it would be better'\ - 'to checkout a stable release. The last stable release behind'\ - 'the commit you are using is: ' --emph "${git_tag_short}"\ - ' (' --emph "${tag_date}" ')\n' 'The repository state is '\ + Print_Warning 'You are not using an official release of the Hybrid-handler.' \ + 'Unless you have a reason not to do so, it would be better' \ + 'to checkout a stable release. The last stable release behind' \ + 'the commit you are using is: ' --emph "${git_tag_short}" \ + ' (' --emph "${tag_date}" ')\n' 'The repository state is ' \ --emph "${git_tag_long}" '' '(see git-describe documentation for more information).' else __static__Print_Pretty_Version_Line "${git_tag_short}" "${tag_date}" @@ -69,5 +69,4 @@ function __static__Is_Git_Version_Older_Than() fi } - Make_Functions_Defined_In_This_File_Readonly diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 55230ce..4b53017 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -13,8 +13,8 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() suite_name="$1" if [[ ! ${suite_name} =~ ^(functional|unit)$ ]]; then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ - 'Invalid tests type ' --emph "${suite_name:-}" '. Valid values: '\ - --emph 'unit' ' or ' --emph 'functional' '.'\ + 'Invalid tests type ' --emph "${suite_name:-}" '. Valid values: ' \ + --emph 'unit' ' or ' --emph 'functional' '.' \ 'Use the ' --emph '--help' ' option to get more information.' fi code_filename="${HYBRIDT_tests_folder}/${suite_name}_tests.bash" @@ -34,22 +34,24 @@ function Parse_Tests_Suite_Parameter_And_Source_Specific_Code() function Parse_Tests_Command_Line_Options() { # This function needs the array of tests not sparse => enforce it - HYBRIDT_tests_to_be_run=( "${HYBRIDT_tests_to_be_run[@]}" ) + HYBRIDT_tests_to_be_run=("${HYBRIDT_tests_to_be_run[@]}") while [[ $# -gt 0 ]]; do case $1 in - -h | --help ) + -h | --help) __static__Print_Helper exit ${HYBRID_success_exit_code} - shift ;; - -r | --report-level ) + shift + ;; + -r | --report-level) if [[ ${2-} =~ ^[0-3]$ ]]; then readonly HYBRIDT_report_level=$2 else Print_Option_Specification_Error_And_Exit "$1" fi - shift 2 ;; - -t | --run-tests ) + shift 2 + ;; + -t | --run-tests) if [[ ! ${2-} =~ ^- && "${2-}" != '' ]]; then if [[ $2 =~ ^[1-9][0-9]*([,\-][1-9][0-9]*)*$ ]]; then __static__Set_Tests_To_Be_Run_Using_Numbers "$2" @@ -62,13 +64,15 @@ function Parse_Tests_Command_Line_Options() __static__Print_List_Of_Tests exit ${HYBRID_success_exit_code} fi - shift 2 ;; - -k | --keep-tests-folder ) + shift 2 + ;; + -k | --keep-tests-folder) HYBRIDT_clean_test_folder='FALSE' - shift ;; - * ) + shift + ;; + *) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ - 'Invalid option ' --emph "$1" ' specified! Use the '\ + 'Invalid option ' --emph "$1" ' specified! Use the ' \ --emph '--help' ' option to get further information.' ;; esac @@ -87,19 +91,19 @@ function __static__Print_Helper() __static__Add_Option_To_Helper "functional" "Tests of the handler as whole script." __static__Add_Option_To_Helper "unit" "Unit tests of the codebase." printf " ${emph_color}Execute tests with the following optional arguments:${default_color}\n\n" - __static__Add_Option_To_Helper "-r | --report-level"\ - "Verbosity of test report (default value ${HYBRIDT_report_level})."\ - "To be chosen among"\ + __static__Add_Option_To_Helper "-r | --report-level" \ + "Verbosity of test report (default value ${HYBRIDT_report_level})." \ + "To be chosen among" \ " 0 = binary, 1 = summary, 2 = short, 3 = detailed." - __static__Add_Option_To_Helper "-t | --run-tests"\ - "Specify which tests have to be run. A comma-separated"\ - "list of numbers and/or of intervals (e.g. 1,3-5) or"\ - "a string (e.g. 'help*') has to be specified. The string"\ - "is matched against test names using bash regular globbing."\ - "Remember to quote the argument to avoid shell expansion."\ - "If no value is specified the available tests list is printed."\ + __static__Add_Option_To_Helper "-t | --run-tests" \ + "Specify which tests have to be run. A comma-separated" \ + "list of numbers and/or of intervals (e.g. 1,3-5) or" \ + "a string (e.g. 'help*') has to be specified. The string" \ + "is matched against test names using bash regular globbing." \ + "Remember to quote the argument to avoid shell expansion." \ + "If no value is specified the available tests list is printed." \ "Without this option all existing tests are run." - __static__Add_Option_To_Helper "-k | --keep-tests-folder"\ + __static__Add_Option_To_Helper "-k | --keep-tests-folder" \ "Leave all the created folders and files in the test folder." Print_Warning \ " Values from options must be separated by space and short options cannot be combined." @@ -113,12 +117,12 @@ function __static__Add_Option_To_Helper() name="$1" description="$2" shift 2 - printf "${options_color}%s${default_color} -> ${text_color}%s\n"\ - "$(printf "%s%-${length_option}s" "${indentation}" "${name}")"\ + printf "${options_color}%s${default_color} -> ${text_color}%s\n" \ + "$(printf "%s%-${length_option}s" "${indentation}" "${name}")" \ "${description}" while [[ $# -gt 0 ]]; do - printf "%s %s\n"\ - "$(printf "%s%${length_option}s" "${indentation}" "")"\ + printf "%s %s\n" \ + "$(printf "%s%${length_option}s" "${indentation}" "")" \ "$1" shift done @@ -141,16 +145,16 @@ function __static__Set_Tests_To_Be_Run_Using_Numbers() selected_tests=() for number in "${numeric_list[@]}"; do # The user selects human-friendly numbers (1,2,...), here go back to array indices - (( number-- )) + ((number--)) if [[ ${number} -lt ${#HYBRIDT_tests_to_be_run[@]} ]]; then - selected_tests+=( "${HYBRIDT_tests_to_be_run[number]}" ) + selected_tests+=("${HYBRIDT_tests_to_be_run[number]}") else exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ - 'Some specified test number within ' --emph "$1" ' is not valid! Use'\ + 'Some specified test number within ' --emph "$1" ' is not valid! Use' \ 'the ' --emph '-t' ' option without value to get a list of available tests.' fi done - HYBRIDT_tests_to_be_run=( "${selected_tests[@]}" ) + HYBRIDT_tests_to_be_run=("${selected_tests[@]}") Print_Debug 'Selected tests: ' --emph "( ${HYBRIDT_tests_to_be_run[*]} )" } @@ -162,13 +166,13 @@ function __static__Set_Tests_To_Be_Run_Using_Globbing() for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do # In this if-clause, no quotes must be used -> globbing comparison! if [[ ${test_name} = ${selection_string} ]]; then - selected_tests+=( "${test_name}" ) + selected_tests+=("${test_name}") fi done - HYBRIDT_tests_to_be_run=( "${selected_tests[@]}" ) + HYBRIDT_tests_to_be_run=("${selected_tests[@]}") if [[ ${#HYBRIDT_tests_to_be_run[@]} -eq 0 ]]; then exit_code=${HYBRID_fatal_value_error} Print_Fatal_And_Exit \ - "No test name found matching \"$1\" globbing pattern! Use"\ + "No test name found matching \"$1\" globbing pattern! Use" \ "the '-t' option without value to get a list of available tests." fi Print_Debug 'Selected tests: ' --emph "( ${HYBRIDT_tests_to_be_run[*]} )" @@ -178,11 +182,10 @@ function __static__Print_List_Of_Tests() { local index indentation width_of_list printf " \e[96mList of available tests:\e[0m\n\n" - width_of_list=$(( $(tput cols) * 4 / 5 )) + width_of_list=$(($( tput cols) * 4 / 5)) for ((index = 0; index < ${#HYBRIDT_tests_to_be_run[@]}; index++)); do - printf '%3d) %s\n' "$(( index+1 ))" "${HYBRIDT_tests_to_be_run[index]}" + printf '%3d) %s\n' "$((index + 1))" "${HYBRIDT_tests_to_be_run[index]}" done | column -c "${width_of_list}" } - Make_Functions_Defined_In_This_File_Readonly diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index 1878741..b81c35c 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -37,5 +37,5 @@ function Clean_Tests_Environment_For_Following_Test() function Run_Hybrid_Handler_With_Given_Options_In_Subshell() { - ( "${HYBRIDT_repository_top_level_path}/Hybrid-handler" "$@" ) + ("${HYBRIDT_repository_top_level_path}/Hybrid-handler" "$@" ) } diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index d70966a..22f8037 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -40,7 +40,7 @@ function Functional_Test__do-Afterburner-only() # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' terminal_output_file='Afterburner/Terminal_Output.txt' - BLACK_BOX_FAIL='invalid_config'\ + BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid Afterburner input.' @@ -57,13 +57,13 @@ function Functional_Test__do-Afterburner-only() mv 'Afterburner' 'Afterburner-invalid-config' # Expect failure and test "SMASH" unfinished/lock files Print_Info 'Running Hybrid-handler expecting crash in Afterburner software' - BLACK_BOX_FAIL='smash_crashes'\ + BLACK_BOX_FAIL='smash_crashes' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with Afterburner software crashing.' return 1 fi - unfinished_files=( Afterburner/*.{unfinished,lock} ) + unfinished_files=(Afterburner/*.{unfinished,lock}) if [[ ${#unfinished_files[@]} -ne 3 ]]; then Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." return 1 diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 6fe3f31..7570589 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -33,7 +33,7 @@ function Functional_Test__do-Hydro-only() # Expect failure when giving an invalid IC output Print_Info 'Running Hybrid-handler expecting invalid IC argument' terminal_output_file='Hydro/Terminal_Output.txt' - BLACK_BOX_FAIL='invalid_input'\ + BLACK_BOX_FAIL='invalid_input' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input for Hydro.' @@ -69,7 +69,7 @@ function Functional_Test__do-Hydro-only() # Expect failure when an invalid config was supplied Print_Info 'Running Hybrid-handler expecting invalid config argument' terminal_output_file='Hydro/Terminal_Output.txt' - BLACK_BOX_FAIL='invalid_config'\ + BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Hydro.' @@ -86,7 +86,7 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-invalid-config' # Expect failure and test terminal output in the case of a crash of vHLLE Print_Info 'Running Hybrid-handler expecting crash in Hydro' - BLACK_BOX_FAIL='crash'\ + BLACK_BOX_FAIL='crash' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with Hydro crashing.' @@ -108,7 +108,7 @@ function Functional_Test__do-Hydro-only() ' "$(pwd)" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -ne 110 ]]; then + if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index e8374f0..9e4aee3 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -28,7 +28,7 @@ function Functional_Test__do-IC-only() # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid IC input file failure' terminal_output_file='IC/Terminal_Output.txt' - BLACK_BOX_FAIL='invalid_config'\ + BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input.' @@ -45,13 +45,13 @@ function Functional_Test__do-IC-only() mv 'IC' 'IC-invalid-config' # Expect failure and test "SMASH" unfinished/lock files Print_Info 'Running Hybrid-handler expecting crash in IC software' - BLACK_BOX_FAIL='smash_crashes'\ + BLACK_BOX_FAIL='smash_crashes' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with IC software crashing.' return 1 fi - unfinished_files=( IC/*.{unfinished,lock} ) + unfinished_files=(IC/*.{unfinished,lock}) if [[ ${#unfinished_files[@]} -ne 3 ]]; then Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." return 1 diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index d76c38d..70b2b8e 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -31,7 +31,7 @@ function Functional_Test__do-Sampler-only() local terminal_output_file error_message terminal_output_file='Sampler/Terminal_Output.txt' Print_Info 'Running Hybrid-handler expecting crash in Sampler' - BLACK_BOX_FAIL='true'\ + BLACK_BOX_FAIL='true' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with Sampler crashing.' @@ -40,7 +40,7 @@ function Functional_Test__do-Sampler-only() Print_Error 'File ' --emph "${terminal_output_file}" ' not found.' return 1 fi - error_message="$(<"${terminal_output_file}")" + error_message="$(< "${terminal_output_file}")" if [[ "${error_message}" != 'Sampler black-box crashed!' ]]; then Print_Error 'Sampler crashed with unexpected terminal output.' return 1 @@ -56,8 +56,8 @@ function Functional_Test__do-Sampler-only() Sampler: Executable: %s/tests/mocks/sampler_black-box.py Config_file: %s - ' "${HYBRIDT_repository_top_level_path}"\ - "${invalid_sampler_config}" > "${hybrid_handler_config}" + ' "${HYBRIDT_repository_top_level_path}" \ + "${invalid_sampler_config}" > "${hybrid_handler_config}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Sampler.' diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash index 20baa11..9e1e9fd 100644 --- a/tests/functional_tests_full_workflow.bash +++ b/tests/functional_tests_full_workflow.bash @@ -21,7 +21,7 @@ function __static__Test_Full_Workflow() { shopt -s nullglob local -r \ - config_filename='Handler_config.yaml'\ + config_filename='Handler_config.yaml' \ mocks_folder="${HYBRIDT_tests_folder}/mocks" __static__Prepare_Full_Handler_Configuration_File "$1" __static__Create_Auxiliaries_For_Hydro diff --git a/tests/functional_tests_version.bash b/tests/functional_tests_version.bash index ca32f87..4217a4e 100644 --- a/tests/functional_tests_version.bash +++ b/tests/functional_tests_version.bash @@ -17,7 +17,10 @@ function Functional_Test__version() if ! hash git &> /dev/null; then Print_Warning 'Command ' --emph 'git' ' not available, part of test cannot be run.' else - git_describe=$(cd "${HYBRIDT_repository_top_level_path}"; git describe --abbrev=0) + git_describe=$( + cd "${HYBRIDT_repository_top_level_path}" + git describe --abbrev=0 + ) printf "${version_output}\n" if [[ $(grep -c "${git_describe}" <<< "${version_output}") -gt 0 ]]; then return 0 diff --git a/tests/tests_runner b/tests/tests_runner index 8f07c83..7f682e5 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -63,14 +63,14 @@ function Enable_Global_Needed_Shell_Options() # the codebase functions, see 'Call_Codebase_Function[_In_Subshell]'. } -function Define_Tests_Global_Variables () +function Define_Tests_Global_Variables() { readonly HYBRIDT_repository_top_level_path=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../" &> /dev/null && pwd) readonly HYBRIDT_command=${HYBRIDT_repository_top_level_path}/Hybrid-handler readonly HYBRIDT_tests_folder=${HYBRIDT_repository_top_level_path}/tests readonly HYBRIDT_folder_to_run_tests=${HYBRIDT_tests_folder}/run_tests readonly HYBRIDT_log_file=${HYBRIDT_folder_to_run_tests}/$(basename ${BASH_SOURCE[0]}).log - readonly HYBRIDT_auxiliary_files_and_folders=( "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_log_file}" ) + readonly HYBRIDT_auxiliary_files_and_folders=("${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_log_file}") HYBRIDT_clean_test_folder='TRUE' HYBRIDT_report_level=3 # Report level: 0 = binary, 1 = summary, 2 = short, 3 = detailed HYBRIDT_tests_run=0 @@ -83,7 +83,7 @@ function Define_Tests_Global_Variables () function Source_Needed_Files() { source "${HYBRIDT_repository_top_level_path}/bash/error_codes.bash" || exit 1 - source "${HYBRIDT_repository_top_level_path}/bash/logger.bash"\ + source "${HYBRIDT_repository_top_level_path}/bash/logger.bash" \ --fd 9 --default-exit-code ${HYBRID_internal_exit_code} || exit ${HYBRID_fatal_builtin} local -r files_to_be_sourced=( "${HYBRIDT_repository_top_level_path}/bash/utility_functions.bash" @@ -110,8 +110,8 @@ function Prepare_Test_Environment() postfix=$(date +'%Y-%m-%d_%H%M%S') if [[ -d "${HYBRIDT_folder_to_run_tests}" ]]; then Print_Warning 'Found ' --emph "${HYBRIDT_folder_to_run_tests}" ', renaming it!\n' - mv "${HYBRIDT_folder_to_run_tests}"\ - "${HYBRIDT_folder_to_run_tests}_${postfix}" || exit ${HYBRID_fatal_builtin} + mv "${HYBRIDT_folder_to_run_tests}" \ + "${HYBRIDT_folder_to_run_tests}_${postfix}" || exit ${HYBRID_fatal_builtin} fi mkdir "${HYBRIDT_folder_to_run_tests}" || exit ${HYBRID_fatal_builtin} } @@ -123,7 +123,7 @@ function Run_Tests() fi local test_name for test_name in "${HYBRIDT_tests_to_be_run[@]}"; do - (( HYBRIDT_tests_run++ )) + ((HYBRIDT_tests_run++)) # Run in sub-shell to have the same starting environment ( # Make sure each test is run from the same folder (no matter from where tests are run) @@ -142,11 +142,12 @@ function Run_Tests() function Announce_Running_Test() { local test_name padded_name - test_name=$1; shift + test_name=$1 + shift if [[ ${HYBRIDT_report_level} -eq 3 ]]; then printf -v padded_name "%-60s" "__${test_name}$(printf '\e[94m')_" padded_name="${padded_name// /.}" - printf ' %+2s/%-2s\e[96m%s'\ + printf ' %+2s/%-2s\e[96m%s' \ ${HYBRIDT_tests_run} ${#HYBRIDT_tests_to_be_run[@]} "${padded_name//_/ }" fi } @@ -157,13 +158,13 @@ function Inspect_Test_Outcome() test_exit_code=$1 test_name=$2 if [[ ${test_exit_code} -eq 0 ]]; then - (( HYBRIDT_tests_passed++ )) + ((HYBRIDT_tests_passed++)) if [[ ${HYBRIDT_report_level} -eq 3 ]]; then printf " \e[92mpassed\e[0m\n" fi else - (( HYBRIDT_tests_failed++ )) - HYBRIDT_which_tests_failed+=( "${test_name}" ) + ((HYBRIDT_tests_failed++)) + HYBRIDT_which_tests_failed+=("${test_name}") if [[ ${HYBRIDT_report_level} -eq 3 ]]; then printf " \e[91mfailed\e[0m\n" fi @@ -173,18 +174,18 @@ function Inspect_Test_Outcome() function Print_Tests_Report() { if [[ ${HYBRIDT_report_level} -ge 1 ]]; then - local indentation left_margin test_name_string_length\ - index separator_length passed_string failed_string + local indentation left_margin test_name_string_length \ + index separator_length passed_string failed_string indentation=' ' left_margin=' ' test_name_string_length=$(printf '%s\n' "${HYBRIDT_tests_to_be_run[@]}" | wc -L) if [[ ${test_name_string_length} -lt 25 ]]; then # Minimum length test_name_string_length=25 fi - if (( test_name_string_length % 2 == 1 )); then # Aesthetics - (( test_name_string_length+=1 )) + if ((test_name_string_length % 2 == 1)); then # Aesthetics + ((test_name_string_length += 1)) fi - separator_length=$(( test_name_string_length + 3 + 2 * ${#left_margin} )) + separator_length=$((test_name_string_length + 3 + 2 * ${#left_margin})) passed_string="$(printf "Run %d test(s): %2d passed" ${HYBRIDT_tests_run} ${HYBRIDT_tests_passed})" failed_string="$(printf " ${HYBRIDT_tests_run//?/ } %2d failed" ${HYBRIDT_tests_failed})" Print_Line_of_Equals "${separator_length}" "${indentation}" '\n\e[96m' '\e[0m\n' @@ -194,8 +195,7 @@ function Print_Tests_Report() fi if [[ ${HYBRIDT_report_level} -ge 2 ]]; then local name percentage - percentage=$(awk\ - '{ + percentage=$(awk '{ if($2!=0) {printf "%.0f%%", 100*$1/$2} else {printf "-- %"} }' <<< "${HYBRIDT_tests_passed} ${HYBRIDT_tests_run}") Print_Centered_Line "${percentage} of tests passed!" ${separator_length} "${indentation}" @@ -226,10 +226,10 @@ function Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So() label='symlink' elif [[ -f "${global_path}" ]]; then label='file' - elif [[ ! -e "${global_path}" ]]; then + elif [[ ! -e "${global_path}" ]]; then continue else - Print_Internal_And_Exit 'Error in ' --emph "${FUNCNAME}" '.'\ + Print_Internal_And_Exit 'Error in ' --emph "${FUNCNAME}" '.' \ --emph "${global_path}" ' seems to neither be a file nor a directory, leaving it!' fi Print_Info "Removing ${label} " --emph "${global_path}" '.' diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 2333410..ed9e513 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -22,7 +22,7 @@ __static__Do_Preliminary_Setup_Operations() Define_Further_Global_Variables HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Afterburner" HYBRID_software_base_config_file[Afterburner]='my_cool_conf.yaml' - HYBRID_given_software_sections=( 'Afterburner' ) + HYBRID_given_software_sections=('Afterburner') HYBRID_software_executable[Afterburner]=$(which echo) # Use command as fake executable } @@ -39,7 +39,7 @@ function Unit_Test__Afterburner-create-input-file() touch "${HYBRID_software_base_config_file[Afterburner]}" mkdir -p "${HYBRID_software_output_directory[Sampler]}" local -r \ - plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ + plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${plist_Sampler}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner @@ -65,7 +65,7 @@ function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-fi function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-spectators() { - __static__Do_Preliminary_Setup_Operations + __static__Do_Preliminary_Setup_Operations HYBRID_optional_feature[Add_spectators_from_IC]='TRUE' Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts @@ -74,12 +74,12 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-sp function Unit_Test__Afterburner-create-input-file-with-spectators() { mkdir -p \ - "${HYBRID_software_output_directory[Sampler]}"\ - "${HYBRID_software_output_directory[IC]}"\ + "${HYBRID_software_output_directory[Sampler]}" \ + "${HYBRID_software_output_directory[IC]}" \ "${HYBRID_software_output_directory[Afterburner]}" local -r \ - plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar"\ - plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar"\ + plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ + plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" \ plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null @@ -148,13 +148,13 @@ function Unit_Test__Afterburner-check-all-input() Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then Print_Error \ - 'Ensuring existence of existing folder/file unexpectedly failed,'\ + 'Ensuring existence of existing folder/file unexpectedly failed,' \ ' although all files were provided.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${HYBRID_software_output_directory[Sampler]}/original_sampling0" - ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampling0"\ + ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampling0" \ "${HYBRID_software_output_directory[Afterburner]}/sampling0" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -ne 0 ]]; then @@ -190,9 +190,9 @@ function Unit_Test__Afterburner-test-run-software() return 1 fi terminal_output_result=$(< "${afterburner_terminal_output}") - printf -v correct_result '%s'\ - "-i ${HYBRID_software_configuration_file[Afterburner]} "\ - "-o ${HYBRID_software_output_directory[Afterburner]} -n" + printf -v correct_result '%s' \ + "-i ${HYBRID_software_configuration_file[Afterburner]} " \ + "-o ${HYBRID_software_output_directory[Afterburner]} -n" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then Print_Error 'The terminal output has not the expected content.' return 1 diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index cc22407..2a19309 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -22,7 +22,7 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() Define_Further_Global_Variables HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Hydro" HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' - HYBRID_given_software_sections=('Hydro' ) + HYBRID_given_software_sections=('Hydro') HYBRID_software_executable[Hydro]="$(which echo)" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 2429403..95d2c0a 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -22,7 +22,7 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() Define_Further_Global_Variables HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_IC" HYBRID_software_base_config_file[IC]='my_cool_conf.yaml' - HYBRID_given_software_sections=( 'IC' ) + HYBRID_given_software_sections=('IC') HYBRID_software_executable[IC]=$(which echo) # Use command as fake executable Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index d7123c1..41eac38 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -9,7 +9,7 @@ function Make_Test_Preliminary_Operations__Sampler-create-input-file() { - local file_to_be_sourced list_of_files + local file_to_be_sourced list_of_files list_of_files=( 'Sampler_functionality.bash' 'global_variables.bash' @@ -97,8 +97,8 @@ function Unit_Test__Sampler-check-all-input() Print_Error 'Ensuring existence of not-existing freezeout surface file succeeded.' return 1 fi - printf '%s\n'\ - "surface $(which ls)"\ + printf '%s\n' \ + "surface $(which ls)" \ "spectra_dir ${HOME}" > "${HYBRID_software_configuration_file[Sampler]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler if [[ $? -ne 0 ]]; then @@ -180,13 +180,13 @@ function Unit_Test__Sampler-validate-config-file() # Config file with incorrect value type for other keys local wrong_key_value for wrong_key_value in \ - 'number_of_events 3.14'\ - 'rescatter 3..14'\ - 'weakContribution false'\ - 'shear true'\ - 'ecrit +-1'\ - 'Nbins -100'\ - 'q_max 1.6'; do + 'number_of_events 3.14' \ + 'rescatter 3..14' \ + 'weakContribution false' \ + 'shear true' \ + 'ecrit +-1' \ + 'Nbins -100' \ + 'q_max 1.6'; do printf '%s\n' "${wrong_key_value}" > "${HYBRID_software_configuration_file[Sampler]}" Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid &> /dev/null if [[ $? -eq 0 ]]; then @@ -218,8 +218,8 @@ function Make_Test_Preliminary_Operations__Sampler-test-run-software() function Unit_Test__Sampler-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Sampler]}" - local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt"\ - sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" + local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" \ + sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Sampler if [[ ! -f "${sampler_terminal_output}" ]]; then diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index d676a2f..72dd361 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -22,13 +22,13 @@ function Make_Test_Preliminary_Operations__parse-execution-mode() function __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success() ( - local expected_option=$1\ - expected_size=$2\ - first_option="${HYBRID_command_line_options_to_parse[0]-}" + local expected_option=$1 \ + expected_size=$2 \ + first_option="${HYBRID_command_line_options_to_parse[0]-}" Call_Codebase_Function Parse_Execution_Mode - if [[ $? -ne 0 ]] ||\ - [[ "${HYBRID_execution_mode}" != "${expected_option}" ]] ||\ - [[ ${#HYBRID_command_line_options_to_parse[@]} -ne "${expected_size}" ]]; then + if [[ $? -ne 0 ]] \ + || [[ "${HYBRID_execution_mode}" != "${expected_option}" ]] \ + || [[ ${#HYBRID_command_line_options_to_parse[@]} -ne "${expected_size}" ]]; then Print_Error 'Parsing of valid execution mode ' --emph "${first_option}" ' failed.' return 1 fi @@ -38,25 +38,25 @@ function Unit_Test__parse-execution-mode() { HYBRID_command_line_options_to_parse=() __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'help' 0 || return 1 - HYBRID_command_line_options_to_parse=( 'help' ) + HYBRID_command_line_options_to_parse=('help') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'help' 0 || return 1 - HYBRID_command_line_options_to_parse=( '--help' ) + HYBRID_command_line_options_to_parse=('--help') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'help' 0 || return 1 - HYBRID_command_line_options_to_parse=( 'version' ) + HYBRID_command_line_options_to_parse=('version') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'version' 0 || return 1 - HYBRID_command_line_options_to_parse=( '--version' ) + HYBRID_command_line_options_to_parse=('--version') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'version' 0 || return 1 - HYBRID_command_line_options_to_parse=( 'help' 'with-invalid' 'irrelevant-options' ) + HYBRID_command_line_options_to_parse=('help' 'with-invalid' 'irrelevant-options') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'help' 0 || return 1 - HYBRID_command_line_options_to_parse=( 'version' 'with-invalid' 'irrelevant-options' ) + HYBRID_command_line_options_to_parse=('version' 'with-invalid' 'irrelevant-options') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'version' 0 || return 1 - HYBRID_command_line_options_to_parse=( 'do' ) + HYBRID_command_line_options_to_parse=('do') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'do' 0 || return 1 - HYBRID_command_line_options_to_parse=( 'do' '-o' '/path/to/folder' ) + HYBRID_command_line_options_to_parse=('do' '-o' '/path/to/folder') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'do' 2 || return 1 - HYBRID_command_line_options_to_parse=( 'do' '-o' '/path/to/folder' '--help' ) + HYBRID_command_line_options_to_parse=('do' '-o' '/path/to/folder' '--help') __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success 'do-help' 0 || return 1 - HYBRID_command_line_options_to_parse=( 'invalid-mode' ) + HYBRID_command_line_options_to_parse=('invalid-mode') Call_Codebase_Function_In_Subshell Parse_Execution_Mode &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Parsing of invalid execution mode succeeded.' @@ -75,7 +75,7 @@ function __static__Test_CLO_Parsing_Missing_Value() { Call_Codebase_Function_In_Subshell Parse_Command_Line_Options &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Parsing of CLO ' --emph "${HYBRID_command_line_options_to_parse[0]}"\ + Print_Error 'Parsing of CLO ' --emph "${HYBRID_command_line_options_to_parse[0]}" \ ' with missing value succeeded.' return 1 fi @@ -99,9 +99,9 @@ function Unit_Test__parse-command-line-options() return 1 fi HYBRID_execution_mode='do' - HYBRID_command_line_options_to_parse=( --output-directory ) + HYBRID_command_line_options_to_parse=(--output-directory) __static__Test_CLO_Parsing_Missing_Value || return 1 - HYBRID_command_line_options_to_parse=( --configuration-file ) + HYBRID_command_line_options_to_parse=(--configuration-file) __static__Test_CLO_Parsing_Missing_Value || return 1 HYBRID_command_line_options_to_parse=() Call_Codebase_Function_In_Subshell Parse_Command_Line_Options @@ -109,8 +109,8 @@ function Unit_Test__parse-command-line-options() Print_Error 'Parsing of CLO with no CLO failed.' return 1 fi - HYBRID_command_line_options_to_parse=( -o "${HOME}" ) + HYBRID_command_line_options_to_parse=(-o "${HOME}") __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_output_directory "${HOME}" || return 1 - HYBRID_command_line_options_to_parse=( -c /path/to/file ) + HYBRID_command_line_options_to_parse=(-c /path/to/file) __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_configuration_file '/path/to/file' || return 1 } diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index 11de20d..c179d33 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -178,8 +178,8 @@ function __static__Test_Section_Parsing_In_Subshell() input_file=$4 new_keys=$5 Call_Codebase_Function Validate_And_Parse_Configuration_File - if [[ "${#HYBRID_given_software_sections[@]}" -ne 1 ]] ||\ - [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then + if [[ "${#HYBRID_given_software_sections[@]}" -ne 1 ]] \ + || [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (section storing).' fi if [[ ${HYBRID_software_executable[${section}]} != "${executable}" ]]; then diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash index cc4142a..21a9830 100644 --- a/tests/unit_tests_formatting.bash +++ b/tests/unit_tests_formatting.bash @@ -13,7 +13,7 @@ function Unit_Test__codebase-formatting() if hash shfmt &> /dev/null; then formatter_found='TRUE' else - Print_Error 'Command ' --emph 'beautysh'\ + Print_Error 'Command ' --emph 'beautysh' \ ' not available, unable to fully check codebase formatting.' fi local -r max_length=120 @@ -25,7 +25,7 @@ function Unit_Test__codebase-formatting() files_with_too_long_lines=() for file in "${list_of_source_files[@]}"; do if [[ $(wc -L < "${file}") -gt ${max_length} ]]; then - files_with_too_long_lines+=( "${file}" ) + files_with_too_long_lines+=("${file}") continue fi done @@ -40,10 +40,10 @@ function Unit_Test__codebase-formatting() fi if [[ ${#files_with_too_long_lines[@]} -gt 0 ]]; then Print_Error \ - 'There are ' --emph "${#files_with_too_long_lines[@]}" ' file(s) with lines longer than '\ + 'There are ' --emph "${#files_with_too_long_lines[@]}" ' file(s) with lines longer than ' \ --emph "${max_length}" ' characters:' for file in "${files_with_too_long_lines[@]}"; do - Print_Error -l -- ' - '\ + Print_Error -l -- ' - ' \ --emph "$(realpath --relative-base="${HYBRIDT_repository_top_level_path}" "${file}")" done Print_Info '\nPlease adjust too long lines in the above mentioned files.' @@ -59,10 +59,10 @@ function Unit_Test__codebase-formatting() --emph "$(realpath --relative-base="${HYBRIDT_repository_top_level_path}" "${file}")" done Print_Info \ - '\nTo format all bash files correctly run:\n'\ + '\nTo format all bash files correctly run:\n' \ --emph "shfmt -w -ln bash -i 4 -bn -ci -sr -kp -fn \"${HYBRIDT_repository_top_level_path}\"" fi - if (( ${#files_with_too_long_lines[@]} + ${#files_with_wrong_formatting[@]} > 0 )); then + if ((${#files_with_too_long_lines[@]} + ${#files_with_wrong_formatting[@]} > 0)); then return 1 fi } diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index d1c2211..45bf0bf 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -78,7 +78,10 @@ function Unit_Test__system-requirements() return 1 fi printf '\n' - ( unset -v 'TERM'; Call_Codebase_Function_In_Subshell Check_System_Requirements_And_Make_Report ) + ( + unset -v 'TERM' + Call_Codebase_Function_In_Subshell Check_System_Requirements_And_Make_Report + ) if [[ $? -ne 0 ]]; then Print_Error "Check system requirements making report of bad system failed." return 1 diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 3419b82..e4a4102 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -12,32 +12,32 @@ function Unit_Test__utility-has-YAML-string-given-key() { Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Wrong call to function succeeded." return 1 fi Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Function called on invalid YAML succeeded." return 1 fi Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c' &> /dev/null - if [[ $? -ne 0 ]] ; then + if [[ $? -ne 0 ]]; then Print_Error 'Existing key ' --emph '{a: {b: {c:}}}' ' not found.' return 1 fi Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' &> /dev/null - if [[ $? -ne 0 ]] ; then + if [[ $? -ne 0 ]]; then Print_Error 'Existing key ' --emph '{a: {b:}}' ' not found.' return 1 fi Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' &> /dev/null - if [[ $? -ne 0 ]] ; then + if [[ $? -ne 0 ]]; then Print_Error 'Existing key ' --emph '{a:}' ' not found.' return 1 fi Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'nope' &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Not existing key found." return 1 fi @@ -46,28 +46,28 @@ function Unit_Test__utility-has-YAML-string-given-key() function Unit_Test__utility-read-from-YAML-string-given-key() { Call_Codebase_Function_In_Subshell Read_From_YAML_String_Given_Key &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Wrong call to function succeeded." return 1 fi Call_Codebase_Function_In_Subshell Read_From_YAML_String_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Function called on invalid YAML succeeded." return 1 fi Call_Codebase_Function_In_Subshell Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'nope' &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Not existing key successfully read." return 1 fi local result result=$(Call_Codebase_Function Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c') - if [[ ${result} -ne 42 ]] ; then + if [[ ${result} -ne 42 ]]; then Print_Error "Reading scalar key failed." return 1 fi result=$(Call_Codebase_Function Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b') - if [[ "${result}" != 'c: 42' ]] ; then + if [[ "${result}" != 'c: 42' ]]; then Print_Error "Reading map key failed." return 1 fi @@ -76,33 +76,33 @@ function Unit_Test__utility-read-from-YAML-string-given-key() function Unit_Test__utility-print-YAML-string-without-given-key() { Call_Codebase_Function_In_Subshell Print_YAML_String_Without_Given_Key &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Wrong call to function succeeded." return 1 fi Call_Codebase_Function_In_Subshell Print_YAML_String_Without_Given_Key $'Scalar\nKey: Value\n' 'Key' &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Function called on invalid YAML succeeded." return 1 fi Call_Codebase_Function_In_Subshell Print_YAML_String_Without_Given_Key $'a:\n b: 42\n' 'nope' &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Not existing key successfully deleted." return 1 fi local result result=$(Call_Codebase_Function Print_YAML_String_Without_Given_Key $'a: 42\nb: 17\n' 'b') - if [[ "${result}" != 'a: 42' ]] ; then + if [[ "${result}" != 'a: 42' ]]; then Print_Error "Deleting scalar key failed." return 1 fi result=$(Call_Codebase_Function Print_YAML_String_Without_Given_Key $'a:\n b:\n c: 17\n' 'a' 'b') - if [[ "${result}" != 'a: {}' ]] ; then + if [[ "${result}" != 'a: {}' ]]; then Print_Error "Deleting map key failed." return 1 fi result=$(Call_Codebase_Function Print_YAML_String_Without_Given_Key $'a: 42\n' 'a') - if [[ "${result}" != '{}' ]] ; then + if [[ "${result}" != '{}' ]]; then Print_Error "Deleting only existing key failed." return 1 fi @@ -114,7 +114,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() cd "${HYBRIDT_folder_to_run_tests}" # Test case 0 Call_Codebase_Function_In_Subshell Remove_Comments_In_Existing_File 'not_existing_file.txt' &> /dev/null - if [[ $? -eq 0 ]] ; then + if [[ $? -eq 0 ]]; then Print_Error "Remove comments on not existent file did not fail." return 1 fi @@ -142,7 +142,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() printf $'Some\n #comment\ntext\n#comment\namong\n#comment\ncomments\n' > "${file_containing_three_commented_lines}" number_of_lines=$(wc -l < "${file_containing_three_commented_lines}") Call_Codebase_Function_In_Subshell Remove_Comments_In_File "${file_containing_three_commented_lines}" - if (( $(wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3 )); then + if (($( wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3)); then Print_Error 'Removing comments in ' --emph "${file_containing_three_commented_lines}" ' file failed.' return 1 fi @@ -189,7 +189,7 @@ function Unit_Test__utility-check-shell-variables-set() Print_Error 'Checking empty array variable failed.' return 1 fi - foo=( '' ) + foo=('') Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set foo if [[ $? -ne 0 ]]; then Print_Error 'Checking array variable set to one empty entry failed.' @@ -234,7 +234,7 @@ function Unit_Test__utility-check-shell-variables-set-not-empty() Print_Error 'Checking empty array variable succeeded.' return 1 fi - foo=( '' ) + foo=('') Call_Codebase_Function_In_Subshell Ensure_That_Given_Variables_Are_Set_And_Not_Empty foo if [[ $? -ne 0 ]]; then Print_Error 'Checking array variable set to one empty entry failed.' diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash index 463c94b..efc0be4 100644 --- a/tests/unit_tests_version.bash +++ b/tests/unit_tests_version.bash @@ -17,16 +17,19 @@ function Unit_Test__version() HYBRID_codebase_version='SMASH-vHLLE-hybrid-1.0' local std_output expected_output # Unsetting PATH in the subshell so that 'git' will not be found - std_output=$(PATH=''; Call_Codebase_Function Print_Software_Version) - if [[ $? -ne 0 ]] ||\ - [[ $(Strip_ANSI_Color_Codes_From_String "${std_output}") != "This is ${HYBRID_codebase_version}" ]] ; then + std_output=$( + PATH='' + Call_Codebase_Function Print_Software_Version + ) + if [[ $? -ne 0 ]] \ + || [[ $(Strip_ANSI_Color_Codes_From_String "${std_output}") != "This is ${HYBRID_codebase_version}" ]]; then Print_Error "Version printing without git available failed." return 1 fi if hash git &> /dev/null; then # We want to capture here a logger message that goes to fd 9 std_output=$(Call_Codebase_Function Print_Software_Version 9>&1) - if [[ $? -ne 0 || $(grep -c "${HYBRID_codebase_version}" <<< "${std_output}") -eq 0 ]] ; then + if [[ $? -ne 0 || $(grep -c "${HYBRID_codebase_version}" <<< "${std_output}") -eq 0 ]]; then Print_Error "Version printing with git failed." return 1 fi diff --git a/tests/utility_functions.bash b/tests/utility_functions.bash index 3a0204e..9310157 100644 --- a/tests/utility_functions.bash +++ b/tests/utility_functions.bash @@ -10,15 +10,15 @@ function Define_Available_Tests_For() { case "$1" in - unit_tests ) + unit_tests) local -r files_prefix='unit_tests_' local -r functions_prefix='Unit_Test__' ;; - functional_tests ) + functional_tests) local -r files_prefix='functional_tests_' local -r functions_prefix='Functional_Test__' ;; - * ) + *) Print_Internal_And_Exit 'Wrong call to ' --emph "${FUNCNAME}" ' function.' ;; esac @@ -38,11 +38,11 @@ function Define_Available_Tests_For() source "${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done # Available tests are based on functions in this file whose names begins with "${functions_prefix}" - local -r grep_regex='^function[[:space:]]+'"${functions_prefix}"'[-[:alnum:]_:]+\(\)[[:space:]]*$'\ - sed_regex='^function[[:space:]]+'"${functions_prefix}"'([^(]+)\(\)[[:space:]]*$' + local -r grep_regex='^function[[:space:]]+'"${functions_prefix}"'[-[:alnum:]_:]+\(\)[[:space:]]*$' \ + sed_regex='^function[[:space:]]+'"${functions_prefix}"'([^(]+)\(\)[[:space:]]*$' HYBRIDT_tests_to_be_run=( # Here word splitting can split names, no space allowed in function name! - $(grep -hE "${grep_regex}" "${files_to_be_sourced[@]}" |\ - sed -E 's/'"${sed_regex}"'/\1/') + $(grep -hE "${grep_regex}" "${files_to_be_sourced[@]}" \ + | sed -E 's/'"${sed_regex}"'/\1/') ) } diff --git a/tests/utility_functions_functional.bash b/tests/utility_functions_functional.bash index 82f4a52..1f1ade2 100644 --- a/tests/utility_functions_functional.bash +++ b/tests/utility_functions_functional.bash @@ -18,23 +18,23 @@ function Check_If_Software_Produced_Expected_Output() Sampler) expected_output_files=4 ;; - Afterburner ) + Afterburner) expected_output_files=6 ;; - * ) + *) Print_Internal_And_Exit 'Invalid case branch entered in ' --emph "${FUNCNAME}." ;; esac - unfinished_files=( "${folder}"/*.{unfinished,lock} ) - output_files=( "${folder}"/* ) + unfinished_files=("${folder}"/*.{unfinished,lock}) + output_files=("${folder}"/*) if [[ ${#unfinished_files[@]} -gt 0 ]]; then exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit \ - 'Some unexpected ' --emph '.{unfinished,lock}' ' output file remained'\ + 'Some unexpected ' --emph '.{unfinished,lock}' ' output file remained' \ 'in ' --emph "${folder}" return 1 elif [[ ${#output_files[@]} -ne ${expected_output_files} ]]; then exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit \ - 'Expected ' --emph "${expected_output_files}" ' output files in '\ + 'Expected ' --emph "${expected_output_files}" ' output files in ' \ --emph "${block}" " folder, but ${#output_files[@]} found." return 1 fi From 885ef8045a4f9de1b94904e17dc369ff285e74e3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 30 Nov 2023 09:46:30 +0100 Subject: [PATCH 265/549] Reformat full codebase with shfmt disabling -kp option Actually the shfmt developers realised that the -kp has too many border cases and, indeed, it does some weird formatting sometimes. They even plan to remove this in the future v4 of their codebase. See issue https://github.com/mvdan/sh/issues/658 for more information. Hence, we decided to avoid using it here, too. After all, the result of the formatting is not so bad looking and the main difference is that we now have to only indent once more lines after the first of multi-line commands instead of making them align after the command name of the first line. --- bash/Afterburner_functionality.bash | 4 +- bash/Hydro_functionality.bash | 4 +- bash/Sampler_functionality.bash | 30 ++--- bash/command_line_parsers/helper.bash | 38 +++--- bash/command_line_parsers/main_parser.bash | 6 +- bash/configuration_parser.bash | 8 +- bash/global_variables.bash | 114 +++++++++--------- bash/sanity_checks.bash | 6 +- bash/system_requirements.bash | 58 ++++----- bash/utility_functions.bash | 34 +++--- bash/version.bash | 18 +-- tests/command_line_parser_for_tests.bash | 40 +++--- tests/functional_tests.bash | 4 +- tests/functional_tests_Afterburner_only.bash | 14 +-- tests/functional_tests_Hydro_only.bash | 6 +- tests/functional_tests_version.bash | 4 +- tests/tests_runner | 10 +- tests/unit_tests.bash | 4 +- .../unit_tests_Afterburner_functionality.bash | 8 +- tests/unit_tests_Sampler_functionality.bash | 8 +- tests/unit_tests_command_line_parser.bash | 6 +- tests/unit_tests_configuration.bash | 2 +- tests/unit_tests_formatting.bash | 6 +- tests/unit_tests_system_requirements.bash | 2 +- tests/unit_tests_utility_functions.bash | 2 +- tests/unit_tests_version.bash | 6 +- tests/utility_functions.bash | 2 +- 27 files changed, 222 insertions(+), 222 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 544b4f2..ca59c00 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -36,7 +36,7 @@ function Prepare_Software_Input_File_Afterburner() 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml" \ '\ndoes not exist, but is needed to check number of initial nucleons.' \ 'This file is expected to be produced by the IC software run.' - elif [[ ! -f "${HYBRID_software_input_file[Spectators]}" ]]; then + elif [[ ! -f "${HYBRID_software_input_file[Spectators]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Spectator file ' --emph "${HYBRID_software_input_file[Spectators]}" \ ' does not exist.' @@ -72,7 +72,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0" \ - ' was not found.' + ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then Print_Internal_And_Exit \ 'Something went wrong when creating the Afterburner symbolic link.' diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 4bb1f53..acdc327 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -76,7 +76,7 @@ function Ensure_All_Needed_Input_Exists_Hydro() 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' fi if [[ ! -e "${HYBRID_software_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ 'The input file ' --emph "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ ' was not found.' elif [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then @@ -93,7 +93,7 @@ function Run_Software_Hydro() ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}" \ - "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" + "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" } Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 25fc273..691b419 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -37,23 +37,23 @@ function Prepare_Software_Input_File_Sampler() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'TXT' "${HYBRID_software_configuration_file[Sampler]}" \ "$(printf "%s: %s\n" \ - 'surface' "${freezeout_path}" \ - 'spectra_dir' "${output_directory}")" + 'surface' "${freezeout_path}" \ + 'spectra_dir' "${output_directory}")" # The following symbolic link is not needed by the sampler, as the sampler only refers to information # specified in its input file. However, we want to have all input for a software in the output folder # for future easier reproducibility (and we do so for all software handled in the codebase). - if [[ "$( dirname "${freezeout_path}")" != "${HYBRID_software_output_directory[Sampler]}" ]]; then + if [[ "$(dirname "${freezeout_path}")" != "${HYBRID_software_output_directory[Sampler]}" ]]; then ln -s "${freezeout_path}" \ - "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" + "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" fi } function Ensure_All_Needed_Input_Exists_Sampler() { if [[ ! -d "${HYBRID_software_output_directory[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}" \ - ' does not exist.' + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ + 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}" \ + ' does not exist.' fi if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ @@ -73,8 +73,8 @@ function Run_Software_Sampler() local -r sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" cd "${HYBRID_software_output_directory[Sampler]}" - "${HYBRID_software_executable[Sampler]}" 'events' '1' \ - "${sampler_config_file_path}" >> "${sampler_terminal_output}" + "${HYBRID_software_executable[Sampler]}" 'events' '1' \ + "${sampler_config_file_path}" >> "${sampler_terminal_output}" } function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() @@ -83,7 +83,7 @@ function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() field="$1" # We assume here that the configuration file is fine as it was validated before value=$(awk -v name="${field}" '$1 == name {print $2; exit}' \ - "${HYBRID_software_configuration_file[Sampler]}") + "${HYBRID_software_configuration_file[Sampler]}") cd "${HYBRID_software_output_directory[Sampler]}" # If realpath succeeds, it prints the path that is the result of the function if ! realpath "${value}" 2> /dev/null; then @@ -94,7 +94,7 @@ function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() } function __static__Is_Sampler_Config_Valid() - { +{ local -r config_file="${HYBRID_software_configuration_file[Sampler]}" # Remove empty lines from configuration file if ! sed -i '/^[[:space:]]*$/d' "${config_file}"; then @@ -155,21 +155,21 @@ function __static__Is_Sampler_Config_Valid() rescatter | weakContribution | shear) if [[ ! "${value}" =~ ^[01]$ ]]; then Print_Error 'Key ' --emph "${key}" ' must be either ' \ - --emph '0' ' or ' --emph '1' '.' + --emph '0' ' or ' --emph '1' '.' return 1 fi ;; number_of_events | Nbins) if [[ ! "${value}" =~ ^[1-9][0-9]*$ ]]; then Print_Error 'Found not-integer value ' --emph "${value}" \ - ' for ' --emph "${key}" ' key.' + ' for ' --emph "${key}" ' key.' return 1 fi ;; *) if [[ ! "${value}" =~ ^[+-]?[0-9]+(\.[0-9]*)?$ ]]; then Print_Error 'Found invalid value ' --emph "${value}" \ - ' for ' --emph "${key}" ' key.' + ' for ' --emph "${key}" ' key.' return 1 fi ;; @@ -178,7 +178,7 @@ function __static__Is_Sampler_Config_Valid() # Check that all required keys were found if [[ ${keys_to_be_found} -gt 0 ]]; then Print_Error 'Either ' --emph 'surface' ' or ' --emph 'spectra_dir' \ - ' key is missing in sampler configuration file.' + ' key is missing in sampler configuration file.' return 1 fi } diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index a0148e4..51615f2 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -31,15 +31,15 @@ function __static__Print_Main_Help_Message() # that handler prerequisites are missing. Hence, possibly only bash should be used. declare -A section_headers auxiliary_modes_description execution_modes_description section_headers=( - [auxiliary_modes_description]='Auxiliary modes for help, information or setup' - [execution_modes_description]='Prepare needed files and folders and/or submit/run new simulation(s)' + [auxiliary_modes_description]='Auxiliary modes for help, information or setup' + [execution_modes_description]='Prepare needed files and folders and/or submit/run new simulation(s)' ) auxiliary_modes_description=( - [help]='Display this help message' - [version]='Get information about the version in use' + [help]='Display this help message' + [version]='Get information about the version in use' ) execution_modes_description=( - [do]='Do everything is necessary to run the workflow given in the configuration file' + [do]='Do everything is necessary to run the workflow given in the configuration file' ) __static__Print_Handler_Header_And_Usage_Synopsis __static__Print_Modes_Description @@ -49,7 +49,7 @@ function __static__Print_Main_Help_Message() function __static__Print_Do_Help_Message() { printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n' \ - 'You can specify the following command line options to the' 'do' 'execution mode:' + 'You can specify the following command line options to the' 'do' 'execution mode:' __static__Print_Command_Line_Option_Help \ '-o | --output-directory' "${HYBRID_output_directory}" \ "Directory where the run folder(s) will be created." @@ -61,19 +61,19 @@ function __static__Print_Do_Help_Message() function __static__Print_Handler_Header_And_Usage_Synopsis() { printf '\e[96m%s\e[0m\n' \ - ' #----------------------------------------------------------------------------#' \ - ' # __ __ __ _ __ __ __ ____ #' \ - ' # / / / /_ __/ /_ _____(_)___/ / / / / /___ _____ ____/ / /__ _____ #' \ - ' # / /_/ / / / / __ \/ ___/ / __ / / /_/ / __ `/ __ \/ __ / / _ \/ ___/ #' \ - ' # / __ / /_/ / /_/ / / / / /_/ / / __ / /_/ / / / / /_/ / / __/ / #' \ - ' # /_/ /_/\__, /_.___/_/ /_/\__,_/ /_/ /_/\__,_/_/ /_/\__,_/_/\___/_/ #' \ - ' # /____/ #' \ - ' # #' \ - ' #----------------------------------------------------------------------------#' + ' #----------------------------------------------------------------------------#' \ + ' # __ __ __ _ __ __ __ ____ #' \ + ' # / / / /_ __/ /_ _____(_)___/ / / / / /___ _____ ____/ / /__ _____ #' \ + ' # / /_/ / / / / __ \/ ___/ / __ / / /_/ / __ `/ __ \/ __ / / _ \/ ___/ #' \ + ' # / __ / /_/ / /_/ / / / / /_/ / / __ / /_/ / / / / /_/ / / __/ / #' \ + ' # /_/ /_/\__, /_.___/_/ /_/\__,_/ /_/ /_/\__,_/_/ /_/\__,_/_/\___/_/ #' \ + ' # /____/ #' \ + ' # #' \ + ' #----------------------------------------------------------------------------#' printf '\n' printf '\e[38;5;85m%s\e[0m\n' \ - ' USAGE: Hybrid-handler [--help] [--version]' \ - ' [...]' + ' USAGE: Hybrid-handler [--help] [--version]' \ + ' [...]' printf '\n' } @@ -82,7 +82,7 @@ function __static__Print_Modes_Description() Ensure_That_Given_Variables_Are_Set_And_Not_Empty section_headers "${!section_headers[@]}" local section mode section_string printf '\e[38;5;38m %s\e[0m\n' \ - 'Here in the following you find an overview of the existing execution modes.' + 'Here in the following you find an overview of the existing execution modes.' for section in "${!section_headers[@]}"; do printf "\n \e[93m${section_headers[${section}]}\e[0m\n" declare -n list_of_modes="${section}" @@ -105,7 +105,7 @@ function __static__Print_Modes_Description() fi done printf '\n\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n\n' \ - 'Use' '--help' 'after each non auxiliary mode to get further information about it.' + 'Use' '--help' 'after each non auxiliary mode to get further information about it.' } function __static__Print_Command_Line_Option_Help() diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 1bbb3cf..b20cb80 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -26,8 +26,8 @@ function Parse_Execution_Mode() ;; *) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ - 'Specified mode ' --emph "$1" ' not valid! Run ' \ - --emph 'Hybrid-handler help' ' to get further information.' + 'Specified mode ' --emph "$1" ' not valid! Run ' \ + --emph 'Hybrid-handler help' ' to get further information.' ;; esac shift @@ -36,7 +36,7 @@ function Parse_Execution_Mode() # Ignore any command line option in some specific cases if [[ ${HYBRID_execution_mode} =~ ^(help|version)$ ]]; then HYBRID_command_line_options_to_parse=() - elif Element_In_Array_Equals_To '--help' "${HYBRID_command_line_options_to_parse[@]}"; then + elif Element_In_Array_Equals_To '--help' "${HYBRID_command_line_options_to_parse[@]}"; then HYBRID_execution_mode+='-help' HYBRID_command_line_options_to_parse=() fi diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 25629a2..b6d75d0 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -38,7 +38,7 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() { local input_section_labels label index valid_software_label software_sections_indices # Here word splitting separates keys into array entries, hence we assume keys do not contain spaces! - input_section_labels=($( yq 'keys | .[]' "${HYBRID_configuration_file}")) + input_section_labels=($(yq 'keys | .[]' "${HYBRID_configuration_file}")) software_sections_indices=() for label in "${input_section_labels[@]}"; do if Element_In_Array_Equals_To "${label}" "${HYBRID_valid_auxiliary_configuration_sections[@]}"; then @@ -70,7 +70,7 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() fi local gaps_between_indices gaps_between_indices=$(awk 'NR>1{print $1-x}{x=$1}' \ - <(printf '%d\n' "${software_sections_indices[@]}") | sort -u) + <(printf '%d\n' "${software_sections_indices[@]}") | sort -u) if [[ "${gaps_between_indices}" != '1' ]]; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'Missing software section(s) in the handler configuration file.' @@ -116,7 +116,7 @@ function __static__Validate_Keys_Of_Section() local invalid_keys yaml_section if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" - invalid_keys=($( __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string "${yaml_section}")) + invalid_keys=($(__static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string "${yaml_section}")) if [[ ${#invalid_keys[@]} -ne 0 ]]; then invalid_report+=(" ${section_label}" "${invalid_keys[@]/#/ }") fi @@ -128,7 +128,7 @@ function __static__Get_Top_Level_Invalid_Keys_In_Given_YAML_string() { Ensure_That_Given_Variables_Are_Set valid_keys local input_keys key invalid_keys - input_keys=($( yq 'keys | .[]' <<< "$1")) + input_keys=($(yq 'keys | .[]' <<< "$1")) invalid_keys=() for key in "${input_keys[@]}"; do if ! Element_In_Array_Equals_To "${key}" "${valid_keys[@]}"; then diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 16a4210..132374d 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -29,47 +29,47 @@ function Define_Further_Global_Variables() readonly HYBRID_default_configurations_folder="${HYBRID_top_level_path}/configs" readonly HYBRID_python_folder="${HYBRID_top_level_path}/python" declare -rgA HYBRID_external_python_scripts=( - [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" + [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" ) - declare -rgA HYBRID_software_default_input_filename=( - [IC]='' - [Hydro]="SMASH_IC.dat" - [Sampler]="freezeout.dat" # Not used at the moment for how the sampler works - [Spectators]="SMASH_IC.oscar" - [Afterburner]="particle_lists.oscar" + declare -rgA HYBRID_software_default_input_filename=( + [IC]='' + [Hydro]="SMASH_IC.dat" + [Sampler]="freezeout.dat" # Not used at the moment for how the sampler works + [Spectators]="SMASH_IC.oscar" + [Afterburner]="particle_lists.oscar" ) declare -rgA HYBRID_software_configuration_filename=( - [IC]='IC_config.yaml' - [Hydro]='hydro_config.txt' - [Sampler]='sampler_config.txt' - [Afterburner]='afterburner_config.yaml' + [IC]='IC_config.yaml' + [Hydro]='hydro_config.txt' + [Sampler]='sampler_config.txt' + [Afterburner]='afterburner_config.yaml' ) # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=() declare -rgA HYBRID_ic_valid_keys=( - [Executable]='HYBRID_software_executable[IC]' - [Config_file]='HYBRID_software_base_config_file[IC]' - [Software_keys]='HYBRID_software_new_input_keys[IC]' + [Executable]='HYBRID_software_executable[IC]' + [Config_file]='HYBRID_software_base_config_file[IC]' + [Software_keys]='HYBRID_software_new_input_keys[IC]' ) declare -rgA HYBRID_hydro_valid_keys=( - [Executable]='HYBRID_software_executable[Hydro]' - [Config_file]='HYBRID_software_base_config_file[Hydro]' - [Input_file]='HYBRID_software_user_custom_input_file[Hydro]' - [Software_keys]='HYBRID_software_new_input_keys[Hydro]' + [Executable]='HYBRID_software_executable[Hydro]' + [Config_file]='HYBRID_software_base_config_file[Hydro]' + [Input_file]='HYBRID_software_user_custom_input_file[Hydro]' + [Software_keys]='HYBRID_software_new_input_keys[Hydro]' ) declare -rgA HYBRID_sampler_valid_keys=( - [Executable]='HYBRID_software_executable[Sampler]' - [Config_file]='HYBRID_software_base_config_file[Sampler]' - [Software_keys]='HYBRID_software_new_input_keys[Sampler]' + [Executable]='HYBRID_software_executable[Sampler]' + [Config_file]='HYBRID_software_base_config_file[Sampler]' + [Software_keys]='HYBRID_software_new_input_keys[Sampler]' ) declare -rgA HYBRID_afterburner_valid_keys=( - [Executable]='HYBRID_software_executable[Afterburner]' - [Config_file]='HYBRID_software_base_config_file[Afterburner]' - [Input_file]='HYBRID_software_user_custom_input_file[Afterburner]' - [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' - [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' - [Spectators_source]='HYBRID_optional_feature[Spectators_source]' + [Executable]='HYBRID_software_executable[Afterburner]' + [Config_file]='HYBRID_software_base_config_file[Afterburner]' + [Input_file]='HYBRID_software_user_custom_input_file[Afterburner]' + [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' + [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' + [Spectators_source]='HYBRID_optional_feature[Spectators_source]' ) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' @@ -78,51 +78,51 @@ function Define_Further_Global_Variables() # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_given_software_sections=() declare -gA HYBRID_software_executable=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) declare -gA HYBRID_software_user_custom_input_file=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Spectators]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Spectators]='' + [Afterburner]='' ) declare -gA HYBRID_software_base_config_file=( - [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" - [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" - [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" - [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" + [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" + [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" + [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" + [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" ) declare -gA HYBRID_software_new_input_keys=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) declare -gA HYBRID_optional_feature=( - [Add_spectators_from_IC]='FALSE' - [Spectators_source]='' + [Add_spectators_from_IC]='FALSE' + [Spectators_source]='' ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded declare -gA HYBRID_software_output_directory=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) declare -gA HYBRID_software_configuration_file=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' ) declare -gA HYBRID_software_input_file=( - [Hydro]='' - [Spectators]='' - [Afterburner]='' + [Hydro]='' + [Spectators]='' + [Afterburner]='' ) } diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 6028c79..29cc212 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -35,7 +35,7 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var "${HYBRID_software_output_directory[${relative_key}]}" \ "${HYBRID_software_default_input_filename[${key}]}" else - if Element_In_Array_Equals_To "${relative_key}" "${HYBRID_given_software_sections[@]}"; then + if Element_In_Array_Equals_To "${relative_key}" "${HYBRID_given_software_sections[@]}"; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Requesting custom ' --emph "${key}" ' input file although executing ' \ --emph "${relative_key}" ' with default output name.' @@ -45,10 +45,10 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var fi fi done - if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then if [[ "${HYBRID_optional_feature[Spectators_source]}" != '' ]]; then HYBRID_software_input_file['Spectators']="${HYBRID_optional_feature[Spectators_source]}" - if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then + if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Requesting custom ' --emph 'Spectators' ' input file although executing ' \ --emph 'IC' ' with default output name.' diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 434b4c5..a536e09 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -19,11 +19,11 @@ function __static__Declare_System_Requirements() if ! declare -p HYBRID_version_regex &> /dev/null; then readonly HYBRID_version_regex='[0-9](.[0-9]+)*' declare -rgA HYBRID_versions_requirements=( - [awk]='4.1' - [bash]='4.4' - [sed]='4.2.1' - [tput]='5.7' - [yq]='4.18.1' + [awk]='4.1' + [bash]='4.4' + [sed]='4.2.1' + [tput]='5.7' + [yq]='4.18.1' ) declare -rga HYBRID_programs_just_required=( cat @@ -72,8 +72,8 @@ function Check_System_Requirements_And_Make_Report() { __static__Declare_System_Requirements local system_report=() - local -r single_field_length=18 # This variable is used to prepare the report correctly formatted - declare -A system_information # Same use of this variable as in 'Check_System_Requirements' function + local -r single_field_length=18 # This variable is used to prepare the report correctly formatted + declare -A system_information # Same use of this variable as in 'Check_System_Requirements' function __static__Analyze_System_Properties __static__Print_Report_Title __static__Print_Report_Of_Programs_With_Minimum_version @@ -137,7 +137,7 @@ function __static__Analyze_System_Properties() # Handling of function exit code must be done in this form because of errexit mode; # the subshell is needed because the function exits when the variable is unset or empty return_code=0 - ( Ensure_That_Given_Variables_Are_Set_And_Not_Empty "${name}" &> /dev/null ) || return_code=$? + (Ensure_That_Given_Variables_Are_Set_And_Not_Empty "${name}" &> /dev/null) || return_code=$? if [[ ${return_code} -eq 0 ]]; then system_information["${name}"]='OK' else @@ -157,8 +157,8 @@ function __static__Exit_If_Some_GNU_Requirement_Is_Missing() is_gnu=$(__static__Get_Field_In_System_Information_String "GNU-${program}" 1) if [[ ${is_gnu} = '---' ]]; then Print_Error --emph "${program}" ' either not found or non-GNU version in use.' \ - 'Please, ensure that ' --emph "${program}" ' is installed and in use.' - ((errors++)) || true + 'Please, ensure that ' --emph "${program}" ' is installed and in use.' + ((errors++)) || true fi done if [[ ${errors} -ne 0 ]]; then @@ -174,22 +174,22 @@ function __static__Exit_If_Minimum_Versions_Are_Not_Available() min_version=${HYBRID_versions_requirements["${program}"]} program_found=$(__static__Get_Field_In_System_Information_String "${program}" 0) version_found=$(__static__Get_Field_In_System_Information_String "${program}" 1) - version_ok=$( __static__Get_Field_In_System_Information_String "${program}" 2) + version_ok=$(__static__Get_Field_In_System_Information_String "${program}" 2) if [[ "${program_found}" = '---' ]]; then Print_Error --emph "${program}" ' command not found! Minimum version ' \ - --emph "${min_version}" ' is required.' - ((errors++)) || true + --emph "${min_version}" ' is required.' + ((errors++)) || true continue fi if [[ ${version_found} = '---' ]]; then Print_Warning 'Unable to find version of ' --emph "${program}" ', skipping version check!' \ - 'Please ensure that current version is at least ' --emph "${min_version}" '.' + 'Please ensure that current version is at least ' --emph "${min_version}" '.' continue fi if [[ "${version_ok}" = '---' ]]; then Print_Error --emph "${program}" ' version ' --emph "${version_found}" \ - ' found, but version ' --emph "${min_version}" ' is required.' - ((errors++)) || true + ' found, but version ' --emph "${min_version}" ' is required.' + ((errors++)) || true fi done if [[ ${errors} -ne 0 ]]; then @@ -204,8 +204,8 @@ function __static__Exit_If_Some_Needed_Environment_Variable_Is_Missing() Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[${name}]" if [[ ${system_information[${name}]} = '---' ]]; then Print_Error --emph "${name}" ' environment variable either unset or empty.' \ - 'Please, ensure that ' --emph "${name}" ' is properly set.' - ((errors++)) || true + 'Please, ensure that ' --emph "${name}" ' is properly set.' + ((errors++)) || true fi done if [[ ${errors} -ne 0 ]]; then @@ -279,7 +279,7 @@ function __static__Print_Formatted_Binary_Report() # here. Although in some cases this might fail, we refresh and use COLUMNS variable # here (see https://stackoverflow.com/a/48016366/14967071 for more information). shopt -s checkwinsize # Do not assume it is on (as it usually is) - ( : ) # Refresh LINES and COLUMNS, this happens when a child process exits + (:) # Refresh LINES and COLUMNS, this happens when a child process exits local -r num_cols=$((${COLUMNS-100} / 2 / single_field_length)) local index printf_descriptor printf_descriptor="%${single_field_length}s" # At least one column @@ -322,14 +322,14 @@ function __static__Try_Find_Version() ;; tput) found_version=$(tput -V | grep -oE "${HYBRID_version_regex}") - found_version=(${found_version//./ }) # Use word split to separate version numbers + found_version=(${found_version//./ }) # Use word split to separate version numbers found_version="${found_version[0]-}.${found_version[1]-}" # Use empty variables if 'tput -V' failed ;; yq) # Versions before v4.30.3 do not have the 'v' prefix found_version=$(yq --version \ - | grep -oE "version [v]?${HYBRID_version_regex}" \ - | grep -oE "${HYBRID_version_regex}") + | grep -oE "version [v]?${HYBRID_version_regex}" \ + | grep -oE "${HYBRID_version_regex}") ;; *) Print_Internal_And_Exit 'Version finding for ' --emph "$1" ' to be added!' @@ -395,8 +395,8 @@ function __static__Get_Larger_Version() shorter_version=v2 dots_of_shorter_version=dots_of_v2 dots_of_longer_version=dots_of_v1 fi while [[ ${#dots_of_shorter_version} -ne ${#dots_of_longer_version} ]]; do - shorter_version+='.0' # Add zeroes to shorter string - dots_of_shorter_version+='.' # Add dot to keep counting accurate + shorter_version+='.0' # Add zeroes to shorter string + dots_of_shorter_version+='.' # Add dot to keep counting accurate done # If versions are equal, we're done if [[ "${v2}" = "${v1}" ]]; then @@ -412,10 +412,10 @@ function __static__Get_Larger_Version() if [[ "${v1_array[index]}" -eq "${v2_array[index]}" ]]; then continue elif [[ "${v1_array[index]}" -lt "${v2_array[index]}" ]]; then - printf "$2" # Print input version, unchanged + printf "$2" # Print input version, unchanged return else - printf "$1" # Print input version, unchanged + printf "$1" # Print input version, unchanged return fi done @@ -432,7 +432,7 @@ function __static__Print_Requirement_Version_Report_Line() text_color='\e[38;5;38m' \ default='\e[0m' local line found version_found version_ok tmp_array program=$1 - tmp_array=(${system_information[${program}]//|/ }) # Unquoted to let word splitting act + tmp_array=(${system_information[${program}]//|/ }) # Unquoted to let word splitting act found=${tmp_array[0]} version_found=${tmp_array[1]} version_ok=${tmp_array[2]} @@ -443,7 +443,7 @@ function __static__Print_Requirement_Version_Report_Line() line+="${green} " fi line+=$(printf "found ${text_color}Required version: ${emph_color}%6s${default}" \ - "${HYBRID_versions_requirements[${program}]}") + "${HYBRID_versions_requirements[${program}]}") if [[ ${found} != '---' ]]; then line+=" ${text_color}System version:${default} " if [[ ${version_found} =~ ^(---|\?)$ ]]; then @@ -487,7 +487,7 @@ function __static__Get_Single_Tick_Cross_Requirement_Report() function __static__Get_Field_In_System_Information_String() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty "system_information[$1]" - local tmp_array=(${system_information[$1]//|/ }) # Unquoted to let word splitting act + local tmp_array=(${system_information[$1]//|/ }) # Unquoted to let word splitting act printf '%s' "${tmp_array[$2]}" } diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 7ec81f5..afe5750 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -35,7 +35,7 @@ function Has_YAML_String_Given_Key() Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with less than 2 arguments.' fi yaml_string=$1 - shift + shift if ! yq <<< "${yaml_string}" &> /dev/null; then Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with invalid YAML string.' fi @@ -60,7 +60,7 @@ function Read_From_YAML_String_Given_Key() Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with YAML string not containing given key.' fi yaml_string=$1 - shift + shift key="$(printf '.%s' "$@")" yq "${key}" <<< "${yaml_string}" } @@ -77,7 +77,7 @@ function Print_YAML_String_Without_Given_Key() Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with YAML string not containing given key.' fi yaml_string=$1 - shift + shift key="$(printf '.%s' "$@")" yq 'del('"${key}"')' <<< "${yaml_string}" } @@ -86,9 +86,9 @@ function Print_Line_of_Equals() { local length indentation prefix postfix length="$1" - indentation="${2-}" # Input arg. or empty string - prefix="${3-}" # Input arg. or empty string - postfix="${4-\n}" # Input arg. or endline + indentation="${2-}" # Input arg. or empty string + prefix="${3-}" # Input arg. or empty string + postfix="${4-\n}" # Input arg. or endline printf "${prefix}${indentation}" for ((i = 0; i < ${length}; i++)); do printf '=' @@ -99,7 +99,7 @@ function Print_Line_of_Equals() function Print_Centered_Line() { local input_string output_total_width indentation padding_character \ - postfix real_length padding_utility + postfix real_length padding_utility input_string="$1" output_total_width="${2:-$(tput cols)}" # Input arg. or full width of terminal indentation="${3-}" # Input arg. or empty string @@ -116,11 +116,11 @@ function Print_Centered_Line() # that the number to be used there is passed to printf as argument. padding_utility="$(printf '%0.1s' "${padding_character}"{1..500})" printf "${indentation}%0.*s %s %0.*s${postfix}" \ - "$(((output_total_width - 2 - real_length) / 2))" \ - "${padding_utility}" \ - "${input_string}" \ - "$(((output_total_width - 2 - real_length) / 2))" \ - "${padding_utility}" + "$(((output_total_width - 2 - real_length) / 2))" \ + "${padding_utility}" \ + "${input_string}" \ + "$(((output_total_width - 2 - real_length) / 2))" \ + "${padding_utility}" } function Print_Option_Specification_Error_And_Exit() @@ -193,11 +193,11 @@ function Call_Function_If_Existing_Or_No_Op() # 'declare -p array[0]' would fail even if array[0] existed, while the test # [[ -v array[0] ]] would succeed. Hence we treat this case separately. function Ensure_That_Given_Variables_Are_Set() - { +{ local variable_name for variable_name in "$@"; do if ! declare -p "${variable_name}" &> /dev/null; then - if [[ ${variable_name} =~ \]$ && -v ${variable_name} ]]; then + if [[ ${variable_name} =~ \]$ && -v ${variable_name} ]]; then continue fi Print_Internal_And_Exit \ @@ -213,7 +213,7 @@ function Ensure_That_Given_Variables_Are_Set() # Hence, for 'foo=""', ${#foo[@]} would return 1 and a non zero length is not # synonym of a non-empty variable. function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() - { +{ local variable_name for variable_name in "$@"; do # The following can be done using the "${ref@A}" bash-5 expansion which @@ -224,7 +224,7 @@ function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() continue fi else - set +u # Here variable_name might be unset! Do not exit if so + set +u # Here variable_name might be unset! Do not exit if so if [[ "${!variable_name}" != '' ]]; then set -u continue @@ -255,7 +255,7 @@ function Make_Functions_Defined_In_This_File_Readonly() local declared_functions declared_functions=( # Here word splitting can split names, no space allowed in function name! $(grep -E '^[[:space:]]*function[[:space:]]+[-[:alnum:]_:]+\(\)[[:space:]]*$' "${BASH_SOURCE[1]}" \ - | sed -E 's/^[[:space:]]*function[[:space:]]+([^(]+)\(\)[[:space:]]*$/\1/') + | sed -E 's/^[[:space:]]*function[[:space:]]+([^(]+)\(\)[[:space:]]*$/\1/') ) if [[ ${#declared_functions[@]} -eq 0 ]]; then Print_Internal_And_Exit \ diff --git a/bash/version.bash b/bash/version.bash index ec27044..67eaead 100644 --- a/bash/version.bash +++ b/bash/version.bash @@ -13,16 +13,16 @@ function Print_Software_Version() # First handle cases where git is not available, or codebase downloaded as archive and not cloned # NOTE: Git introduced -C option in version 1.8.5 if ! hash git &> /dev/null \ - || __static__Is_Git_Version_Older_Than '1.8.3' \ - || ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null; then + || __static__Is_Git_Version_Older_Than '1.8.3' \ + || ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null; then __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" return 0 fi local git_tag_short git_tag_long tag_date if ! git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags 2> /dev/null); then Print_Warning 'It was not possible to obtain the version in use!' \ - 'This probably (but not necessarily) means that you are' \ - 'behind any release in the Hybrid-handler history.\n' + 'This probably (but not necessarily) means that you are' \ + 'behind any release in the Hybrid-handler history.\n' __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" return 0 fi @@ -37,11 +37,11 @@ function Print_Software_Version() git_tag_long=$(git -C "${HYBRID_top_level_path}" describe --tags --dirty --broken 2> /dev/null) fi Print_Warning 'You are not using an official release of the Hybrid-handler.' \ - 'Unless you have a reason not to do so, it would be better' \ - 'to checkout a stable release. The last stable release behind' \ - 'the commit you are using is: ' --emph "${git_tag_short}" \ - ' (' --emph "${tag_date}" ')\n' 'The repository state is ' \ - --emph "${git_tag_long}" '' '(see git-describe documentation for more information).' + 'Unless you have a reason not to do so, it would be better' \ + 'to checkout a stable release. The last stable release behind' \ + 'the commit you are using is: ' --emph "${git_tag_short}" \ + ' (' --emph "${tag_date}" ')\n' 'The repository state is ' \ + --emph "${git_tag_long}" '' '(see git-describe documentation for more information).' else __static__Print_Pretty_Version_Line "${git_tag_short}" "${tag_date}" fi diff --git a/tests/command_line_parser_for_tests.bash b/tests/command_line_parser_for_tests.bash index 4b53017..877bff3 100644 --- a/tests/command_line_parser_for_tests.bash +++ b/tests/command_line_parser_for_tests.bash @@ -42,7 +42,7 @@ function Parse_Tests_Command_Line_Options() __static__Print_Helper exit ${HYBRID_success_exit_code} shift - ;; + ;; -r | --report-level) if [[ ${2-} =~ ^[0-3]$ ]]; then readonly HYBRIDT_report_level=$2 @@ -50,7 +50,7 @@ function Parse_Tests_Command_Line_Options() Print_Option_Specification_Error_And_Exit "$1" fi shift 2 - ;; + ;; -t | --run-tests) if [[ ! ${2-} =~ ^- && "${2-}" != '' ]]; then if [[ $2 =~ ^[1-9][0-9]*([,\-][1-9][0-9]*)*$ ]]; then @@ -65,11 +65,11 @@ function Parse_Tests_Command_Line_Options() exit ${HYBRID_success_exit_code} fi shift 2 - ;; + ;; -k | --keep-tests-folder) HYBRIDT_clean_test_folder='FALSE' shift - ;; + ;; *) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ 'Invalid option ' --emph "$1" ' specified! Use the ' \ @@ -92,19 +92,19 @@ function __static__Print_Helper() __static__Add_Option_To_Helper "unit" "Unit tests of the codebase." printf " ${emph_color}Execute tests with the following optional arguments:${default_color}\n\n" __static__Add_Option_To_Helper "-r | --report-level" \ - "Verbosity of test report (default value ${HYBRIDT_report_level})." \ - "To be chosen among" \ - " 0 = binary, 1 = summary, 2 = short, 3 = detailed." + "Verbosity of test report (default value ${HYBRIDT_report_level})." \ + "To be chosen among" \ + " 0 = binary, 1 = summary, 2 = short, 3 = detailed." __static__Add_Option_To_Helper "-t | --run-tests" \ - "Specify which tests have to be run. A comma-separated" \ - "list of numbers and/or of intervals (e.g. 1,3-5) or" \ - "a string (e.g. 'help*') has to be specified. The string" \ - "is matched against test names using bash regular globbing." \ - "Remember to quote the argument to avoid shell expansion." \ - "If no value is specified the available tests list is printed." \ - "Without this option all existing tests are run." + "Specify which tests have to be run. A comma-separated" \ + "list of numbers and/or of intervals (e.g. 1,3-5) or" \ + "a string (e.g. 'help*') has to be specified. The string" \ + "is matched against test names using bash regular globbing." \ + "Remember to quote the argument to avoid shell expansion." \ + "If no value is specified the available tests list is printed." \ + "Without this option all existing tests are run." __static__Add_Option_To_Helper "-k | --keep-tests-folder" \ - "Leave all the created folders and files in the test folder." + "Leave all the created folders and files in the test folder." Print_Warning \ " Values from options must be separated by space and short options cannot be combined." } @@ -118,12 +118,12 @@ function __static__Add_Option_To_Helper() description="$2" shift 2 printf "${options_color}%s${default_color} -> ${text_color}%s\n" \ - "$(printf "%s%-${length_option}s" "${indentation}" "${name}")" \ - "${description}" + "$(printf "%s%-${length_option}s" "${indentation}" "${name}")" \ + "${description}" while [[ $# -gt 0 ]]; do printf "%s %s\n" \ - "$(printf "%s%${length_option}s" "${indentation}" "")" \ - "$1" + "$(printf "%s%${length_option}s" "${indentation}" "")" \ + "$1" shift done printf "${default_color}\n" @@ -182,7 +182,7 @@ function __static__Print_List_Of_Tests() { local index indentation width_of_list printf " \e[96mList of available tests:\e[0m\n\n" - width_of_list=$(($( tput cols) * 4 / 5)) + width_of_list=$(($(tput cols) * 4 / 5)) for ((index = 0; index < ${#HYBRIDT_tests_to_be_run[@]}; index++)); do printf '%3d) %s\n' "$((index + 1))" "${HYBRIDT_tests_to_be_run[index]}" done | column -c "${width_of_list}" diff --git a/tests/functional_tests.bash b/tests/functional_tests.bash index b81c35c..522c2c2 100644 --- a/tests/functional_tests.bash +++ b/tests/functional_tests.bash @@ -26,7 +26,7 @@ function Make_Test_Preliminary_Operations() function Run_Test() { local test_name=$1 - Functional_Test__$1 &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger + Functional_Test__$1 &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } function Clean_Tests_Environment_For_Following_Test() @@ -37,5 +37,5 @@ function Clean_Tests_Environment_For_Following_Test() function Run_Hybrid_Handler_With_Given_Options_In_Subshell() { - ("${HYBRIDT_repository_top_level_path}/Hybrid-handler" "$@" ) + ("${HYBRIDT_repository_top_level_path}/Hybrid-handler" "$@") } diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 22f8037..422bfac 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -14,7 +14,7 @@ function __static__Check_Successful_Handler_Run() 'Hybrid-handler unexpectedly failed.' return 1 fi - Check_If_Software_Produced_Expected_Output 'Afterburner' "$(pwd)/Afterburner" + Check_If_Software_Produced_Expected_Output 'Afterburner' "$(pwd)/Afterburner" } function Functional_Test__do-Afterburner-only() @@ -81,7 +81,7 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -98,7 +98,7 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then @@ -119,7 +119,7 @@ function Functional_Test__do-Afterburner-only() File_Directory: "." ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - __static__Check_Successful_Handler_Run $? + __static__Check_Successful_Handler_Run $? mv 'Afterburner' 'Afterburner-success-with-spectators' # Expect success and test the add_spectator functionality with custom spectator input Print_Info 'Running Hybrid-handler expecting success with the custom add_spectator option' @@ -135,9 +135,9 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - __static__Check_Successful_Handler_Run $? || return 1 + __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' # Expect failure when combining custom spectator lists and running IC Print_Info 'Running Hybrid-handler expecting failure with the add_spectator option and IC at the same time' @@ -156,7 +156,7 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 7570589..9fbdd33 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -53,7 +53,7 @@ function Functional_Test__do-Hydro-only() Hydro: Executable: %s/vhlle_black-box.py Input_file: %s/test/input - ' "$(pwd)" "$(pwd)" > "${config_filename}" + ' "$(pwd)" "$(pwd)" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated rm 'IC/SMASH_IC.dat' mkdir -p test @@ -105,10 +105,10 @@ function Functional_Test__do-Hydro-only() Hydro: Executable: %s/vhlle_black-box.py Input_file: %s/test/input - ' "$(pwd)" "$(pwd)" > "${config_filename}" + ' "$(pwd)" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" - if [[ $? -ne 110 ]]; then + if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi diff --git a/tests/functional_tests_version.bash b/tests/functional_tests_version.bash index 4217a4e..dfda8d1 100644 --- a/tests/functional_tests_version.bash +++ b/tests/functional_tests_version.bash @@ -18,8 +18,8 @@ function Functional_Test__version() Print_Warning 'Command ' --emph 'git' ' not available, part of test cannot be run.' else git_describe=$( - cd "${HYBRIDT_repository_top_level_path}" - git describe --abbrev=0 + cd "${HYBRIDT_repository_top_level_path}" + git describe --abbrev=0 ) printf "${version_output}\n" if [[ $(grep -c "${git_describe}" <<< "${version_output}") -gt 0 ]]; then diff --git a/tests/tests_runner b/tests/tests_runner index 7f682e5..a1242b9 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -143,12 +143,12 @@ function Announce_Running_Test() { local test_name padded_name test_name=$1 - shift + shift if [[ ${HYBRIDT_report_level} -eq 3 ]]; then printf -v padded_name "%-60s" "__${test_name}$(printf '\e[94m')_" padded_name="${padded_name// /.}" printf ' %+2s/%-2s\e[96m%s' \ - ${HYBRIDT_tests_run} ${#HYBRIDT_tests_to_be_run[@]} "${padded_name//_/ }" + ${HYBRIDT_tests_run} ${#HYBRIDT_tests_to_be_run[@]} "${padded_name//_/ }" fi } @@ -175,14 +175,14 @@ function Print_Tests_Report() { if [[ ${HYBRIDT_report_level} -ge 1 ]]; then local indentation left_margin test_name_string_length \ - index separator_length passed_string failed_string + index separator_length passed_string failed_string indentation=' ' left_margin=' ' test_name_string_length=$(printf '%s\n' "${HYBRIDT_tests_to_be_run[@]}" | wc -L) if [[ ${test_name_string_length} -lt 25 ]]; then # Minimum length test_name_string_length=25 fi - if ((test_name_string_length % 2 == 1)); then # Aesthetics + if ((test_name_string_length % 2 == 1)); then # Aesthetics ((test_name_string_length += 1)) fi separator_length=$((test_name_string_length + 3 + 2 * ${#left_margin})) @@ -226,7 +226,7 @@ function Delete_Tests_Files_If_No_Test_Failed_And_User_Wishes_So() label='symlink' elif [[ -f "${global_path}" ]]; then label='file' - elif [[ ! -e "${global_path}" ]]; then + elif [[ ! -e "${global_path}" ]]; then continue else Print_Internal_And_Exit 'Error in ' --emph "${FUNCNAME}" '.' \ diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index 7218924..ae86510 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -27,7 +27,7 @@ function Make_Test_Preliminary_Operations() function Run_Test() { - Unit_Test__$1 &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger + Unit_Test__$1 &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } function Clean_Tests_Environment_For_Following_Test() @@ -57,7 +57,7 @@ function __static__Call_Codebase_Function_As_Desired() # codebase function runs an exit command. local return_code=0 if [[ ${1-} = 'IN_SUBSHELL' ]]; then - ( Call_Function_If_Existing_Or_Exit "${@:2}" ) || return_code=$? + (Call_Function_If_Existing_Or_Exit "${@:2}") || return_code=$? else Call_Function_If_Existing_Or_Exit "$@" || return_code=$? fi diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index ed9e513..c709e71 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -82,7 +82,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" \ plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then Print_Error \ 'Files preparation did not fail with exit code 110' \ @@ -90,7 +90,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then Print_Error \ 'Files preparation did not fail with exit code 110' \ @@ -99,7 +99,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() fi rm "${HYBRID_software_output_directory[Afterburner]}/"* touch "${HYBRID_software_output_directory[IC]}/config.yaml" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then Print_Error \ 'Files preparation did not fail with exit code 110' \ @@ -155,7 +155,7 @@ function Unit_Test__Afterburner-check-all-input() rm "${HYBRID_software_output_directory[Afterburner]}/sampling0" touch "${HYBRID_software_output_directory[Sampler]}/original_sampling0" ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampling0" \ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" + "${HYBRID_software_output_directory[Afterburner]}/sampling0" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing file unexpectedly failed.' diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 41eac38..11a90ac 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -46,9 +46,9 @@ function Unit_Test__Sampler-create-input-file() # Ensure that paths in Sampler config were replaced by global paths local surface_path spectra_dir_path surface_path=$(awk '$1 == "surface" {print $2; exit}' \ - "${HYBRID_software_configuration_file[Sampler]}") + "${HYBRID_software_configuration_file[Sampler]}") spectra_dir_path=$(awk '$1 == "spectra_dir" {print $2; exit}' \ - "${HYBRID_software_configuration_file[Sampler]}") + "${HYBRID_software_configuration_file[Sampler]}") if [[ "${surface_path}" != /* || "${spectra_dir_path}" != /* ]]; then Print_Error 'Freezeout and/or output directory path in Sampler config is not a global path.' return 1 @@ -98,8 +98,8 @@ function Unit_Test__Sampler-check-all-input() return 1 fi printf '%s\n' \ - "surface $(which ls)" \ - "spectra_dir ${HOME}" > "${HYBRID_software_configuration_file[Sampler]}" + "surface $(which ls)" \ + "spectra_dir ${HOME}" > "${HYBRID_software_configuration_file[Sampler]}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Sampler if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of all input files unexpectedly failed.' diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index 72dd361..7620b7b 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -27,8 +27,8 @@ function __static__Test_Parsing_Of_Execution_Mode_In_Subshell_Expecting_Success( first_option="${HYBRID_command_line_options_to_parse[0]-}" Call_Codebase_Function Parse_Execution_Mode if [[ $? -ne 0 ]] \ - || [[ "${HYBRID_execution_mode}" != "${expected_option}" ]] \ - || [[ ${#HYBRID_command_line_options_to_parse[@]} -ne "${expected_size}" ]]; then + || [[ "${HYBRID_execution_mode}" != "${expected_option}" ]] \ + || [[ ${#HYBRID_command_line_options_to_parse[@]} -ne "${expected_size}" ]]; then Print_Error 'Parsing of valid execution mode ' --emph "${first_option}" ' failed.' return 1 fi @@ -76,7 +76,7 @@ function __static__Test_CLO_Parsing_Missing_Value() Call_Codebase_Function_In_Subshell Parse_Command_Line_Options &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Parsing of CLO ' --emph "${HYBRID_command_line_options_to_parse[0]}" \ - ' with missing value succeeded.' + ' with missing value succeeded.' return 1 fi } diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index c179d33..f8d985b 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -179,7 +179,7 @@ function __static__Test_Section_Parsing_In_Subshell() new_keys=$5 Call_Codebase_Function Validate_And_Parse_Configuration_File if [[ "${#HYBRID_given_software_sections[@]}" -ne 1 ]] \ - || [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then + || [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (section storing).' fi if [[ ${HYBRID_software_executable[${section}]} != "${executable}" ]]; then diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash index 21a9830..c109991 100644 --- a/tests/unit_tests_formatting.bash +++ b/tests/unit_tests_formatting.bash @@ -14,7 +14,7 @@ function Unit_Test__codebase-formatting() formatter_found='TRUE' else Print_Error 'Command ' --emph 'beautysh' \ - ' not available, unable to fully check codebase formatting.' + ' not available, unable to fully check codebase formatting.' fi local -r max_length=120 local list_of_source_files files_with_too_long_lines files_with_wrong_formatting file @@ -35,7 +35,7 @@ function Unit_Test__codebase-formatting() # Quoting shfmt manual: "If a given path is a directory, all shell # scripts found under that directory will be used." files_with_wrong_formatting=( - $(shfmt -l -ln bash -i 4 -bn -ci -sr -kp -fn "${HYBRIDT_repository_top_level_path}") + $(shfmt -l -ln bash -i 4 -bn -ci -sr -fn "${HYBRIDT_repository_top_level_path}") ) fi if [[ ${#files_with_too_long_lines[@]} -gt 0 ]]; then @@ -60,7 +60,7 @@ function Unit_Test__codebase-formatting() done Print_Info \ '\nTo format all bash files correctly run:\n' \ - --emph "shfmt -w -ln bash -i 4 -bn -ci -sr -kp -fn \"${HYBRIDT_repository_top_level_path}\"" + --emph "shfmt -w -ln bash -i 4 -bn -ci -sr -fn \"${HYBRIDT_repository_top_level_path}\"" fi if ((${#files_with_too_long_lines[@]} + ${#files_with_wrong_formatting[@]} > 0)); then return 1 diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 45bf0bf..ab680e1 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -80,7 +80,7 @@ function Unit_Test__system-requirements() printf '\n' ( unset -v 'TERM' - Call_Codebase_Function_In_Subshell Check_System_Requirements_And_Make_Report + Call_Codebase_Function_In_Subshell Check_System_Requirements_And_Make_Report ) if [[ $? -ne 0 ]]; then Print_Error "Check system requirements making report of bad system failed." diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index e4a4102..f98efe2 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -142,7 +142,7 @@ function Unit_Test__utility-remove-comments-in-existing-file() printf $'Some\n #comment\ntext\n#comment\namong\n#comment\ncomments\n' > "${file_containing_three_commented_lines}" number_of_lines=$(wc -l < "${file_containing_three_commented_lines}") Call_Codebase_Function_In_Subshell Remove_Comments_In_File "${file_containing_three_commented_lines}" - if (($( wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3)); then + if (($(wc -l < "${file_containing_three_commented_lines}") != number_of_lines - 3)); then Print_Error 'Removing comments in ' --emph "${file_containing_three_commented_lines}" ' file failed.' return 1 fi diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash index efc0be4..a81ec87 100644 --- a/tests/unit_tests_version.bash +++ b/tests/unit_tests_version.bash @@ -18,11 +18,11 @@ function Unit_Test__version() local std_output expected_output # Unsetting PATH in the subshell so that 'git' will not be found std_output=$( - PATH='' - Call_Codebase_Function Print_Software_Version + PATH='' + Call_Codebase_Function Print_Software_Version ) if [[ $? -ne 0 ]] \ - || [[ $(Strip_ANSI_Color_Codes_From_String "${std_output}") != "This is ${HYBRID_codebase_version}" ]]; then + || [[ $(Strip_ANSI_Color_Codes_From_String "${std_output}") != "This is ${HYBRID_codebase_version}" ]]; then Print_Error "Version printing without git available failed." return 1 fi diff --git a/tests/utility_functions.bash b/tests/utility_functions.bash index 9310157..8e6857c 100644 --- a/tests/utility_functions.bash +++ b/tests/utility_functions.bash @@ -43,6 +43,6 @@ function Define_Available_Tests_For() HYBRIDT_tests_to_be_run=( # Here word splitting can split names, no space allowed in function name! $(grep -hE "${grep_regex}" "${files_to_be_sourced[@]}" \ - | sed -E 's/'"${sed_regex}"'/\1/') + | sed -E 's/'"${sed_regex}"'/\1/') ) } From 2d3bdbb446f2c8539fad8cb570161bf12996bcbd Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 30 Nov 2023 09:59:46 +0100 Subject: [PATCH 266/549] Fix too long lines present in the codebase --- bash/Hydro_functionality.bash | 9 ++++++--- bash/configuration_parser.bash | 3 ++- tests/unit_tests_Hydro_functionality.bash | 5 ++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index acdc327..9bbdba3 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -52,7 +52,8 @@ function Prepare_Software_Input_File_Hydro() ln -s "${eos_folder}" "${link_to_eos_folder}" else exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'A ' --emph 'eos' ' folder called already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}" \ + 'A ' --emph 'eos' ' folder called already exists at ' \ + --emph "${HYBRID_software_output_directory[Hydro]}" \ '.' 'Please remove it and run the hybrid handler again.' fi fi @@ -92,8 +93,10 @@ function Run_Software_Hydro() hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" - "${HYBRID_software_executable[Hydro]}" "-params" "${hydro_config_file_path}" \ - "-ISinput" "${ic_output_file_path}" "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" + "${HYBRID_software_executable[Hydro]}" \ + "-params" "${hydro_config_file_path}" \ + "-ISinput" "${ic_output_file_path}" \ + "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" } Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index b6d75d0..0038379 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -60,7 +60,8 @@ function __static__Abort_If_Sections_Are_Violating_Any_Requirement() exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'No software section was specified in the handler configuration file.' elif [[ ${#software_sections_indices[@]} -gt 1 ]]; then - if [[ $(sort -u <(printf '%d\n' "${software_sections_indices[@]}") | wc -l) -ne ${#software_sections_indices[@]} ]]; then + local -r number_of_distinct_sections=$(sort -u <(printf '%d\n' "${software_sections_indices[@]}") | wc -l) + if [[ ${number_of_distinct_sections} -ne ${#software_sections_indices[@]} ]]; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ 'The same software section in the handler configuration file cannot be repeated.' fi diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 2a19309..6427040 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -164,7 +164,10 @@ function Unit_Test__Hydro-test-run-software() return 1 fi terminal_output_result=$(< "${hydro_terminal_output}") - correct_result="-params ${Hydro_config_file_path} -ISinput ${IC_output_file_path} -outputDir ${HYBRID_software_output_directory[Hydro]}" + printf -v correct_result '%s' \ + "-params ${Hydro_config_file_path} " \ + "-ISinput ${IC_output_file_path} " \ + "-outputDir ${HYBRID_software_output_directory[Hydro]}" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then Print_Error 'The terminal output has not the expected content.' return 1 From 34cee0c4106432766040401f6f213afe29d7fc5e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 23 Nov 2023 09:06:46 +0100 Subject: [PATCH 267/549] Add statement about codebase formatting --- CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d150a2d..36619cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,6 +63,14 @@ Finally, a short remark about `extglob` option. To motivate why we decided to en ## Bash notation in the codebase The general advice is pretty trivial: **Be consistent with what you find**. + +The codebase is formatted using [`shfmt`](https://github.com/mvdan/sh#shfmt). +Any developer should be aware that, because of the nature of the Bash scripting language, it is probably impossible to have a perfect formatter, which ensure rules in all details (as e.g. `clang-format` does for C++). +Therefore, it is crucial that everybody tries to be consistent with existing style and, more importantly, takes some minutes to read the following lists. +In particular, be aware the the formatter will not enforce many of the rules explained below. +Before opening a PR, make sure all tests pass. +One of them will try to check formatting and complain if something has to be adjusted. + Here a list of some aspects worth mentioning about the codebase: * indentation is done _exclusively with spaces_ and **no** Tab should be used; * lines of code are split around 100 characters and should never be longer than 120; From 3dc51cd611ddb23c70a534a04b3799e7f97d24c2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 23 Nov 2023 12:55:09 +0100 Subject: [PATCH 268/549] Minor improvement to formatting unit test --- tests/unit_tests_formatting.bash | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash index c109991..b58864c 100644 --- a/tests/unit_tests_formatting.bash +++ b/tests/unit_tests_formatting.bash @@ -32,12 +32,14 @@ function Unit_Test__codebase-formatting() if [[ ${formatter_found} = 'FALSE' ]]; then continue else - # Quoting shfmt manual: "If a given path is a directory, all shell - # scripts found under that directory will be used." + # Quoting shfmt manual: + # "If a given path is a directory, all shell scripts found under that directory will be used." files_with_wrong_formatting=( $(shfmt -l -ln bash -i 4 -bn -ci -sr -fn "${HYBRIDT_repository_top_level_path}") ) fi + # Now some nice report to user + local report_before='FALSE' if [[ ${#files_with_too_long_lines[@]} -gt 0 ]]; then Print_Error \ 'There are ' --emph "${#files_with_too_long_lines[@]}" ' file(s) with lines longer than ' \ @@ -47,9 +49,10 @@ function Unit_Test__codebase-formatting() --emph "$(realpath --relative-base="${HYBRIDT_repository_top_level_path}" "${file}")" done Print_Info '\nPlease adjust too long lines in the above mentioned files.' + report_before='TRUE' fi if [[ ${#files_with_wrong_formatting[@]} -gt 0 ]]; then - if [[ ${#files_with_too_long_lines[@]} -gt 0 ]]; then + if [[ ${report_before} = 'TRUE' ]]; then printf '\n' fi Print_Error \ From 6f8d04e052d5d552531a362a28e593e5aa788659 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 23 Nov 2023 15:15:20 +0100 Subject: [PATCH 269/549] Add execution mode to main script to format the codebase --- Hybrid-handler | 5 ++++- bash/command_line_parsers/main_parser.bash | 3 +++ bash/formatter.bash | 22 ++++++++++++++++++++++ bash/source_codebase_files.bash | 1 + tests/unit_tests_formatting.bash | 4 +--- 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 bash/formatter.bash diff --git a/Hybrid-handler b/Hybrid-handler index 078e78d..3206c48 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -94,10 +94,13 @@ function Act_And_Exit_If_User_Ran_Auxiliary_Modes() *help) Give_Required_Help ;;& + format) + Format_Codebase + ;;& version) Print_Software_Version ;;& - *help | version) + *help | format | version) exit ${HYBRID_success_exit_code} ;; esac diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index b20cb80..49b7cee 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -21,6 +21,9 @@ function Parse_Execution_Mode() version | --version) HYBRID_execution_mode='version' ;; + format | --format) + HYBRID_execution_mode='format' + ;; do) HYBRID_execution_mode='do' ;; diff --git a/bash/formatter.bash b/bash/formatter.bash new file mode 100644 index 0000000..5eb3350 --- /dev/null +++ b/bash/formatter.bash @@ -0,0 +1,22 @@ +#=================================================== +# +# Copyright (c) 2023 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Format_Codebase() +{ + if ! hash shfmt &> /dev/null; then + Print_Fatal_And_Exit \ + 'Command ' --emph 'shfmt' ' not available, unable to format codebase.' \ + 'Please, install it (https://github.com/mvdan/sh#shfmt) and run the formatting again.' + else + Ensure_That_Given_Variables_Are_Set_And_Not_Empty HYBRID_top_level_path + shfmt -w -ln bash -i 4 -bn -ci -sr -fn "${HYBRID_top_level_path}" + fi +} + +Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index bc35a91..37b6b0a 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -25,6 +25,7 @@ function __static__Source_Codebase_Files() 'command_line_parsers/sub_parser.bash' 'configuration_parser.bash' 'dispatch_functions.bash' + 'formatter.bash' 'global_variables.bash' 'Hydro_functionality.bash' 'IC_functionality.bash' diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash index b58864c..1ff9920 100644 --- a/tests/unit_tests_formatting.bash +++ b/tests/unit_tests_formatting.bash @@ -61,9 +61,7 @@ function Unit_Test__codebase-formatting() Print_Error -l -- ' - ' \ --emph "$(realpath --relative-base="${HYBRIDT_repository_top_level_path}" "${file}")" done - Print_Info \ - '\nTo format all bash files correctly run:\n' \ - --emph "shfmt -w -ln bash -i 4 -bn -ci -sr -fn \"${HYBRIDT_repository_top_level_path}\"" + Print_Info '\nRun ' --emph 'Hybrid-handler format' ' to correctly format the codebase.' fi if ((${#files_with_too_long_lines[@]} + ${#files_with_wrong_formatting[@]} > 0)); then return 1 From 510f33142048832374565920907b212dab660239 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 23 Nov 2023 15:27:03 +0100 Subject: [PATCH 270/549] Rephrase some text about bash notation --- CONTRIBUTING.md | 52 ++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36619cb..3d7aa06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,37 +66,41 @@ The general advice is pretty trivial: **Be consistent with what you find**. The codebase is formatted using [`shfmt`](https://github.com/mvdan/sh#shfmt). Any developer should be aware that, because of the nature of the Bash scripting language, it is probably impossible to have a perfect formatter, which ensure rules in all details (as e.g. `clang-format` does for C++). -Therefore, it is crucial that everybody tries to be consistent with existing style and, more importantly, takes some minutes to read the following lists. +Therefore, it is crucial for the developer to stay consistent with the existing style and, more importantly, to take some minutes to read the following lists. In particular, be aware the the formatter will not enforce many of the rules explained below. Before opening a PR, make sure all tests pass. One of them will try to check formatting and complain if something has to be adjusted. +The main script has a `format` execution mode which formats the full codebase. +This is meant for developers only and therefore does not appear in the helper description. -Here a list of some aspects worth mentioning about the codebase: -* indentation is done _exclusively with spaces_ and **no** Tab should be used; -* lines of code are split around 100 characters and should never be longer than 120; -* bash functions use both the `function` keyword and parenthesis and the enclosing braces are put on separate lines, +### Some aspects about the codebase + +* Indentation is done _exclusively with spaces_ and **no** Tab should be used. +* Lines of code are split around 100 characters and should never be longer than 120 (hard limit). +* Bash functions use both the `function` keyword and parenthesis and the enclosing braces are put on separate lines. ```bash function Example_Function() { # Body of the function } ``` -* loops and conditional clauses are started on a single line, i.e. the `do` and `then` keywords are **NOT** put on a separate line; -* local variables are typed with all small letters and words separated by underscores, e.g. `local_variable_name`; -* global variables in the codebase are prefixed by `HYBRID_` and this is meant for better readability, e.g. `HYBRID_global_variable`; -* analogously, global variables in tests are prefixed by `HYBRIDT_`; -* variables are always expanded using braces, i.e. you should use `${variable}` instead of `$variable`; -* function names are made of underscore-separated words with initials capitalized, e.g. `Function_Name_With_Words`; -* functions that are and should be used only in the file where declared are prefixed by `__static__`; -* quotes are correctly used, i.e. everything that _might_ break if unquoted is quoted; -* single quotes are used if there is no need of using double or different quotes; -* all functions declared in each separate file are marked in the end of the file as `readonly`; -* files are sourced all together by sourcing a single dedicated file (cf. *bash/source_codebase_files.bash* file). - -Here, instead, a list of aspects specific to tests that should be kept in mind: -* unit tests must be put in files whose names begin with `unit_tests_` and have the `.bash` extension (this convention allows the runner to source them all automatically) in the ***tests*** folder; -* unit tests are automatically recognized by the tests runner as functions having the `Unit_Test__` prefix (and the remaining part of the function will be the unit test name); -* operations to be done before or after a unit test can be put in the `Make_Test_Preliminary_Operations__[test-name]` and `Clean_Tests_Environment_For_Following_Test__[test-name]` functions, respectively (here `[test-name]` must match the string used in the unit test function name); -* codebase functions to be invoked in unit tests should be called through the `Call_Codebase_Function` and `Call_Codebase_Function_In_Subshell` interface functions (passing the name of the function to be invoked as first argument and the arguments to be forward afterwards); -* functional tests work analogously to unit tests, with the only differences that they have to be put in files with a `functional_tests_` prefix and implemented in bash functions starting by `Functional_Test__`; -* in functional tests you'll probably want to run the hybrid handler with some options and this can be easily achieved by using the `Run_Hybrid_Handler_With_Given_Options_In_Subshell` function. +* Loops and conditional clauses are started on a single line, i.e. the `do` and `then` keywords are **NOT** put on a separate line. +* Local variables are typed with all small letters and words separated by underscores, e.g. `local_variable_name`. +* Global variables in the codebase are prefixed by `HYBRID_` and this is meant for better readability, e.g. `HYBRID_global_variable`. +* Analogously, global variables in tests are prefixed by `HYBRIDT_`. +* Variables are always expanded using braces, i.e. you should use `${variable}` instead of `$variable`. +* Function names are made of underscore-separated words with initials capitalized, e.g. `Function_Name_With_Words`. +* Functions that are and should be used only in the file where declared are prefixed by `__static__`. +* Quotes are correctly used, i.e. everything that _might_ break if unquoted is quoted. +* Single quotes are used if there is no need of using double or different quotes. +* All functions declared in each separate file are marked in the end of the file as `readonly`. +* Files are sourced all together by sourcing a single dedicated file (cf. *bash/source_codebase_files.bash* file). + +### Some aspects specific to tests + +* Unit tests must be put in files whose names begin with `unit_tests_` and have the `.bash` extension (this convention allows the runner to source them all automatically) in the ***tests*** folder. +* Unit tests are automatically recognized by the tests runner as functions having the `Unit_Test__` prefix (and the remaining part of the function will be the unit test name). **No space must occur before the `function` keyword.** +* Operations to be done before or after a unit test can be put in the `Make_Test_Preliminary_Operations__[test-name]` and `Clean_Tests_Environment_For_Following_Test__[test-name]` functions, respectively (here `[test-name]` must match the string used in the unit test function name). +* Codebase functions to be invoked in unit tests should be called through the `Call_Codebase_Function` and `Call_Codebase_Function_In_Subshell` interface functions (passing the name of the function to be invoked as first argument and the arguments to be forward afterwards). +* Functional tests work analogously to unit tests, with the only differences that they have to be put in files with a `functional_tests_` prefix and implemented in bash functions starting by `Functional_Test__`. +* In functional tests you'll probably want to run the hybrid handler with some options and this can be easily achieved by using the `Run_Hybrid_Handler_With_Given_Options_In_Subshell` function. From 9f98972613a4f61240020833b332b2bd0b069fb2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 30 Nov 2023 10:11:32 +0100 Subject: [PATCH 271/549] Add formatter installation to GitHub actions --- .github/workflows/github-actions-on-github-servers.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index ffc851c..3fd9805 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -47,6 +47,9 @@ jobs: # install needed non-standard packages - name: Install Python dependencies run: python -m pip install --upgrade pip numpy pyyaml + # install the formatter used in the codebase + - name: Install bash formatter (shfmt) + run: sudo apt-get install -y shfmt # now everything should be ready to setup bash and run codebase tests - name: code_check # we set some environment variables which depend on the specific matrix element From 60fc72ff45c5ff2bf28df65b9c5318fa59ca6baa Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 30 Nov 2023 10:54:35 +0100 Subject: [PATCH 272/549] Install shfmt using go and picking latest version I moved the installation of the custom bash-4.4 to the home as it was done in the same folder of the codebase and this was making the formatter also format bash files there and, hence, make the formatting test fail. I actually think it is much better not to work in the codebase folder to install anything. --- .../github-actions-on-github-servers.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index 3fd9805..2f3fa6d 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -34,7 +34,10 @@ jobs: bash_version: "bash-4.4" steps: # this is an action provided by GitHub to checkout the repository - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 + with: + cache: false # taken from https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - name: Set up Python 3.x uses: actions/setup-python@v4 @@ -47,9 +50,6 @@ jobs: # install needed non-standard packages - name: Install Python dependencies run: python -m pip install --upgrade pip numpy pyyaml - # install the formatter used in the codebase - - name: Install bash formatter (shfmt) - run: sudo apt-get install -y shfmt # now everything should be ready to setup bash and run codebase tests - name: code_check # we set some environment variables which depend on the specific matrix element @@ -60,10 +60,15 @@ jobs: # we recall that in YAML the pipe symbol "|" means that the following lines, including newlines, are interpreted literally run: | # we save in a variable the path of the starting directory - export main_dir=${PWD} + export main_dir="${PWD}" + # we install the formatter + export PATH="${PATH}:$(go env GOPATH)/bin" + go install mvdan.cc/sh/v3/cmd/shfmt@latest + printf "\n$(pwd)\nInstalled shfmt at $(which shfmt): $(shfmt --version)\n\n" # if we are not using the default bash version, we download and compile the desired one if [ ${BASH_TESTED_VERSION} != "default" ]; then - printf "Download and compile bash-${BASH_TESTED_VERSION}...\n" + cd ${HOME} + printf "Download and compile bash-${BASH_TESTED_VERSION} into ${HOME}...\n" wget -nv https://ftp.gnu.org/gnu/bash/${BASH_TESTED_VERSION}.tar.gz tar xf ${BASH_TESTED_VERSION}.tar.gz output_file=configure_make.log @@ -83,7 +88,7 @@ jobs: # we set the TERM environment variable (used by utilities exploited in the tests) export TERM="xterm" # now we enter into the tests directory and execute the tests with maximum detail (-r 3) - cd $main_dir/tests + cd ${main_dir}/tests ${BASH_EXE} tests_runner unit -r 3 -k ||\ { printf "\nDetailed output of unit tests for debugging purposes:\n"; cat run_tests/tests_runner.log; exit 1; } rm -rf run_tests From f8ecfaa79dd02c22275526966895c8b8b9a6ea19 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 1 Dec 2023 13:43:50 +0100 Subject: [PATCH 273/549] Be more explicit about what the formatter enforce --- CONTRIBUTING.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d7aa06..7b42a5f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,7 +67,8 @@ The general advice is pretty trivial: **Be consistent with what you find**. The codebase is formatted using [`shfmt`](https://github.com/mvdan/sh#shfmt). Any developer should be aware that, because of the nature of the Bash scripting language, it is probably impossible to have a perfect formatter, which ensure rules in all details (as e.g. `clang-format` does for C++). Therefore, it is crucial for the developer to stay consistent with the existing style and, more importantly, to take some minutes to read the following lists. -In particular, be aware the the formatter will not enforce many of the rules explained below. +In particular, be aware the the formatter will not enforce the rules explained below. + Before opening a PR, make sure all tests pass. One of them will try to check formatting and complain if something has to be adjusted. The main script has a `format` execution mode which formats the full codebase. @@ -75,16 +76,14 @@ This is meant for developers only and therefore does not appear in the helper de ### Some aspects about the codebase -* Indentation is done _exclusively with spaces_ and **no** Tab should be used. -* Lines of code are split around 100 characters and should never be longer than 120 (hard limit). -* Bash functions use both the `function` keyword and parenthesis and the enclosing braces are put on separate lines. +* Lines of code are split around 100 characters and should never be longer than 120 (hard limit, tests will fail if longer lines exist). +* Bash functions use **both** the `function` keyword **and** parenthesis (with the enclosing braces are put on separate lines). ```bash function Example_Function() { # Body of the function } ``` -* Loops and conditional clauses are started on a single line, i.e. the `do` and `then` keywords are **NOT** put on a separate line. * Local variables are typed with all small letters and words separated by underscores, e.g. `local_variable_name`. * Global variables in the codebase are prefixed by `HYBRID_` and this is meant for better readability, e.g. `HYBRID_global_variable`. * Analogously, global variables in tests are prefixed by `HYBRIDT_`. @@ -96,6 +95,11 @@ This is meant for developers only and therefore does not appear in the helper de * All functions declared in each separate file are marked in the end of the file as `readonly`. * Files are sourced all together by sourcing a single dedicated file (cf. *bash/source_codebase_files.bash* file). +#### Other conventions in use that the formatter enforces + +* Indentation is done _exclusively with spaces_ and **no** Tab should be used. +* Loops and conditional clauses are started on a single line, i.e. the `do` and `then` keywords are **NOT** put on a separate line. + ### Some aspects specific to tests * Unit tests must be put in files whose names begin with `unit_tests_` and have the `.bash` extension (this convention allows the runner to source them all automatically) in the ***tests*** folder. From 5a8d3765e0d721e2c6f2ab52bd2ab47e94d4e2f4 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 6 Dec 2023 16:21:54 +0100 Subject: [PATCH 274/549] Add additional Run_ID folder to the output structure of the Hybrid handler. --- bash/global_variables.bash | 5 ++++- bash/sanity_checks.bash | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 132374d..6a81e5b 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -46,7 +46,9 @@ function Define_Further_Global_Variables() ) # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. - declare -rgA HYBRID_hybrid_handler_valid_keys=() + declare -rgA HYBRID_hybrid_handler_valid_keys=( + [Run_ID]='HYBRID_run_id' + ) declare -rgA HYBRID_ic_valid_keys=( [Executable]='HYBRID_software_executable[IC]' [Config_file]='HYBRID_software_base_config_file[IC]' @@ -76,6 +78,7 @@ function Define_Further_Global_Variables() HYBRID_configuration_file='./config.yaml' HYBRID_output_directory="$(realpath .)" # Variables to be set (and possibly made readonly) from configuration/setup + HYBRID_run_id="Run_$(date +'%Y-%m-%d_%H%M%S')" HYBRID_given_software_sections=() declare -gA HYBRID_software_executable=( [IC]='' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 29cc212..b56281f 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -13,7 +13,7 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var for key in "${HYBRID_valid_software_configuration_sections[@]}"; do # The software output directories are always ALL set, even if not all software is run. This # is important as some software might rely on files in directories of other workflow blocks. - HYBRID_software_output_directory[${key}]="${HYBRID_output_directory}/${key}" + HYBRID_software_output_directory[${key}]="${HYBRID_output_directory}/${key}/${HYBRID_run_id}" if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then __static__Ensure_Executable_Exists "${key}" printf -v HYBRID_software_configuration_file[${key}] \ From 3eb87c8605418ec4c375b5a118fed86480c99932 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Wed, 6 Dec 2023 16:28:35 +0100 Subject: [PATCH 275/549] Start fixing the unit tests --- tests/unit_tests_IC_functionality.bash | 2 +- tests/unit_tests_Sampler_functionality.bash | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 95d2c0a..2085b99 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -92,7 +92,7 @@ function Make_Test_Preliminary_Operations__IC-test-run-software() function Unit_Test__IC-test-run-software() { - local -r ic_terminal_output="${HYBRID_output_directory}/IC/Terminal_Output.txt" + local -r ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" mkdir -p "${HYBRID_software_output_directory[IC]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_IC diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 11a90ac..539842d 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -29,10 +29,10 @@ function Make_Test_Preliminary_Operations__Sampler-create-input-file() function Unit_Test__Sampler-create-input-file() { HYBRID_software_base_config_file[Sampler]='fake_sampler_config' - printf '%s\n' \ - 'surface ../Hydro/freezeout.dat' \ - 'spectra_dir .' \ - > "${HYBRID_software_base_config_file[Sampler]}" + printf ' + surface %s/freezeout.dat + spectra_dir . + ' "${HYBRID_software_output_directory[Hydro]}" > "${HYBRID_software_base_config_file[Sampler]}" mkdir -p "${HYBRID_software_output_directory[Hydro]}" touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler From 5eaf5268418f937e8585844d1b04565933908820 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Fri, 8 Dec 2023 12:17:27 +0100 Subject: [PATCH 276/549] Fix unit tests, change default path in sampler base config. --- bash/Sampler_functionality.bash | 29 ++++++++++++++++----- configs/hadron_sampler | 4 +-- tests/unit_tests_Sampler_functionality.bash | 14 +++++----- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 691b419..26e2df7 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -84,13 +84,24 @@ function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() # We assume here that the configuration file is fine as it was validated before value=$(awk -v name="${field}" '$1 == name {print $2; exit}' \ "${HYBRID_software_configuration_file[Sampler]}") - cd "${HYBRID_software_output_directory[Sampler]}" - # If realpath succeeds, it prints the path that is the result of the function - if ! realpath "${value}" 2> /dev/null; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Unable to transform relative path ' --emph "${value}" ' into global one.' + if [[ "${value}" = '=DEFAULT=' ]]; then + case "${field}" in + surface) + printf "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + ;; + spectra_dir) + printf "${HYBRID_software_output_directory[Sampler]}" + ;; + esac + else + cd "${HYBRID_software_output_directory[Sampler]}" + # If realpath succeeds, it prints the path that is the result of the function + if ! realpath "${value}" 2> /dev/null; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ + 'Unable to transform relative path ' --emph "${value}" ' into global one.' + fi + cd - > /dev/null fi - cd - > /dev/null } function __static__Is_Sampler_Config_Valid() @@ -134,6 +145,12 @@ function __static__Is_Sampler_Config_Valid() return 1 fi case "${key}" in + surface | spectra_dir) + if [[ "${value}" = '=DEFAULT=' ]]; then + ((keys_to_be_found--)) + continue + fi + ;;& surface) cd "${HYBRID_software_output_directory[Sampler]}" if [[ ! -f "${value}" ]]; then diff --git a/configs/hadron_sampler b/configs/hadron_sampler index 24bb38e..2d97e94 100644 --- a/configs/hadron_sampler +++ b/configs/hadron_sampler @@ -1,5 +1,5 @@ -surface ../Hydro/freezeout.dat -spectra_dir . +surface =DEFAULT= +spectra_dir =DEFAULT= number_of_events 100 weakContribution 0 shear 1 diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 539842d..8eda691 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -28,11 +28,11 @@ function Make_Test_Preliminary_Operations__Sampler-create-input-file() function Unit_Test__Sampler-create-input-file() { - HYBRID_software_base_config_file[Sampler]='fake_sampler_config' - printf ' - surface %s/freezeout.dat - spectra_dir . - ' "${HYBRID_software_output_directory[Hydro]}" > "${HYBRID_software_base_config_file[Sampler]}" + # HYBRID_software_base_config_file[Sampler]='fake_sampler_config' + # printf ' + # surface %s/freezeout.dat + # spectra_dir . + # ' "${HYBRID_software_output_directory[Hydro]}" > "${HYBRID_software_base_config_file[Sampler]}" mkdir -p "${HYBRID_software_output_directory[Hydro]}" touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler @@ -63,7 +63,7 @@ function Unit_Test__Sampler-create-input-file() function Clean_Tests_Environment_For_Following_Test__Sampler-create-input-file() { - rm "${HYBRID_software_base_config_file[Sampler]}" + # rm "${HYBRID_software_base_config_file[Sampler]}" rm -r "${HYBRID_output_directory}" } @@ -198,6 +198,8 @@ function Unit_Test__Sampler-validate-config-file() cp "${HYBRID_software_base_config_file[Sampler]}" "${HYBRID_software_configuration_file[Sampler]}" mkdir -p "${HYBRID_software_output_directory[Hydro]}" touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + pwd + ls Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid if [[ $? -ne 0 ]]; then Print_Error 'Shipped sampler configuration unexpectedly detected as incorrect.' From e7f6c34fc1fcecad0de94b489438632451776a4b Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 12 Dec 2023 14:51:02 +0100 Subject: [PATCH 277/549] Fix functional afterburner test --- tests/functional_tests_Afterburner_only.bash | 40 ++++++++++++-------- tests/utility_functions_functional.bash | 5 ++- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 422bfac..c389fd4 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -21,17 +21,21 @@ function Functional_Test__do-Afterburner-only() { shopt -s nullglob local -r config_filename='Handler_config.yaml' + local -r run_id='Handler_run_id' local unfinished_files output_files terminal_output_file failure_message - mkdir 'Sampler' - touch 'Sampler/particle_lists.oscar' + + mkdir -p "Sampler/${run_id}" + touch "Sampler/${run_id}/particle_lists.oscar" printf ' + Hybrid_handler: + Run_ID: %s Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py Software_keys: Modi: List: File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -39,7 +43,7 @@ function Functional_Test__do-Afterburner-only() mv 'Afterburner' 'Afterburner-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' - terminal_output_file='Afterburner/Terminal_Output.txt' + terminal_output_file="Afterburner/${run_id}/Terminal_Output.txt" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then @@ -63,14 +67,14 @@ function Functional_Test__do-Afterburner-only() Print_Error 'Hybrid-handler unexpectedly succeeded with Afterburner software crashing.' return 1 fi - unfinished_files=(Afterburner/*.{unfinished,lock}) + unfinished_files=(Afterburner/*/*.{unfinished,lock}) if [[ ${#unfinished_files[@]} -ne 3 ]]; then Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." return 1 fi mv 'Afterburner' 'Afterburner-software-crash' #Test with custom input - rm 'Sampler/particle_lists.oscar' + rm "Sampler/${run_id}/particle_lists.oscar" mkdir -p test touch 'test/particle_lists_2.oscar' printf ' @@ -89,6 +93,8 @@ function Functional_Test__do-Afterburner-only() mv 'Afterburner' 'Afterburner-success-custom-input' # Expect failure when using custom input while also running the sampler printf ' + Hybrid_handler: + Run_ID: %s Sampler: Executable: echo Afterburner: @@ -98,18 +104,20 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "${run_id}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi # Expect success and test the add_spectator functionality Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' - mkdir 'IC' - touch 'IC/config.yaml' 'IC/SMASH_IC.oscar' 'Sampler/particle_lists.oscar' + mkdir -p "IC/${run_id}" + touch "IC/${run_id}/config.yaml" "IC/${run_id}/SMASH_IC.oscar" "Sampler/${run_id}/particle_lists.oscar" printf ' + Hybrid_handler: + Run_ID: %s Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py Add_spectators_from_IC: TRUE @@ -117,16 +125,18 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? mv 'Afterburner' 'Afterburner-success-with-spectators' # Expect success and test the add_spectator functionality with custom spectator input Print_Info 'Running Hybrid-handler expecting success with the custom add_spectator option' rm -r "IC"/* - mkdir -p test - touch 'test/SMASH_IC_2.oscar' 'IC/config.yaml' + mkdir -p test "IC/${run_id}" + touch 'test/SMASH_IC_2.oscar' "IC/${run_id}/config.yaml" printf ' + Hybrid_handler: + Run_ID: %s Afterburner: Executable: %s/mocks/smash_afterburner_black-box.py Add_spectators_from_IC: TRUE @@ -135,7 +145,7 @@ function Functional_Test__do-Afterburner-only() Modi: List: File_Directory: "." - ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" + ' "${run_id}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' @@ -157,7 +167,7 @@ function Functional_Test__do-Afterburner-only() List: File_Directory: "." ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 diff --git a/tests/utility_functions_functional.bash b/tests/utility_functions_functional.bash index 1f1ade2..e91649b 100644 --- a/tests/utility_functions_functional.bash +++ b/tests/utility_functions_functional.bash @@ -25,8 +25,9 @@ function Check_If_Software_Produced_Expected_Output() Print_Internal_And_Exit 'Invalid case branch entered in ' --emph "${FUNCNAME}." ;; esac - unfinished_files=("${folder}"/*.{unfinished,lock}) - output_files=("${folder}"/*) + # It is expected that in the case of the functional tests, only one folder appears after each block + unfinished_files=("${folder}"/*/*.{unfinished,lock}) + output_files=("${folder}"/*/*) if [[ ${#unfinished_files[@]} -gt 0 ]]; then exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit \ 'Some unexpected ' --emph '.{unfinished,lock}' ' output file remained' \ From 99c095e0f5c5a315ee54eb5f0063baad28ce30e6 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 12 Dec 2023 14:58:55 +0100 Subject: [PATCH 278/549] Fix functional Hydro test --- tests/functional_tests_Hydro_only.bash | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 9fbdd33..e9c870d 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -12,16 +12,19 @@ function Functional_Test__do-Hydro-only() shopt -s nullglob local -r config_filename='vhlle_hydro_config' local output_files terminal_output_file failure_message + local -r run_id='Handler_run_id' # Make a symlink to the python mock such that the eos folder doesn't have to be created in the mock folder ln -s "${HYBRIDT_repository_top_level_path}/tests/mocks/vhlle_black-box.py" "vhlle_black-box.py" mkdir 'eos' printf ' + Hybrid_handler: + Run_ID: %s Hydro: Executable: %s/vhlle_black-box.py - ' "$(pwd)" > "${config_filename}" + ' "${run_id}" "$(pwd)" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated - mkdir -p 'IC' - touch 'IC/SMASH_IC.dat' + mkdir -p "IC/${run_id}" + touch "IC/${run_id}/SMASH_IC.dat" Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 0 ]]; then @@ -32,7 +35,7 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-success' # Expect failure when giving an invalid IC output Print_Info 'Running Hybrid-handler expecting invalid IC argument' - terminal_output_file='Hydro/Terminal_Output.txt' + terminal_output_file="Hydro/${run_id}/Terminal_Output.txt" BLACK_BOX_FAIL='invalid_input' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then @@ -50,12 +53,14 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-invalid-input' #Expect success with custom input file name printf ' + Hybrid_handler: + Run_ID: %s Hydro: Executable: %s/vhlle_black-box.py Input_file: %s/test/input - ' "$(pwd)" "$(pwd)" > "${config_filename}" + ' "${run_id}" "$(pwd)" "$(pwd)" > "${config_filename}" # Run the hydro stage and check if freezeout is successfully generated - rm 'IC/SMASH_IC.dat' + rm "IC/${run_id}/SMASH_IC.dat" mkdir -p test touch 'test/input' Print_Info 'Running Hybrid-handler expecting success' @@ -68,7 +73,7 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-success-custom-input' # Expect failure when an invalid config was supplied Print_Info 'Running Hybrid-handler expecting invalid config argument' - terminal_output_file='Hydro/Terminal_Output.txt' + terminal_output_file="Hydro/${run_id}/Terminal_Output.txt" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then @@ -100,12 +105,14 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-crash' #Expect failure with custom input file name while also using IC printf ' + Hybrid_handler: + Run_ID: %s IC: Executable: echo Hydro: Executable: %s/vhlle_black-box.py Input_file: %s/test/input - ' "$(pwd)" "$(pwd)" > "${config_filename}" + ' "${run_id}" "$(pwd)" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -ne 110 ]]; then From ccb8b3f82d92da31f1e5d461da350408c4cfed44 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 12 Dec 2023 15:01:54 +0100 Subject: [PATCH 279/549] Fix functional IC test --- tests/functional_tests_Hydro_only.bash | 2 +- tests/functional_tests_IC_only.bash | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index e9c870d..5d685f8 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -11,8 +11,8 @@ function Functional_Test__do-Hydro-only() { shopt -s nullglob local -r config_filename='vhlle_hydro_config' - local output_files terminal_output_file failure_message local -r run_id='Handler_run_id' + local output_files terminal_output_file failure_message # Make a symlink to the python mock such that the eos folder doesn't have to be created in the mock folder ln -s "${HYBRIDT_repository_top_level_path}/tests/mocks/vhlle_black-box.py" "vhlle_black-box.py" mkdir 'eos' diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index 9e4aee3..66cf31d 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -11,11 +11,14 @@ function Functional_Test__do-IC-only() { shopt -s nullglob local -r config_filename='IC_config.yaml' + local -r run_id='Handler_run_id' local unfinished_files output_files terminal_output_file failure_message printf ' + Hybrid_handler: + Run_ID: %s IC: Executable: %s/tests/mocks/smash_IC_black-box.py - ' "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" @@ -27,7 +30,7 @@ function Functional_Test__do-IC-only() mv 'IC' 'IC-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid IC input file failure' - terminal_output_file='IC/Terminal_Output.txt' + terminal_output_file="IC/${run_id}/Terminal_Output.txt" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then @@ -51,7 +54,7 @@ function Functional_Test__do-IC-only() Print_Error 'Hybrid-handler unexpectedly succeeded with IC software crashing.' return 1 fi - unfinished_files=(IC/*.{unfinished,lock}) + unfinished_files=(IC/*/*.{unfinished,lock}) if [[ ${#unfinished_files[@]} -ne 3 ]]; then Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." return 1 From a5b0b76dfe08fe747b98cd279ee0aa6786acbc97 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 12 Dec 2023 15:04:39 +0100 Subject: [PATCH 280/549] Fix functional Sampler test --- tests/functional_tests_Sampler_only.bash | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 70b2b8e..dde1fe4 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -11,13 +11,16 @@ function Functional_Test__do-Sampler-only() { shopt -s nullglob local -r hybrid_handler_config='hybrid_config' + local -r run_id='Handler_run_id' local output_files - mkdir -p 'Hydro' - touch 'Hydro/freezeout.dat' + mkdir -p "Hydro/${run_id}" + touch "Hydro/${run_id}/freezeout.dat" printf ' + Hybrid_handler: + Run_ID: %s Sampler: Executable: %s/tests/mocks/sampler_black-box.py - ' "${HYBRIDT_repository_top_level_path}" > "${hybrid_handler_config}" + ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${hybrid_handler_config}" # Expect success and test presence of output files Print_Info 'Running Hybrid-handler expecting success' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" @@ -29,7 +32,7 @@ function Functional_Test__do-Sampler-only() mv 'Sampler' 'Sampler-success' # Expect failure and test terminal output local terminal_output_file error_message - terminal_output_file='Sampler/Terminal_Output.txt' + terminal_output_file="Sampler/${run_id}/Terminal_Output.txt" Print_Info 'Running Hybrid-handler expecting crash in Sampler' BLACK_BOX_FAIL='true' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" @@ -49,7 +52,7 @@ function Functional_Test__do-Sampler-only() # Expect Hybrid-handler to crash before calling the Sampler because of invalid config file Print_Info 'Running Hybrid-handler expecting invalid config error' BLACK_BOX_FAIL='false' - mkdir -p Sampler + mkdir -p "Sampler/${run_id}" local -r invalid_sampler_config="invalid_hadron_sampler" touch "${invalid_sampler_config}" printf ' From 3982014ae6ab82616008d8f0b893225cb76b2601 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Tue, 12 Dec 2023 15:44:29 +0100 Subject: [PATCH 281/549] Suppress warning in the Hydro unit test --- bash/Sampler_functionality.bash | 2 +- tests/unit_tests_Hydro_functionality.bash | 3 ++- tests/unit_tests_Sampler_functionality.bash | 7 ------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 26e2df7..5545183 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -146,7 +146,7 @@ function __static__Is_Sampler_Config_Valid() fi case "${key}" in surface | spectra_dir) - if [[ "${value}" = '=DEFAULT=' ]]; then + if [[ "${value}" = '=DEFAULT=' ]]; then ((keys_to_be_found--)) continue fi diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 6427040..e198d7d 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -70,7 +70,8 @@ function Unit_Test__Hydro-create-input-file() fi rm "${HYBRID_software_output_directory[Hydro]}"/* ln -s ~ "${HYBRID_software_output_directory[Hydro]}/eos" - Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro + # Suppress logging to file descriptor 9 to suppress the warning + Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro 9> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Preparation failed to replace existing symlink.' return 1 diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 8eda691..5479eb7 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -28,11 +28,6 @@ function Make_Test_Preliminary_Operations__Sampler-create-input-file() function Unit_Test__Sampler-create-input-file() { - # HYBRID_software_base_config_file[Sampler]='fake_sampler_config' - # printf ' - # surface %s/freezeout.dat - # spectra_dir . - # ' "${HYBRID_software_output_directory[Hydro]}" > "${HYBRID_software_base_config_file[Sampler]}" mkdir -p "${HYBRID_software_output_directory[Hydro]}" touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Sampler @@ -198,8 +193,6 @@ function Unit_Test__Sampler-validate-config-file() cp "${HYBRID_software_base_config_file[Sampler]}" "${HYBRID_software_configuration_file[Sampler]}" mkdir -p "${HYBRID_software_output_directory[Hydro]}" touch "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" - pwd - ls Call_Codebase_Function_In_Subshell __static__Is_Sampler_Config_Valid if [[ $? -ne 0 ]]; then Print_Error 'Shipped sampler configuration unexpectedly detected as incorrect.' From 52380783fd2a3315a1bafd099e1549f66319bd3b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 16 Jan 2024 10:38:45 +0100 Subject: [PATCH 282/549] Make formatting test fail if formatter was not found --- tests/unit_tests_formatting.bash | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash index 1ff9920..774129d 100644 --- a/tests/unit_tests_formatting.bash +++ b/tests/unit_tests_formatting.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -63,7 +63,8 @@ function Unit_Test__codebase-formatting() done Print_Info '\nRun ' --emph 'Hybrid-handler format' ' to correctly format the codebase.' fi - if ((${#files_with_too_long_lines[@]} + ${#files_with_wrong_formatting[@]} > 0)); then + if ((${#files_with_too_long_lines[@]} + ${#files_with_wrong_formatting[@]} > 0)) \ + || [[ ${formatter_found} = 'FALSE' ]]; then return 1 fi } From 2173f22ea57f19b538de8ab166398b188c1a27e8 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 18 Jan 2024 11:22:31 +0100 Subject: [PATCH 283/549] Add (failing) test about validation of boolean keys --- tests/unit_tests_configuration.bash | 47 ++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index f8d985b..cead138 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -150,6 +150,51 @@ function Unit_Test__configuration-validate-all-keys() #------------------------------------------------------------------------------- +function Make_Test_Preliminary_Operations__configuration-validate-boolean-values() +{ + Make_Test_Preliminary_Operations__configuration-validate-existence +} + +function Unit_Test__configuration-validate-boolean-values() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + local value counter=0 + for value in y Y yes Yes YES n N no No NO on On ON off Off OFF 0 1; do + printf ' + Afterburner: + Add_spectators_from_IC: %s + ' "${value}" > "${HYBRID_configuration_file}" + Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of configuration file with invalid boolean values succeeded.' + ((counter++)) + fi + done + if [[ ${counter} -ne 0 ]]; then + return ${counter} + else + counter=0 + fi + for value in true True TRUE TrUe false False FALSE FalSe; do + HYBRID_optional_feature[Add_spectators_from_IC]='' # Unset boolean to test that it is set + printf ' + Afterburner: + Add_spectators_from_IC: %s + ' "${value}" > "${HYBRID_configuration_file}" + Call_Codebase_Function Validate_And_Parse_Configuration_File + if [[ $? -ne 0 ]]; then + Print_Error 'Validation of configuration file with valid boolean values failed.' + ((counter++)) + elif [[ ! "${HYBRID_optional_feature[Add_spectators_from_IC]}" =~ ^(TRUE|FALSE)$ ]]; then + Print_Error 'Value of boolean variable was not stored all capitalized.' + ((counter++)) + fi + done + return ${counter} +} + +#------------------------------------------------------------------------------- + function Make_Test_Preliminary_Operations__configuration-parse-general-section() { Make_Test_Preliminary_Operations__configuration-validate-existence From 9ab399dece24ea9543d54ad6cf1e91308adb0c79 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 18 Jan 2024 11:43:06 +0100 Subject: [PATCH 284/549] Implement validation of boolean values in config parsing --- bash/configuration_parser.bash | 18 +++++++++++++++++- bash/global_variables.bash | 10 ++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 0038379..fba1b36 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -166,10 +166,26 @@ function __static__Parse_Key_And_Store_It() declare -n store_variable=$2 if Has_YAML_String_Given_Key "${yaml_section}" "${key}"; then store_variable=$(Read_From_YAML_String_Given_Key "${yaml_section}" "${key}") + if Element_In_Array_Equals_To "${key}" "${HYBRID_boolean_keys[@]}"; then + __static__Validate_Boolean_Value "${key}" "$2" + fi yaml_section="$(Print_YAML_String_Without_Given_Key "${yaml_section}" "${key}")" fi } +function __static__Validate_Boolean_Value() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty store_variable + local -r key=$1 + if [[ ! "${store_variable^^}" =~ ^(TRUE|FALSE)$ ]]; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ + 'Invalid value found at ' --emph "${key}: ${store_variable}" \ + '.' 'The value is expected to be a YAML 1.2 boolean one (e.g. ' \ + --emph 'true|false' ').' + fi + store_variable=${store_variable^^} # Ensure string is fully capitalized +} + function __static__YAML_section_must_be_empty() { local yaml_section=$1 diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 132374d..97bec78 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -45,7 +45,7 @@ function Define_Further_Global_Variables() [Afterburner]='afterburner_config.yaml' ) # The following associative arrays declare maps between valid keys in the handler config - # file and bash variables in which the input information will be stored once parsed. + # file and bash variables in which the input information will be stored once parsed declare -rgA HYBRID_hybrid_handler_valid_keys=() declare -rgA HYBRID_ic_valid_keys=( [Executable]='HYBRID_software_executable[IC]' @@ -71,6 +71,12 @@ function Define_Further_Global_Variables() [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' [Spectators_source]='HYBRID_optional_feature[Spectators_source]' ) + # This array declares a list of boolean keys. Here we do not keep track of sections + # as it would be strange to use the same key name in different sections once as + # boolean and once as something else. + declare -rg HYBRID_boolean_keys=( + 'Add_spectators_from_IC' + ) # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' From 19d4405c0f86a9b2e88984f8e74617ece744f5c3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 18 Jan 2024 11:43:44 +0100 Subject: [PATCH 285/549] Use different possible boolean values in functional tests --- tests/functional_tests_Afterburner_only.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 422bfac..46c7ec6 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -112,7 +112,7 @@ function Functional_Test__do-Afterburner-only() printf ' Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py - Add_spectators_from_IC: TRUE + Add_spectators_from_IC: true Software_keys: Modi: List: @@ -129,7 +129,7 @@ function Functional_Test__do-Afterburner-only() printf ' Afterburner: Executable: %s/mocks/smash_afterburner_black-box.py - Add_spectators_from_IC: TRUE + Add_spectators_from_IC: True Spectators_source: %s/test/SMASH_IC_2.oscar Software_keys: Modi: From 9fc156cde03d6818ae83e9a4a251ce7e8b6532a4 Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Fri, 19 Jan 2024 10:48:26 +0100 Subject: [PATCH 286/549] Minor fixes and renaming --- bash/Sampler_functionality.bash | 2 +- tests/functional_tests_Afterburner_only.bash | 9 +++++---- tests/functional_tests_Hydro_only.bash | 5 +++-- tests/functional_tests_IC_only.bash | 7 ++++--- tests/functional_tests_Sampler_only.bash | 5 +++-- tests/unit_tests_Sampler_functionality.bash | 1 - tests/utility_functions_functional.bash | 6 +++++- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 5545183..84c6f55 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -150,7 +150,7 @@ function __static__Is_Sampler_Config_Valid() ((keys_to_be_found--)) continue fi - ;;& + ;;& # Continue matching other cases below surface) cd "${HYBRID_software_output_directory[Sampler]}" if [[ ! -f "${value}" ]]; then diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index c389fd4..5957da0 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -20,8 +20,9 @@ function __static__Check_Successful_Handler_Run() function Functional_Test__do-Afterburner-only() { shopt -s nullglob - local -r config_filename='Handler_config.yaml' - local -r run_id='Handler_run_id' + local -r \ + config_filename='Handler_config.yaml' \ + run_id='Afterburner_only' local unfinished_files output_files terminal_output_file failure_message mkdir -p "Sampler/${run_id}" @@ -67,7 +68,7 @@ function Functional_Test__do-Afterburner-only() Print_Error 'Hybrid-handler unexpectedly succeeded with Afterburner software crashing.' return 1 fi - unfinished_files=(Afterburner/*/*.{unfinished,lock}) + unfinished_files=("Afterburner/${run_id}/"*.{unfinished,lock}) if [[ ${#unfinished_files[@]} -ne 3 ]]; then Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." return 1 @@ -114,7 +115,7 @@ function Functional_Test__do-Afterburner-only() # Expect success and test the add_spectator functionality Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' mkdir -p "IC/${run_id}" - touch "IC/${run_id}/config.yaml" "IC/${run_id}/SMASH_IC.oscar" "Sampler/${run_id}/particle_lists.oscar" + touch "IC/${run_id}/"{config.yaml,SMASH_IC.oscar} "Sampler/${run_id}/particle_lists.oscar" printf ' Hybrid_handler: Run_ID: %s diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 5d685f8..2fb0f2f 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -10,8 +10,9 @@ function Functional_Test__do-Hydro-only() { shopt -s nullglob - local -r config_filename='vhlle_hydro_config' - local -r run_id='Handler_run_id' + local -r \ + config_filename='vhlle_hydro_config' \ + run_id='Hydro_only' local output_files terminal_output_file failure_message # Make a symlink to the python mock such that the eos folder doesn't have to be created in the mock folder ln -s "${HYBRIDT_repository_top_level_path}/tests/mocks/vhlle_black-box.py" "vhlle_black-box.py" diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index 66cf31d..7536016 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -10,8 +10,9 @@ function Functional_Test__do-IC-only() { shopt -s nullglob - local -r config_filename='IC_config.yaml' - local -r run_id='Handler_run_id' + local -r \ + config_filename='IC_config.yaml' \ + run_id='IC_only' local unfinished_files output_files terminal_output_file failure_message printf ' Hybrid_handler: @@ -54,7 +55,7 @@ function Functional_Test__do-IC-only() Print_Error 'Hybrid-handler unexpectedly succeeded with IC software crashing.' return 1 fi - unfinished_files=(IC/*/*.{unfinished,lock}) + unfinished_files=("IC/${run_id}/"*.{unfinished,lock}) if [[ ${#unfinished_files[@]} -ne 3 ]]; then Print_Error 'Expected ' --emph '3' " unfinished/lock files, but ${#unfinished_files[@]} found." return 1 diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index dde1fe4..069d651 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -10,8 +10,9 @@ function Functional_Test__do-Sampler-only() { shopt -s nullglob - local -r hybrid_handler_config='hybrid_config' - local -r run_id='Handler_run_id' + local -r \ + hybrid_handler_config='hybrid_config' \ + run_id='Sampler_only' local output_files mkdir -p "Hydro/${run_id}" touch "Hydro/${run_id}/freezeout.dat" diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 5479eb7..774ed52 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -58,7 +58,6 @@ function Unit_Test__Sampler-create-input-file() function Clean_Tests_Environment_For_Following_Test__Sampler-create-input-file() { - # rm "${HYBRID_software_base_config_file[Sampler]}" rm -r "${HYBRID_output_directory}" } diff --git a/tests/utility_functions_functional.bash b/tests/utility_functions_functional.bash index e91649b..dd52286 100644 --- a/tests/utility_functions_functional.bash +++ b/tests/utility_functions_functional.bash @@ -25,7 +25,11 @@ function Check_If_Software_Produced_Expected_Output() Print_Internal_And_Exit 'Invalid case branch entered in ' --emph "${FUNCNAME}." ;; esac - # It is expected that in the case of the functional tests, only one folder appears after each block + local -r folder_content=("${folder}"/*) + if [[ ${#folder_content[@]} -ne 1 || ! -d ${folder_content[0]} ]]; then + exit_code=${HYBRID_failure_exit_code} Print_Fatal_And_Exit \ + 'Not exactly one ID folder found in ' --emph "${folder}" '.' + fi unfinished_files=("${folder}"/*/*.{unfinished,lock}) output_files=("${folder}"/*/*) if [[ ${#unfinished_files[@]} -gt 0 ]]; then From 31abc4ca76636b1c1842dc3d095dbc4d8b414802 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 16 Jan 2024 10:50:25 +0100 Subject: [PATCH 287/549] Reformat prerequisites in README --- README.md | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9ad12d7..a616f70 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,30 @@ # SMASH-vHLLE-Hybrid -Event-by-event hybrid model for the description of relativistic heavy-ion collisions in the low and high baryon-density regime. This model constitutes a chain of different submodules to appropriately describe each phase of the collision with its corresponding degrees of freedom. It consists of the following modules: -- SMASH hadronic transport approach to provide the initial conditions -- vHLLE 3+1D viscous hydrodynamics approach to describe the evolution of the hot and dense fireball -- CORNELIUS tool to construct a hypersurface of constant energy density from the hydrodynamical evolution (embedded in vHLLE) -- Cooper-Frye sampler to perform particlization of the elements on the freezeout hypersurface -- SMASH hadronic transport approach to perform the afterburner evolution +Event-by-event hybrid model for the description of relativistic heavy-ion collisions in the low and high baryon-density regime. +This model constitutes a chain of different submodules to appropriately describe each phase of the collision with its corresponding degrees of freedom. +It consists of the following modules: +- SMASH hadronic transport approach to provide the initial conditions; +- vHLLE 3+1D viscous hydrodynamics approach to describe the evolution of the hot and dense fireball; +- CORNELIUS tool to construct a hypersurface of constant energy density from the hydrodynamical evolution (embedded in vHLLE); +- Cooper-Frye sampler to perform particlization of the elements on the freezeout hypersurface; +- SMASH hadronic transport approach to perform the afterburner evolution. If you are using the SMASH-vHLLE-hybrid, please cite [arXiv:2212.08724](https://arxiv.org/abs/2112.08724). You may also consult this reference for further details about the hybrid approach. ## Prerequisites -- [cmake](https://cmake.org) version ≥ 3.15.4 -- [SMASH](https://github.com/smash-transport/smash) version ≥ 1.8 -- [vHLLE](https://github.com/yukarpenko/vhlle) -- [vHLLE parameters](https://github.com/yukarpenko/vhlle_params) -- [hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) version ≥ 1.0 -- python version ≥ 2.7 -- ([SMASH-analysis](https://github.com/smash-transport/smash-analysis) version ≥ 1.7, if automatic generation of particle spectra is desired) -Before building the full hybrid model, please make sure that the submodules listed above (SMASH, vHLLE, vHLLE params, sampler) are available and already compiled. Instructions on how to compile them can be found in the corresponding READMEs. +| Software | Required version | +| :------: | :--------------: | +| [CMake](https://cmake.org) | 3.15.4 or higher | +| [SMASH](https://github.com/smash-transport/smash) | 1.8 or higher | +| [vHLLE](https://github.com/yukarpenko/vhlle) | - | +| [vHLLE parameters](https://github.com/yukarpenko/vhlle_params) | - | +| [Hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) | 1.0 or higher | +| [Python](https://www.python.org) | 2.7 or higher | +| [SMASH-analysis](https://github.com/smash-transport/smash-analysis)* | 1.7 or higher | -The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in CMakeLists.txt of each submodule. It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. +*Needed if automatic generation of particle spectra is desired. + +Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. + +The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in CMakeLists.txt of each submodule. +It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. From 88c443e6745e7a7ff7b2a3638085c176a1fd21ce Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 Jan 2024 10:12:45 +0100 Subject: [PATCH 288/549] Make Git ignore VS code temporary files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index df076c6..648a269 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ build* binaries -.DS_Store python_scripts/*.pyc + +# OS specific auxiliary files +.vscode +.DS_Store From 14f57aaf1632fd87973c1f12c508ae8ddb6ea8fd Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 Jan 2024 10:14:03 +0100 Subject: [PATCH 289/549] Hard-code default output directory in helper Using "." instead of the corresponding resolved path makes the helper appear the same independently from which location the command is run. This is probably clearer and less misleading for the user. --- bash/command_line_parsers/helper.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index 51615f2..e1550e5 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -51,7 +51,7 @@ function __static__Print_Do_Help_Message() printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n' \ 'You can specify the following command line options to the' 'do' 'execution mode:' __static__Print_Command_Line_Option_Help \ - '-o | --output-directory' "${HYBRID_output_directory}" \ + '-o | --output-directory' '.' \ "Directory where the run folder(s) will be created." __static__Print_Command_Line_Option_Help \ '-c | --configuration-file' "${HYBRID_configuration_file}" \ From 167ada85370bc7ef634ac359d54a632264d61aaf Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 Jan 2024 10:15:24 +0100 Subject: [PATCH 290/549] Add first handler documentation to README file --- README.md | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/README.md b/README.md index a616f70..0414562 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,172 @@ Instructions on how to compile or install the software above can be found at the The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in CMakeLists.txt of each submodule. It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. + +### Unix system requirements + +The hybrid-handler makes use of many tools which are usually installed on Unix systems. +For some of them a minimum version is required and for the others their availability is enough. +However, in some cases, the GNU version is required and on some Linux distribution or on Apple machines the default installation might not be suitable. +To check out what is required and what is available on your system, simply run the `Hybrid-handler` executable without options: An overview of the main functionality as well as a system requirements overview will be produced. + +## The hybrid handler + +To run any of the different stages of the model, the `Hybrid-handler` executable should be used. +Such an executable has different execution modes and each of these can be invoked with the `--help` option to get specific documentation of such a mode. +Run `./Hybrid-handler do --help` for an example. +Each run of the hybrid handler makes use of a configuration file and it is the user responsibility to provide one. +Few further customizations are possible using command line options, which are documented in the helper of each execution mode. + +### The general behavior + +The main `do` execution mode of the handler runs stages of the model and it will create a given output tree at the specified output directory (by default this is the folder from where the handler is run, but it can customized using the `-o` or `--output-directory` command line option). +Assuming all stages are run, this is what the user will obtain. +``` +📂 Output-directory +│ +├─ 📂 IC +│ └─── 📂 Run_ID +│ └─ # output files +│ +├─ 📂 Hydro +│ └─── 📂 Run_ID +│ └─ # output files +│ +├─ 📂 Sampler +│ └─── 📂 Run_ID +│ └─ # output files +│ +└─ 📂 Afterburner + └─── 📂 Run_ID + └─ # output files +``` + +### The configuration file + +Using YAML syntax it is possible to customize in many different ways which and how different stages of the model are run. +The file must be structured in sections (technically these are YAML maps at the top-level). +Apart from a generic one, there exists one section for each stage of the model and the presence of any of this type of sections means that that stage of the model should be run. +Many sanity checks are performed at start-up and in case you violate any rule, a descriptive self-explanatory error will be provided (e.g. the order of the stages matters, no stage can be repeated, and so on). +If you are new to YAML, be reassured, our YAML usage is definitely basic. +Each key has to be followed by a colon and each section content has to be indented in a consistent way. +In the following documentation you will find examples, too, and they are probably enough to understand how to create your configuration file. + +#### The generic section + +There is a generic section that contains general information which is not specific to one stage only. +This is called `Hybrid_handler` and it can contain the following key(s). + +* `Run_ID`
+ This is the name used by the handler to create the folder for the actual run in the stage-dedicated directory. + If this key is not specified, a default name containing the date and time of the run is used (`Run_YYYY-MM-DD_hhmmss`). + +##### Example: + +```yaml +Hybrid_handler: + Run_ID: Cool_stuff_1 +``` + +#### The software sections + +Each stage of the model has a dedicated section. +These are (with the corresponding software to be used): +* `IC` for the initial conditions run (SMASH); +* `Hydro` for the viscous hydrodynamics stage (vHLLE); +* `Sampler` to perform particlization (Hadron sampler) and +* `Afterburner` for the last stage (SMASH). + +As a general comment, whenever a path has to be specified, both an absolute and a relative one are accepted. +However, **it is strongly encouraged to exclusively use absolute paths** as relative ones should be specified w.r.t. different folders (most of the times relatively to the stage output directory). + +#### Keys common to all software sections + +* `Executable`
+ Path to the executable file of the software to be used. +* `Config_file`
+ Path to the software specific configuration file. + If not specified, the files shipped in the ***configs*** folder are used. +* `Software_keys`
+ The value of this key is a YAML map and should be used to change values of the software configuration file. + It is not possible to add or remove keys, but only change already existing ones. + If you need to add a key to the software default configuration file, you should create a custom one and specify it via the `Config_file` key. + Depending on your needs, you could also create a more complete configuration file and change the values of some keys in your run(s) via this key. + +#### The initial conditions section + +There is no specific key of the `IC` section and only the generic ones can be used. + +##### Example: + +```yaml +IC: + Executable: /path/to/smash + Config_file: /path/to/IC_config.yaml + Software_keys: + General: + End_Time: 100 +``` + +#### The hydrodynamics section + +* `Input_file`
+ The hydrodynamics simulation needs an additional input file which contains the system initial conditions. + This is the main output of the previous stage and, therefore, if not specified, a *SMASH_IC.dat* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. + However, using this key, any file can be specified and used. + +##### Example: + +```yaml +Hydro: + Executable: /path/to/vHLLE + Config_file: /path/to/vHLLE_config + Software_keys: + etaS: 0.42 + Input_file: /path/to/IC_output.dat +``` + +#### The hadron sampler section + +Also the hadron sampler needs in input the freezeout surface file, which is produced at the previous hydrodynamics stage. +This file cannot be specified via any key in the hybrid handler configuration file, because the hadron sampler must receive the path to this file in its own configuration file. +If the user does not use a custom configuration file for the hadron sampler, the hybrid handler will use the default one, in which the path is set to `=DEFAULT=`. +This will be resolved by the hybrid handler to the path of a *freezeout.dat* file in the ***Hydro*** output sub-folder with the same `Run_ID`, which is expected to exist. +A mechanism like this one is technically needed to be able by default to refer to the same run ID and pick up the correct file from the previous stage. +As a side-effect, it is not possible for the user to name the freezeout surface file as _=DEFAULT=_, which anyways would not probably be a very clever choice. :sweat_smile: + +##### Example: + +```yaml +Sampler: + Executable: /path/to/Hadron-sampler + Config_file: /path/to/Hadron-sampler_config + Software_keys: + surface: /path/to/custom/freezeout.dat +``` + +#### The afterburner section + +* `Input_file`
+ As other stages, the afterburner run needs as well an additional input file which contains the initial particles list. + This is the main output of the previous sampler stage and, therefore, if not specified, a *particle_lists.oscar* file is expected to exist in the ***Sampler*** output sub-folder with the same `Run_ID`. + However, using this key, any file can be specified and used. +* `Add_spectators_from_IC`
+ Whether spectators from the initial conditions stage should be included or not in the afterburner run can be decided via this boolean key. + The default value is `false`. +* `Spectators_source`
+ If spectators from the initial conditions stage should be included in the afterburner run, a *SMASH_IC.oscar* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. + However, using this key any file path can be specified. + This key is ignored, unless `Add_spectators_from_IC` is not set to `true`. + +##### Example: + +```yaml +Afterburner: + Executable: /path/to/smash + Config_file: /path/to/Afterburner_config.yaml + Software_keys: + General: + Delta_Time: 0.25 + Add_spectators_from_IC: true + Spectators_source: /path/to/spectators-file.oscar +``` From 00253b2893ea658d987919df60e469700fdc7a1e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 17 Jan 2024 10:30:26 +0100 Subject: [PATCH 291/549] Add general example about basic configuration file --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0414562..cb7e001 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ If you are using the SMASH-vHLLE-hybrid, please cite [arXiv:2212.08724](https:// | [Python](https://www.python.org) | 2.7 or higher | | [SMASH-analysis](https://github.com/smash-transport/smash-analysis)* | 1.7 or higher | -*Needed if automatic generation of particle spectra is desired. +* Needed if automatic generation of particle spectra is desired. Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. @@ -110,6 +110,7 @@ However, **it is strongly encouraged to exclusively use absolute paths** as rela * `Executable`
Path to the executable file of the software to be used. + This key is **required** for all specified stages. * `Config_file`
Path to the software specific configuration file. If not specified, the files shipped in the ***configs*** folder are used. @@ -197,3 +198,25 @@ Afterburner: Add_spectators_from_IC: true Spectators_source: /path/to/spectators-file.oscar ``` + +### An example of a complete hybrid handler configuration file + +Assuming to desire to run a simulation of the full model using all the default behavior of the hybrid handler, then the following configuration file can be used. + +```yaml +IC: + Executable: /path/to/smash + +Hydro: + Executable: /path/to/vHLLE + +Sampler: + Executable: /path/to/Hadron-sampler + +Afterburner: + Executable: /path/to/smash +``` + +Omitting some stages is fine, as long as the omitted one(s) are contiguous from the beginning or from the end. +If one or more stages are omitted at the beginning of the model, it is understood that these have been previously run, because the later stages will need input from the previous ones. +In such a case, it will be needed to either explicitly provide the needed input file for the first stage in the run or specify the same `Run_ID` of the simulations already done. From 8c55565331e40ee11381306913992d5c5b0ea65e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 22 Jan 2024 16:21:46 +0100 Subject: [PATCH 292/549] Rephrase some explaination in README file --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cb7e001..a02ddcc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ It consists of the following modules: - Cooper-Frye sampler to perform particlization of the elements on the freezeout hypersurface; - SMASH hadronic transport approach to perform the afterburner evolution. -If you are using the SMASH-vHLLE-hybrid, please cite [arXiv:2212.08724](https://arxiv.org/abs/2112.08724). You may also consult this reference for further details about the hybrid approach. +If you are using the SMASH-vHLLE-hybrid, please cite [Eur.Phys.J.A 58(2022)11,230](https://arxiv.org/abs/2112.08724). You may also consult this reference for further details about the hybrid approach. ## Prerequisites @@ -72,7 +72,8 @@ Assuming all stages are run, this is what the user will obtain. Using YAML syntax it is possible to customize in many different ways which and how different stages of the model are run. The file must be structured in sections (technically these are YAML maps at the top-level). -Apart from a generic one, there exists one section for each stage of the model and the presence of any of this type of sections means that that stage of the model should be run. +Apart from a generic one, a section corresponding to each stage of the model exists. +The presence of any section of this kind implies that the corresponding stage of the model should be run. Many sanity checks are performed at start-up and in case you violate any rule, a descriptive self-explanatory error will be provided (e.g. the order of the stages matters, no stage can be repeated, and so on). If you are new to YAML, be reassured, our YAML usage is definitely basic. Each key has to be followed by a colon and each section content has to be indented in a consistent way. @@ -113,7 +114,7 @@ However, **it is strongly encouraged to exclusively use absolute paths** as rela This key is **required** for all specified stages. * `Config_file`
Path to the software specific configuration file. - If not specified, the files shipped in the ***configs*** folder are used. + If not specified, the file shipped in the ***configs*** folder is used. * `Software_keys`
The value of this key is a YAML map and should be used to change values of the software configuration file. It is not possible to add or remove keys, but only change already existing ones. @@ -138,7 +139,7 @@ IC: #### The hydrodynamics section * `Input_file`
- The hydrodynamics simulation needs an additional input file which contains the system initial conditions. + The hydrodynamics simulation needs an additional input file which contains the system's initial conditions. This is the main output of the previous stage and, therefore, if not specified, a *SMASH_IC.dat* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. However, using this key, any file can be specified and used. @@ -156,9 +157,11 @@ Hydro: #### The hadron sampler section Also the hadron sampler needs in input the freezeout surface file, which is produced at the previous hydrodynamics stage. -This file cannot be specified via any key in the hybrid handler configuration file, because the hadron sampler must receive the path to this file in its own configuration file. -If the user does not use a custom configuration file for the hadron sampler, the hybrid handler will use the default one, in which the path is set to `=DEFAULT=`. -This will be resolved by the hybrid handler to the path of a *freezeout.dat* file in the ***Hydro*** output sub-folder with the same `Run_ID`, which is expected to exist. +However, there is no dedicated `Input_file` key in the hadron sampler section of the hybrid handler configuration file, because the hadron sampler must receive the path to this file in its own configuration file already. +Therefore, the user can set any path to the freezeout surface file by specifying it in the `Software_keys` subsection, as shown in the example below. + +By default, if the user does not use a custom configuration file for the hadron sampler and does not specify the path to the freezeout surface file via `Software_keys`, the hybrid handler will use the configuration file for the hadron sampler which is contained in the ***configs*** folder and in which the path to the freezeout surface is set to `=DEFAULT=`. +This will be internally resolved by the hybrid handler to the path of a *freezeout.dat* file in the ***Hydro*** output sub-folder with the same `Run_ID`, which is expected to exist. A mechanism like this one is technically needed to be able by default to refer to the same run ID and pick up the correct file from the previous stage. As a side-effect, it is not possible for the user to name the freezeout surface file as _=DEFAULT=_, which anyways would not probably be a very clever choice. :sweat_smile: @@ -175,7 +178,7 @@ Sampler: #### The afterburner section * `Input_file`
- As other stages, the afterburner run needs as well an additional input file which contains the initial particles list. + As other stages, the afterburner run needs an additional input file as well, one which contains the sampled particles list. This is the main output of the previous sampler stage and, therefore, if not specified, a *particle_lists.oscar* file is expected to exist in the ***Sampler*** output sub-folder with the same `Run_ID`. However, using this key, any file can be specified and used. * `Add_spectators_from_IC`
@@ -201,7 +204,7 @@ Afterburner: ### An example of a complete hybrid handler configuration file -Assuming to desire to run a simulation of the full model using all the default behavior of the hybrid handler, then the following configuration file can be used. +If you wish to run a simulation of the full model using the default behavior of all the stages of the hybrid handler, then the following configuration file can be used. ```yaml IC: From 531c6d3bb99d6e4ded0dbae5ad9eadf741f14909 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 24 Jan 2024 10:16:56 +0100 Subject: [PATCH 293/549] Fix couple of errors in formatting unit test --- tests/unit_tests_formatting.bash | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash index 774129d..be0e349 100644 --- a/tests/unit_tests_formatting.bash +++ b/tests/unit_tests_formatting.bash @@ -13,7 +13,7 @@ function Unit_Test__codebase-formatting() if hash shfmt &> /dev/null; then formatter_found='TRUE' else - Print_Error 'Command ' --emph 'beautysh' \ + Print_Error 'Command ' --emph 'shfmt' \ ' not available, unable to fully check codebase formatting.' fi local -r max_length=120 @@ -29,9 +29,7 @@ function Unit_Test__codebase-formatting() continue fi done - if [[ ${formatter_found} = 'FALSE' ]]; then - continue - else + if [[ ${formatter_found} = 'TRUE' ]]; then # Quoting shfmt manual: # "If a given path is a directory, all shell scripts found under that directory will be used." files_with_wrong_formatting=( From ee5b45031b784192b2cf1deca281da854862d131 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 22 Jan 2024 15:47:09 +0100 Subject: [PATCH 294/549] Add first structure of documentation website --- docs/developer/documentation.md | 71 +++++++++++++++++++++++ docs/developer/overview.md | 1 + docs/images/favicon.png | Bin 0 -> 201048 bytes docs/index.md | 29 ++++++++++ docs/javascripts/mathjax.js | 16 ++++++ docs/prerequisites.md | 3 + docs/user/overview.md | 1 + mkdocs.yml | 99 ++++++++++++++++++++++++++++++++ 8 files changed, 220 insertions(+) create mode 100644 docs/developer/documentation.md create mode 100644 docs/developer/overview.md create mode 100644 docs/images/favicon.png create mode 100644 docs/index.md create mode 100644 docs/javascripts/mathjax.js create mode 100644 docs/prerequisites.md create mode 100644 docs/user/overview.md create mode 100644 mkdocs.yml diff --git a/docs/developer/documentation.md b/docs/developer/documentation.md new file mode 100644 index 0000000..9b7cd88 --- /dev/null +++ b/docs/developer/documentation.md @@ -0,0 +1,71 @@ +# Building the documentation + +The documentation is built using [MkDocs](https://www.mkdocs.org) and in particular the impressive [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme. +All documentation files and related material is located in :file_folder: ***docs***. +Therefore it is hosted in the codebase and it naturally evolves together with it. + +## Prerequisite + +Few packages are required and all can be installed using `pip`: +```bash +pip install mkdocs +pip install mkdocs-material +pip install mike +``` + +You can refer to the corresponding installation pages for further information (1). +{ .annotate } + +1. :fontawesome-solid-book: [MkDocs](https://www.mkdocs.org/user-guide/installation/)  + :simple-materialformkdocs: [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/getting-started/#installation)  [mike](https://github.com/jimporter/mike#installation) + +## Serving, building and deploying + +The deployed version is hosted on the :octicons-git-branch-16: `gh-pages` branch, while documentation from any other branch can be comfortably visualized in a browser opening up [http://127.0.0.1:8000/](http://127.0.0.1:8000/) after having run `mkdocs serve` in a terminal from the top-level repository folder. + +The documentation website can also be locally built by running `mkdocs build`, which will create a :file_folder: ***site*** folder containing the website source code. + +Once changes to the website have been locally checked, they are ready to be deployed. +We use `mike` to deploy documentation, because it allows to make different documentation versions coexist and be easily selected. +Our usage of this tool is quite basic, but you can check out [mike's documentation](https://github.com/jimporter/mike) to know more about it. + +!!! warning "Be aware of few caveats" + + Even if we use `mike` to deploy documentation, the same warnings about the [MkDocs deployment](https://www.mkdocs.org/user-guide/deploying-your-docs/) feature apply: + + 1. Be aware that you will not be able to review the built site before it is pushed to GitHub. Therefore, you may want to verify any changes you make to the docs beforehand by using the build or serve commands and reviewing the built files locally. + 2. You should never edit files in your pages repository by hand if you're using the gh-deploy command because you will lose your work the next time you run the command. + 3. If there are untracked files or uncommitted work in the local repository where `mkdocs gh-deploy` is run, these will be included in the pages that are deployed. + + +### Our deployment scheme + +The documentation does not store one version for _all_ versions of the codebase. +In particular, releases like a bug-fix which only change the `patch` number in the version string won't have a new documentation version associated. +To say it differently, the documentation of version `X.Y` will always show the documentation of the corresponding highest patch. + +#### At new releases + +When a new release of the codebase is done, a new version of the documentation should be built and deployed. +This can be easily done via +``` +mike deploy --push --update-aliases X.Y latest +``` +where `X.Y` are the `major.minor` version numbers of the release. +The command will also update the `latest` alias to point to the new release documentation. + +??? tip "Good to know" + Omitting the `--push` option, nothing will be pushed and you have the possibility to check the changes on the `gh-pages` branch, which must be then manually pushed to publish the changes. + +#### At new patches + +When a new patch is released, it is usually worth adjusting the documentation title via `make retitle --push X.Y `, where new title might simply be `X.Y.Z`, i.e. the complete new version string. + +#### The development documentation + +Documentation between releases will naturally evolve and, whenever it makes sense, changes to the documentation should be deployed, too. +For this purpose, developers should run +``` +mike deploy --push develop +``` +reasonably often, since this documentation is thought to reflect the state of the :octicons-git-branch-16: `develop` branch. diff --git a/docs/developer/overview.md b/docs/developer/overview.md new file mode 100644 index 0000000..854139a --- /dev/null +++ b/docs/developer/overview.md @@ -0,0 +1 @@ +# Contributing diff --git a/docs/images/favicon.png b/docs/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..2451f99b7c20e677725243240f26365b8eaab271 GIT binary patch literal 201048 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%0D2{n_=?hq0eO^=Kih) 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 { + MathJax.typesetPromise() + }) diff --git a/docs/prerequisites.md b/docs/prerequisites.md new file mode 100644 index 0000000..30e28b8 --- /dev/null +++ b/docs/prerequisites.md @@ -0,0 +1,3 @@ +# Simulation software + +# Other prerequisites diff --git a/docs/user/overview.md b/docs/user/overview.md new file mode 100644 index 0000000..c0490e9 --- /dev/null +++ b/docs/user/overview.md @@ -0,0 +1 @@ +# A wonderful unique tool diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..1dab084 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,99 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# Refer to https://squidfunk.github.io/mkdocs-material/ for the documentation +# of most of the setup done here (cf. in particular the 'Setup' and 'Reference' +# tabs in the documentation). + +site_name: SMASH-vHLLE-Hybrid +copyright: Copyright © 2024 - SMASH team + +repo_url: https://github.com/smash-transport/smash-vhlle-hybrid +repo_name: 'Repository' + +nav: + - Home: index.md + - Getting started: prerequisites.md + - User Guide: + - The hybrid handler: user/overview.md + - Developer Guide: + - Building the documentation: developer/documentation.md + - Overview: developer/overview.md + +markdown_extensions: + # More markdown functionality + - tables + - footnotes + - attr_list + - md_in_html + - pymdownx.critic + - pymdownx.keys + # Collapsible admonitions + - admonition + - pymdownx.details + # Emojis + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + # For LaTeX and maths + - pymdownx.arithmatex: + generic: true + # For code highlights and related aspects + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + + +theme: + name: material + favicon: images/favicon.png + #logo: images/logo.png # <-- To be added as soon as we have one! + features: + - content.code.copy + - header.autohide + - navigation.tabs + - navigation.sections + - navigation.top + - toc.follow + icon: + repo: fontawesome/brands/github + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: blue + accent: orange + toggle: + icon: material/weather-night + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: deep orange + toggle: + icon: material/weather-sunny + name: Switch to light mode + +extra: + social: + - icon: fontawesome/solid/link + link: https://github.com/smash-transport + name: SMASH transport organization + version: + provider: mike + +extra_javascript: + - javascripts/mathjax.js + - https://polyfill.io/v3/polyfill.min.js?features=es6 + - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js From 9fba5e9b286840b20be0a39a885249b2845f4778 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 23 Jan 2024 10:17:16 +0100 Subject: [PATCH 295/549] Include first user documentation and further work on style --- docs/developer/{documentation.md => index.md} | 0 docs/index.md | 17 +- docs/prerequisites.md | 3 - docs/stylesheets/extra.css | 17 ++ docs/user/configuration-file.md | 147 ++++++++++++++++++ docs/user/index.md | 13 ++ docs/user/overview.md | 38 ++++- docs/user/prerequisites.md | 26 ++++ mkdocs.yml | 17 +- 9 files changed, 258 insertions(+), 20 deletions(-) rename docs/developer/{documentation.md => index.md} (100%) delete mode 100644 docs/prerequisites.md create mode 100644 docs/stylesheets/extra.css create mode 100644 docs/user/configuration-file.md create mode 100644 docs/user/index.md create mode 100644 docs/user/prerequisites.md diff --git a/docs/developer/documentation.md b/docs/developer/index.md similarity index 100% rename from docs/developer/documentation.md rename to docs/developer/index.md diff --git a/docs/index.md b/docs/index.md index 6a0c8a4..007bd48 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,6 +5,7 @@ hide: --- # SMASH-vHLLE-Hybrid + Event-by-event hybrid model for the description of relativistic heavy-ion collisions in the low and high baryon-density regime. This model constitutes a chain of different submodules to appropriately describe each phase of the collision with its corresponding degrees of freedom. It consists of the following modules: - SMASH hadronic transport approach to provide the initial conditions @@ -13,17 +14,7 @@ Event-by-event hybrid model for the description of relativistic heavy-ion collis - Cooper-Frye sampler to perform particlization of the elements on the freezeout hypersurface - SMASH hadronic transport approach to perform the afterburner evolution -If you are using the SMASH-vHLLE-hybrid, please cite [arXiv:2212.08724](https://arxiv.org/abs/2112.08724). You may also consult this reference for further details about the hybrid approach. - -## Prerequisites -- [cmake](https://cmake.org) version ≥ 3.15.4 -- [SMASH](https://github.com/smash-transport/smash) version ≥ 1.8 -- [vHLLE](https://github.com/yukarpenko/vhlle) -- [vHLLE parameters](https://github.com/yukarpenko/vhlle_params) -- [hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) version ≥ 1.0 -- python version ≥ 2.7 -- ([SMASH-analysis](https://github.com/smash-transport/smash-analysis) version ≥ 1.7, if automatic generation of particle spectra is desired) - -Before building the full hybrid model, please make sure that the submodules listed above (SMASH, vHLLE, vHLLE params, sampler) are available and already compiled. Instructions on how to compile them can be found in the corresponding READMEs. +!!! info "Give credit appropriately" -The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in CMakeLists.txt of each submodule. It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. + If you are using the SMASH-vHLLE-hybrid, please cite [Eur.Phys.J.A 58(2022)11,230](https://arxiv.org/abs/2112.08724). + You may also consult this reference for further details about the hybrid approach. diff --git a/docs/prerequisites.md b/docs/prerequisites.md deleted file mode 100644 index 30e28b8..0000000 --- a/docs/prerequisites.md +++ /dev/null @@ -1,3 +0,0 @@ -# Simulation software - -# Other prerequisites diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..37e64b9 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,17 @@ +:root { + --md-admonition-icon--config-key: url('data:image/svg+xml;charset=utf-8,') + } + .md-typeset .admonition.config-key, + .md-typeset details.config-key { + border-color: rgb(43, 155, 70); + } + .md-typeset .config-key > .admonition-title, + .md-typeset .config-key > summary { + background-color: rgba(43, 155, 70, 0.1); + } + .md-typeset .config-key > .admonition-title::before, + .md-typeset .config-key > summary::before { + background-color: rgb(43, 155, 70); + -webkit-mask-image: var(--md-admonition-icon--config-key); + mask-image: var(--md-admonition-icon--config-key); + } diff --git a/docs/user/configuration-file.md b/docs/user/configuration-file.md new file mode 100644 index 0000000..832f5cf --- /dev/null +++ b/docs/user/configuration-file.md @@ -0,0 +1,147 @@ +# The configuration file + +Using YAML syntax it is possible to customize in many different ways which and how different stages of the model are run. +The file must be structured in sections (technically these are YAML maps at the top-level). +Apart from a generic one, a section corresponding to each stage of the model exists. +The presence of any section of this kind implies that the corresponding stage of the model should be run. +Many sanity checks are performed at start-up and in case you violate any rule, a descriptive self-explanatory error will be provided (e.g. the order of the stages matters, no stage can be repeated, and so on). +If you are new to YAML, be reassured, our YAML usage is definitely basic. +Each key has to be followed by a colon and each section content has to be indented in a consistent way. +In the following documentation you will find examples, too, and they are probably enough to understand how to create your configuration file. + +## The generic section + +There is a generic section that contains general information which is not specific to one stage only. +This is called `Hybrid_handler` and it can contain the following key(s). + +???+ config-key "`Run_ID`" + + This is the name used by the handler to create the folder for the actual run in the stage-dedicated directory. + If this key is not specified, a default name containing the date and time of the run is used (`Run_YYYY-MM-DD_hhmmss`). + +```yaml title="Example" +Hybrid_handler: + Run_ID: Cool_stuff_1 +``` + +## The software sections + +Each stage of the model has a dedicated section. +These are (with the corresponding software to be used): + +* `IC` for the initial conditions run (SMASH); +* `Hydro` for the viscous hydrodynamics stage (vHLLE); +* `Sampler` to perform particlization (Hadron sampler) and +* `Afterburner` for the last stage (SMASH). + +As a general comment, whenever a path has to be specified, both an absolute and a relative one are accepted. +However, **it is strongly encouraged to exclusively use absolute paths** as relative ones should be specified w.r.t. different folders (most of the times relatively to the stage output directory). + +## Keys common to all software sections + +* `Executable`
+ Path to the executable file of the software to be used. + This key is **required** for all specified stages. +* `Config_file`
+ Path to the software specific configuration file. + If not specified, the file shipped in the ***configs*** folder is used. +* `Software_keys`
+ The value of this key is a YAML map and should be used to change values of the software configuration file. + It is not possible to add or remove keys, but only change already existing ones. + If you need to add a key to the software default configuration file, you should create a custom one and specify it via the `Config_file` key. + Depending on your needs, you could also create a more complete configuration file and change the values of some keys in your run(s) via this key. + +## The initial conditions section + +There is no specific key of the `IC` section and only the generic ones can be used. + +```yaml title="Example" +IC: + Executable: /path/to/smash + Config_file: /path/to/IC_config.yaml + Software_keys: + General: + End_Time: 100 +``` + +## The hydrodynamics section + +* `Input_file`
+ The hydrodynamics simulation needs an additional input file which contains the system's initial conditions. + This is the main output of the previous stage and, therefore, if not specified, a *SMASH_IC.dat* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. + However, using this key, any file can be specified and used. + +```yaml title="Example" +Hydro: + Executable: /path/to/vHLLE + Config_file: /path/to/vHLLE_config + Software_keys: + etaS: 0.42 + Input_file: /path/to/IC_output.dat +``` + +## The hadron sampler section + +Also the hadron sampler needs in input the freezeout surface file, which is produced at the previous hydrodynamics stage. +However, there is no dedicated key in the hybrid handler configuration file, because the hadron sampler must receive the path to this file in its own configuration file already. +Therefore, the user can set any path to the freezeout surface file by specifying it in the `Software_keys` subsection, as shown in the example below. + +By default, if the user does not use a custom configuration file for the hadron sampler and does not specify the path to the freezeout surface file via `Software_keys`, the hybrid handler will use the configuration file for the hadron sampler which is contained in the ***configs*** folder and in which the path to the freezeout surface is set to `=DEFAULT=`. +This will be internally resolved by the hybrid handler to the path of a *freezeout.dat* file in the ***Hydro*** output sub-folder with the same `Run_ID`, which is expected to exist. +A mechanism like this one is technically needed to be able by default to refer to the same run ID and pick up the correct file from the previous stage. +As a side-effect, it is not possible for the user to name the freezeout surface file as _=DEFAULT=_, which anyways would not probably be a very clever choice. :sweat_smile: + +```yaml title="Example" +Sampler: + Executable: /path/to/Hadron-sampler + Config_file: /path/to/Hadron-sampler_config + Software_keys: + surface: /path/to/custom/freezeout.dat +``` + +## The afterburner section + +* `Input_file`
+ As other stages, the afterburner run needs an additional input file as well, one which contains the sampled particles list. + This is the main output of the previous sampler stage and, therefore, if not specified, a *particle_lists.oscar* file is expected to exist in the ***Sampler*** output sub-folder with the same `Run_ID`. + However, using this key, any file can be specified and used. +* `Add_spectators_from_IC`
+ Whether spectators from the initial conditions stage should be included or not in the afterburner run can be decided via this boolean key. + The default value is `false`. +* `Spectators_source`
+ If spectators from the initial conditions stage should be included in the afterburner run, a *SMASH_IC.oscar* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. + However, using this key any file path can be specified. + This key is ignored, unless `Add_spectators_from_IC` is not set to `true`. + +```yaml title="Example" +Afterburner: + Executable: /path/to/smash + Config_file: /path/to/Afterburner_config.yaml + Software_keys: + General: + Delta_Time: 0.25 + Add_spectators_from_IC: true + Spectators_source: /path/to/spectators-file.oscar +``` + +## An example of a complete hybrid handler configuration file + +If you wish to run a simulation of the full model using the default behavior of all the stages of the hybrid handler, then the following configuration file can be used. + +```yaml title="Example" +IC: + Executable: /path/to/smash + +Hydro: + Executable: /path/to/vHLLE + +Sampler: + Executable: /path/to/Hadron-sampler + +Afterburner: + Executable: /path/to/smash +``` + +Omitting some stages is fine, as long as the omitted one(s) are contiguous from the beginning or from the end. +If one or more stages are omitted at the beginning of the model, it is understood that these have been previously run, because the later stages will need input from the previous ones. +In such a case, it will be needed to either explicitly provide the needed input file for the first stage in the run or specify the same `Run_ID` of the simulations already done. diff --git a/docs/user/index.md b/docs/user/index.md new file mode 100644 index 0000000..438c02b --- /dev/null +++ b/docs/user/index.md @@ -0,0 +1,13 @@ +# A unique wonderful tool + +The hybrid handler is a :simple-gnubash: **Bash script** and therefore it does not need any installation. + +

+ +- :octicons-gear-16: __Ready to go?__ – [Check it out!] +- :sunrise_over_mountains: __What's the main idea?__ - [Here you go!] + +
+ + [Check it out!]: prerequisites.md + [Here you go!]: overview.md diff --git a/docs/user/overview.md b/docs/user/overview.md index c0490e9..4bb572fb1 100644 --- a/docs/user/overview.md +++ b/docs/user/overview.md @@ -1 +1,37 @@ -# A wonderful unique tool +# The hybrid handler + +To run any of the different stages of the model, the :simple-gnubash: `Hybrid-handler` executable should be used. +Such an executable has different execution modes and each of these can be invoked with the `--help` option to get specific documentation of such a mode. + +``` title="Example about getting help for a given mode" +./Hybrid-handler do --help +``` + +If the executable is run without any execution mode (or simply with `--help`), an overview of its features is given. + +Each run of the hybrid handler makes use of a configuration file and it is the user responsibility to provide one. +Few further customizations are possible using command line options, which are documented in the helper of each execution mode. + +## The general behavior + +The main `do` execution mode of the handler runs stages of the model and it will create a given output tree at the specified output directory (by default this is the folder from where the handler is run, but it can customized using the `-o` or `--output-directory` command line option). +Assuming all stages are run, this is what the user will obtain. +``` { .bash .no-copy } +📂 Output-directory +│ +├─ 📂 IC +│ └─── 📂 Run_ID +│ └─ # output files +│ +├─ 📂 Hydro +│ └─── 📂 Run_ID +│ └─ # output files +│ +├─ 📂 Sampler +│ └─── 📂 Run_ID +│ └─ # output files +│ +└─ 📂 Afterburner + └─── 📂 Run_ID + └─ # output files +``` diff --git a/docs/user/prerequisites.md b/docs/user/prerequisites.md new file mode 100644 index 0000000..9fe28eb --- /dev/null +++ b/docs/user/prerequisites.md @@ -0,0 +1,26 @@ + + +# Simulation software + +| Software | Required version | +| :------: | :--------------: | +| [SMASH](https://github.com/smash-transport/smash) | 1.8 or higher | +| [vHLLE](https://github.com/yukarpenko/vhlle) | - | +| [vHLLE parameters](https://github.com/yukarpenko/vhlle_params) | - | +| [Hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) | 1.0 or higher | +| [Python](https://www.python.org) | 2.7 or higher | +| [SMASH-analysis](https://github.com/smash-transport/smash-analysis)[^1] | 1.7 or higher | + +[^1]: `SMASH-analysis` is needed only if automatic generation of particle spectra is desired. + +Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. + +The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in the :material-file: _CMakeLists.txt_ file of each submodule. +It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. + +# Unix system requirements + +The hybrid handler makes use of many tools which are usually installed on Unix systems. +For some of them a minimum version is required and for the others their availability is enough. +However, in some cases, the GNU version is required and on some Linux distribution or on Apple machines the default installation might not be suitable. +To check out what is required and what is available on your system, simply run the `Hybrid-handler` executable without options: An overview of the main functionality as well as a system requirements overview will be produced. diff --git a/mkdocs.yml b/mkdocs.yml index 1dab084..02a6a5e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,11 +19,13 @@ repo_name: 'Repository' nav: - Home: index.md - - Getting started: prerequisites.md - User Guide: + - user/index.md + - Getting started: user/prerequisites.md - The hybrid handler: user/overview.md + - Handler configuration file: user/configuration-file.md - Developer Guide: - - Building the documentation: developer/documentation.md + - developer/index.md - Overview: developer/overview.md markdown_extensions: @@ -52,6 +54,11 @@ markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences + # Customize tables of contents + - toc: + permalink: true + permalink_title: "Link to this section" + toc_depth: 4 theme: @@ -61,8 +68,9 @@ theme: features: - content.code.copy - header.autohide - - navigation.tabs + - navigation.indexes - navigation.sections + - navigation.tabs - navigation.top - toc.follow icon: @@ -93,6 +101,9 @@ extra: version: provider: mike +extra_css: + - stylesheets/extra.css + extra_javascript: - javascripts/mathjax.js - https://polyfill.io/v3/polyfill.min.js?features=es6 From ee54db364de4cca5b6bc233763de0c07890a9b0c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 23 Jan 2024 10:35:03 +0100 Subject: [PATCH 296/549] Finish restyling about configuration keys --- docs/user/configuration-file.md | 79 +++++++++++++++++++-------------- docs/user/index.md | 6 ++- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/docs/user/configuration-file.md b/docs/user/configuration-file.md index 832f5cf..9991da8 100644 --- a/docs/user/configuration-file.md +++ b/docs/user/configuration-file.md @@ -39,17 +39,22 @@ However, **it is strongly encouraged to exclusively use absolute paths** as rela ## Keys common to all software sections -* `Executable`
- Path to the executable file of the software to be used. - This key is **required** for all specified stages. -* `Config_file`
- Path to the software specific configuration file. - If not specified, the file shipped in the ***configs*** folder is used. -* `Software_keys`
- The value of this key is a YAML map and should be used to change values of the software configuration file. - It is not possible to add or remove keys, but only change already existing ones. - If you need to add a key to the software default configuration file, you should create a custom one and specify it via the `Config_file` key. - Depending on your needs, you could also create a more complete configuration file and change the values of some keys in your run(s) via this key. +???+ config-key "`Executable`" + + Path to the executable file of the software to be used. + This key is **required** for all specified stages. + +???+ config-key "`Config_file`" + + Path to the software specific configuration file. + If not specified, the file shipped in the ***configs*** folder is used. + +???+ config-key "`Software_keys`" + + The value of this key is a YAML map and should be used to change values of the software configuration file. + It is not possible to add or remove keys, but only change already existing ones. + If you need to add a key to the software default configuration file, you should create a custom one and specify it via the `Config_file` key. + Depending on your needs, you could also create a more complete configuration file and change the values of some keys in your run(s) via this key. ## The initial conditions section @@ -66,10 +71,11 @@ IC: ## The hydrodynamics section -* `Input_file`
- The hydrodynamics simulation needs an additional input file which contains the system's initial conditions. - This is the main output of the previous stage and, therefore, if not specified, a *SMASH_IC.dat* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. - However, using this key, any file can be specified and used. +???+ config-key "`Input_file`" + + The hydrodynamics simulation needs an additional input file which contains the system's initial conditions. + This is the main output of the previous stage and, therefore, if not specified, a :material-file: *SMASH_IC.dat* file is expected to exist in the :file_folder: ***IC*** output sub-folder with the same `Run_ID`. + However, using this key, any file can be specified and used. ```yaml title="Example" Hydro: @@ -83,13 +89,13 @@ Hydro: ## The hadron sampler section Also the hadron sampler needs in input the freezeout surface file, which is produced at the previous hydrodynamics stage. -However, there is no dedicated key in the hybrid handler configuration file, because the hadron sampler must receive the path to this file in its own configuration file already. +However, there is no dedicated `Input_file` key in the hadron sampler section of the hybrid handler configuration file, because the hadron sampler must receive the path to this file in its own configuration file already. Therefore, the user can set any path to the freezeout surface file by specifying it in the `Software_keys` subsection, as shown in the example below. -By default, if the user does not use a custom configuration file for the hadron sampler and does not specify the path to the freezeout surface file via `Software_keys`, the hybrid handler will use the configuration file for the hadron sampler which is contained in the ***configs*** folder and in which the path to the freezeout surface is set to `=DEFAULT=`. -This will be internally resolved by the hybrid handler to the path of a *freezeout.dat* file in the ***Hydro*** output sub-folder with the same `Run_ID`, which is expected to exist. +By default, if the user does not use a custom configuration file for the hadron sampler and does not specify the path to the freezeout surface file via `Software_keys`, the hybrid handler will use the configuration file for the hadron sampler which is contained in the :file_folder: ***configs*** folder and in which the path to the freezeout surface is set to `=DEFAULT=`. +This will be internally resolved by the hybrid handler to the path of a :material-file: *freezeout.dat* file in the :file_folder: ***Hydro*** output sub-folder with the same `Run_ID`, which is expected to exist. A mechanism like this one is technically needed to be able by default to refer to the same run ID and pick up the correct file from the previous stage. -As a side-effect, it is not possible for the user to name the freezeout surface file as _=DEFAULT=_, which anyways would not probably be a very clever choice. :sweat_smile: +As a side-effect, it is not possible for the user to name the freezeout surface file as `=DEFAULT=`, which anyways would not probably be a very clever choice. :sweat_smile: ```yaml title="Example" Sampler: @@ -101,17 +107,22 @@ Sampler: ## The afterburner section -* `Input_file`
- As other stages, the afterburner run needs an additional input file as well, one which contains the sampled particles list. - This is the main output of the previous sampler stage and, therefore, if not specified, a *particle_lists.oscar* file is expected to exist in the ***Sampler*** output sub-folder with the same `Run_ID`. - However, using this key, any file can be specified and used. -* `Add_spectators_from_IC`
- Whether spectators from the initial conditions stage should be included or not in the afterburner run can be decided via this boolean key. - The default value is `false`. -* `Spectators_source`
- If spectators from the initial conditions stage should be included in the afterburner run, a *SMASH_IC.oscar* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. - However, using this key any file path can be specified. - This key is ignored, unless `Add_spectators_from_IC` is not set to `true`. +???+ config-key "`Input_file`" + + As other stages, the afterburner run needs an additional input file as well, one which contains the sampled particles list. + This is the main output of the previous sampler stage and, therefore, if not specified, a *particle_lists.oscar* file is expected to exist in the ***Sampler*** output sub-folder with the same `Run_ID`. + However, using this key, any file can be specified and used. + +???+ config-key "`Add_spectators_from_IC`" + + Whether spectators from the initial conditions stage should be included or not in the afterburner run can be decided via this boolean key. + The default value is `false`. + +???+ config-key "`Spectators_source`" + + If spectators from the initial conditions stage should be included in the afterburner run, a :material-file: *SMASH_IC.oscar* file is expected to exist in the :file_folder: ***IC*** output sub-folder with the same `Run_ID`. + However, using this key any file path can be specified. + This key is ignored, unless `Add_spectators_from_IC` is not set to `true`. ```yaml title="Example" Afterburner: @@ -142,6 +153,8 @@ Afterburner: Executable: /path/to/smash ``` -Omitting some stages is fine, as long as the omitted one(s) are contiguous from the beginning or from the end. -If one or more stages are omitted at the beginning of the model, it is understood that these have been previously run, because the later stages will need input from the previous ones. -In such a case, it will be needed to either explicitly provide the needed input file for the first stage in the run or specify the same `Run_ID` of the simulations already done. +??? question "What if I want to omit some stages?" + + Omitting some stages is fine, as long as the omitted one(s) are contiguous from the beginning or from the end. + If one or more stages are omitted at the beginning of the model, it is understood that these have been previously run, because the later stages will need input from the previous ones. + In such a case, it will be needed to either explicitly provide the needed input file for the first stage in the run or specify the same `Run_ID` of the simulations already done. diff --git a/docs/user/index.md b/docs/user/index.md index 438c02b..342760b 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -4,10 +4,12 @@ The hybrid handler is a :simple-gnubash: **Bash script** and therefore it does n
-- :octicons-gear-16: __Ready to go?__ – [Check it out!] -- :sunrise_over_mountains: __What's the main idea?__ - [Here you go!] +- :white_check_mark:   __Ready to go?__ – [Check it out!] +- :sunrise_over_mountains:   __What's the main idea?__ - [Here you go!] +- :screwdriver:   __Wanna run?__ - [Write your config file!]
[Check it out!]: prerequisites.md [Here you go!]: overview.md + [Write your config file!]: configuration-file.md From c553e576622b06f40b2c35170fb232065c89dbd6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 23 Jan 2024 10:47:38 +0100 Subject: [PATCH 297/549] Add icon next to link to deploying tool --- docs/developer/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer/index.md b/docs/developer/index.md index 9b7cd88..9aef972 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -17,7 +17,7 @@ You can refer to the corresponding installation pages for further information (1 { .annotate } 1. :fontawesome-solid-book: [MkDocs](https://www.mkdocs.org/user-guide/installation/)  - :simple-materialformkdocs: [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/getting-started/#installation)  [mike](https://github.com/jimporter/mike#installation) + :simple-materialformkdocs: [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/getting-started/#installation)  :material-bike-fast: [mike](https://github.com/jimporter/mike#installation) ## Serving, building and deploying From db01bfbfd5f245cc1e1ea43f30f9e182a6112349 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 23 Jan 2024 11:10:40 +0100 Subject: [PATCH 298/549] Remove text moved into documentation from README A placeholder sentence has been added there and it should be changed soon. --- README.md | 234 +++--------------------------------------------------- 1 file changed, 11 insertions(+), 223 deletions(-) diff --git a/README.md b/README.md index a02ddcc..9ef8528 100644 --- a/README.md +++ b/README.md @@ -1,225 +1,13 @@ # SMASH-vHLLE-Hybrid -Event-by-event hybrid model for the description of relativistic heavy-ion collisions in the low and high baryon-density regime. -This model constitutes a chain of different submodules to appropriately describe each phase of the collision with its corresponding degrees of freedom. -It consists of the following modules: -- SMASH hadronic transport approach to provide the initial conditions; -- vHLLE 3+1D viscous hydrodynamics approach to describe the evolution of the hot and dense fireball; -- CORNELIUS tool to construct a hypersurface of constant energy density from the hydrodynamical evolution (embedded in vHLLE); -- Cooper-Frye sampler to perform particlization of the elements on the freezeout hypersurface; -- SMASH hadronic transport approach to perform the afterburner evolution. -If you are using the SMASH-vHLLE-hybrid, please cite [Eur.Phys.J.A 58(2022)11,230](https://arxiv.org/abs/2112.08724). You may also consult this reference for further details about the hybrid approach. - -## Prerequisites - -| Software | Required version | -| :------: | :--------------: | -| [CMake](https://cmake.org) | 3.15.4 or higher | -| [SMASH](https://github.com/smash-transport/smash) | 1.8 or higher | -| [vHLLE](https://github.com/yukarpenko/vhlle) | - | -| [vHLLE parameters](https://github.com/yukarpenko/vhlle_params) | - | -| [Hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) | 1.0 or higher | -| [Python](https://www.python.org) | 2.7 or higher | -| [SMASH-analysis](https://github.com/smash-transport/smash-analysis)* | 1.7 or higher | - -* Needed if automatic generation of particle spectra is desired. - -Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. - -The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in CMakeLists.txt of each submodule. -It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. - -### Unix system requirements - -The hybrid-handler makes use of many tools which are usually installed on Unix systems. -For some of them a minimum version is required and for the others their availability is enough. -However, in some cases, the GNU version is required and on some Linux distribution or on Apple machines the default installation might not be suitable. -To check out what is required and what is available on your system, simply run the `Hybrid-handler` executable without options: An overview of the main functionality as well as a system requirements overview will be produced. - -## The hybrid handler - -To run any of the different stages of the model, the `Hybrid-handler` executable should be used. -Such an executable has different execution modes and each of these can be invoked with the `--help` option to get specific documentation of such a mode. -Run `./Hybrid-handler do --help` for an example. -Each run of the hybrid handler makes use of a configuration file and it is the user responsibility to provide one. -Few further customizations are possible using command line options, which are documented in the helper of each execution mode. - -### The general behavior - -The main `do` execution mode of the handler runs stages of the model and it will create a given output tree at the specified output directory (by default this is the folder from where the handler is run, but it can customized using the `-o` or `--output-directory` command line option). -Assuming all stages are run, this is what the user will obtain. -``` -📂 Output-directory -│ -├─ 📂 IC -│ └─── 📂 Run_ID -│ └─ # output files -│ -├─ 📂 Hydro -│ └─── 📂 Run_ID -│ └─ # output files -│ -├─ 📂 Sampler -│ └─── 📂 Run_ID -│ └─ # output files -│ -└─ 📂 Afterburner - └─── 📂 Run_ID - └─ # output files -``` - -### The configuration file - -Using YAML syntax it is possible to customize in many different ways which and how different stages of the model are run. -The file must be structured in sections (technically these are YAML maps at the top-level). -Apart from a generic one, a section corresponding to each stage of the model exists. -The presence of any section of this kind implies that the corresponding stage of the model should be run. -Many sanity checks are performed at start-up and in case you violate any rule, a descriptive self-explanatory error will be provided (e.g. the order of the stages matters, no stage can be repeated, and so on). -If you are new to YAML, be reassured, our YAML usage is definitely basic. -Each key has to be followed by a colon and each section content has to be indented in a consistent way. -In the following documentation you will find examples, too, and they are probably enough to understand how to create your configuration file. - -#### The generic section - -There is a generic section that contains general information which is not specific to one stage only. -This is called `Hybrid_handler` and it can contain the following key(s). - -* `Run_ID`
- This is the name used by the handler to create the folder for the actual run in the stage-dedicated directory. - If this key is not specified, a default name containing the date and time of the run is used (`Run_YYYY-MM-DD_hhmmss`). - -##### Example: - -```yaml -Hybrid_handler: - Run_ID: Cool_stuff_1 -``` - -#### The software sections - -Each stage of the model has a dedicated section. -These are (with the corresponding software to be used): -* `IC` for the initial conditions run (SMASH); -* `Hydro` for the viscous hydrodynamics stage (vHLLE); -* `Sampler` to perform particlization (Hadron sampler) and -* `Afterburner` for the last stage (SMASH). - -As a general comment, whenever a path has to be specified, both an absolute and a relative one are accepted. -However, **it is strongly encouraged to exclusively use absolute paths** as relative ones should be specified w.r.t. different folders (most of the times relatively to the stage output directory). - -#### Keys common to all software sections - -* `Executable`
- Path to the executable file of the software to be used. - This key is **required** for all specified stages. -* `Config_file`
- Path to the software specific configuration file. - If not specified, the file shipped in the ***configs*** folder is used. -* `Software_keys`
- The value of this key is a YAML map and should be used to change values of the software configuration file. - It is not possible to add or remove keys, but only change already existing ones. - If you need to add a key to the software default configuration file, you should create a custom one and specify it via the `Config_file` key. - Depending on your needs, you could also create a more complete configuration file and change the values of some keys in your run(s) via this key. - -#### The initial conditions section - -There is no specific key of the `IC` section and only the generic ones can be used. - -##### Example: - -```yaml -IC: - Executable: /path/to/smash - Config_file: /path/to/IC_config.yaml - Software_keys: - General: - End_Time: 100 -``` - -#### The hydrodynamics section - -* `Input_file`
- The hydrodynamics simulation needs an additional input file which contains the system's initial conditions. - This is the main output of the previous stage and, therefore, if not specified, a *SMASH_IC.dat* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. - However, using this key, any file can be specified and used. - -##### Example: - -```yaml -Hydro: - Executable: /path/to/vHLLE - Config_file: /path/to/vHLLE_config - Software_keys: - etaS: 0.42 - Input_file: /path/to/IC_output.dat -``` - -#### The hadron sampler section - -Also the hadron sampler needs in input the freezeout surface file, which is produced at the previous hydrodynamics stage. -However, there is no dedicated `Input_file` key in the hadron sampler section of the hybrid handler configuration file, because the hadron sampler must receive the path to this file in its own configuration file already. -Therefore, the user can set any path to the freezeout surface file by specifying it in the `Software_keys` subsection, as shown in the example below. - -By default, if the user does not use a custom configuration file for the hadron sampler and does not specify the path to the freezeout surface file via `Software_keys`, the hybrid handler will use the configuration file for the hadron sampler which is contained in the ***configs*** folder and in which the path to the freezeout surface is set to `=DEFAULT=`. -This will be internally resolved by the hybrid handler to the path of a *freezeout.dat* file in the ***Hydro*** output sub-folder with the same `Run_ID`, which is expected to exist. -A mechanism like this one is technically needed to be able by default to refer to the same run ID and pick up the correct file from the previous stage. -As a side-effect, it is not possible for the user to name the freezeout surface file as _=DEFAULT=_, which anyways would not probably be a very clever choice. :sweat_smile: - -##### Example: - -```yaml -Sampler: - Executable: /path/to/Hadron-sampler - Config_file: /path/to/Hadron-sampler_config - Software_keys: - surface: /path/to/custom/freezeout.dat -``` - -#### The afterburner section - -* `Input_file`
- As other stages, the afterburner run needs an additional input file as well, one which contains the sampled particles list. - This is the main output of the previous sampler stage and, therefore, if not specified, a *particle_lists.oscar* file is expected to exist in the ***Sampler*** output sub-folder with the same `Run_ID`. - However, using this key, any file can be specified and used. -* `Add_spectators_from_IC`
- Whether spectators from the initial conditions stage should be included or not in the afterburner run can be decided via this boolean key. - The default value is `false`. -* `Spectators_source`
- If spectators from the initial conditions stage should be included in the afterburner run, a *SMASH_IC.oscar* file is expected to exist in the ***IC*** output sub-folder with the same `Run_ID`. - However, using this key any file path can be specified. - This key is ignored, unless `Add_spectators_from_IC` is not set to `true`. - -##### Example: - -```yaml -Afterburner: - Executable: /path/to/smash - Config_file: /path/to/Afterburner_config.yaml - Software_keys: - General: - Delta_Time: 0.25 - Add_spectators_from_IC: true - Spectators_source: /path/to/spectators-file.oscar -``` - -### An example of a complete hybrid handler configuration file - -If you wish to run a simulation of the full model using the default behavior of all the stages of the hybrid handler, then the following configuration file can be used. - -```yaml -IC: - Executable: /path/to/smash - -Hydro: - Executable: /path/to/vHLLE - -Sampler: - Executable: /path/to/Hadron-sampler - -Afterburner: - Executable: /path/to/smash -``` - -Omitting some stages is fine, as long as the omitted one(s) are contiguous from the beginning or from the end. -If one or more stages are omitted at the beginning of the model, it is understood that these have been previously run, because the later stages will need input from the previous ones. -In such a case, it will be needed to either explicitly provide the needed input file for the first stage in the run or specify the same `Run_ID` of the simulations already done. +This project is in a transition phase. +A website associated to this project with full documentation guides will be deployed soon. +For the moment you can serve it locally in few steps: + +1. Get MkDocs and the used theme (once only, of course) + ``` + pip install mkdocs + pip install mkdocs-material + ``` +2. Run `mkdocs serve` from the top-level folder of this repository in your terminal +3. Open up [http://127.0.0.1:8000/](http://127.0.0.1:8000/) in your favorite browser From 1daf3db1a7d96e26eff2fc31b15179fbe2e780ea Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 24 Jan 2024 16:33:38 +0100 Subject: [PATCH 299/549] Update Python requirement and remove SMASH-analysis one --- docs/user/prerequisites.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/user/prerequisites.md b/docs/user/prerequisites.md index 9fe28eb..8bb0966 100644 --- a/docs/user/prerequisites.md +++ b/docs/user/prerequisites.md @@ -8,10 +8,7 @@ | [vHLLE](https://github.com/yukarpenko/vhlle) | - | | [vHLLE parameters](https://github.com/yukarpenko/vhlle_params) | - | | [Hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) | 1.0 or higher | -| [Python](https://www.python.org) | 2.7 or higher | -| [SMASH-analysis](https://github.com/smash-transport/smash-analysis)[^1] | 1.7 or higher | - -[^1]: `SMASH-analysis` is needed only if automatic generation of particle spectra is desired. +| [Python](https://www.python.org) | 3.0 or higher | Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. From 9dda9ce4863704ff84a3d345b408761a919cdd92 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Mon, 22 Jan 2024 18:02:44 +0100 Subject: [PATCH 300/549] Add global variables for config section copy --- bash/Afterburner_functionality.bash | 1 + bash/Hydro_functionality.bash | 1 + bash/IC_functionality.bash | 1 + bash/Sampler_functionality.bash | 1 + bash/configuration_parser.bash | 1 + bash/global_variables.bash | 12 ++++++++++++ bash/software_input_functionality.bash | 5 +++++ 7 files changed, 22 insertions(+) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index ca59c00..1fdc9b9 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -55,6 +55,7 @@ function Prepare_Software_Input_File_Afterburner() --emph "${HYBRID_software_input_file[Afterburner]}" ' to be used.' fi fi + Copy_Hybrid_Handler_Config_Section } function Ensure_All_Needed_Input_Exists_Afterburner() diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 9bbdba3..4bc61a1 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -64,6 +64,7 @@ function Prepare_Software_Input_File_Hydro() else ln -s "${eos_folder}" "${link_to_eos_folder}" fi + Copy_Hybrid_Handler_Config_Section } function Ensure_All_Needed_Input_Exists_Hydro() diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index d36db90..9343416 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -23,6 +23,7 @@ function Prepare_Software_Input_File_IC() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'YAML' "${HYBRID_software_configuration_file[IC]}" "${HYBRID_software_new_input_keys[IC]}" fi + Copy_Hybrid_Handler_Config_Section } function Ensure_All_Needed_Input_Exists_IC() diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 84c6f55..2b09a69 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -46,6 +46,7 @@ function Prepare_Software_Input_File_Sampler() ln -s "${freezeout_path}" \ "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" fi + Copy_Hybrid_Handler_Config_Section } function Ensure_All_Needed_Input_Exists_Sampler() diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index fba1b36..f0862a4 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -147,6 +147,7 @@ function __static__Parse_Section() local yaml_section valid_key if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" + HYBRID_yaml_section["${section_label}"] <<< "${yaml_section}" if [[ ${section_label} != 'Hybrid_handler' ]]; then HYBRID_given_software_sections+=("${section_label}") fi diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 31ca69d..5b881c7 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -44,6 +44,12 @@ function Define_Further_Global_Variables() [Sampler]='sampler_config.txt' [Afterburner]='afterburner_config.yaml' ) + declare -rgA HYBRID_handler_config_section_filename=( + [IC]='Hybrid_handler_IC_config.yaml' + [Hydro]='Hybrid_handler_Hydro_config.yaml' + [Sampler]='Hybrid_handler_Sampler_config.yaml' + [Afterburner]='Hybrid_handler_Afterburner_config.yaml' + ) # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=( @@ -111,6 +117,12 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) + declare -gA HYBRID_yaml_section=( + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' + ) declare -gA HYBRID_optional_feature=( [Add_spectators_from_IC]='FALSE' [Spectators_source]='' diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 15c19eb..842797d 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -99,4 +99,9 @@ function __static__Replace_Keys_Into_Txt_File() awk -i inplace 'BEGIN{FS=":"}{printf("%-20s%s\n", $1, $2)}' "${base_input_file}" } +function Copy_Hybrid_Handler_Config_Section() +{ + +} + Make_Functions_Defined_In_This_File_Readonly From ffecd56a7fac6b47122ad4d9ddd91e62e72e5ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 23 Jan 2024 11:32:41 +0100 Subject: [PATCH 301/549] Copy of raw config succeeded --- bash/Afterburner_functionality.bash | 2 +- bash/Hydro_functionality.bash | 2 +- bash/IC_functionality.bash | 2 +- bash/Sampler_functionality.bash | 2 +- bash/configuration_parser.bash | 2 +- bash/software_input_functionality.bash | 6 +++++- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 1fdc9b9..87d3521 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -55,7 +55,6 @@ function Prepare_Software_Input_File_Afterburner() --emph "${HYBRID_software_input_file[Afterburner]}" ' to be used.' fi fi - Copy_Hybrid_Handler_Config_Section } function Ensure_All_Needed_Input_Exists_Afterburner() @@ -82,6 +81,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() function Run_Software_Afterburner() { + Copy_Hybrid_Handler_Config_Section "Afterburner" "${HYBRID_software_output_directory[Afterburner]}" cd "${HYBRID_software_output_directory[Afterburner]}" local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" "${HYBRID_software_executable[Afterburner]}" \ diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 4bc61a1..a07be60 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -64,7 +64,6 @@ function Prepare_Software_Input_File_Hydro() else ln -s "${eos_folder}" "${link_to_eos_folder}" fi - Copy_Hybrid_Handler_Config_Section } function Ensure_All_Needed_Input_Exists_Hydro() @@ -89,6 +88,7 @@ function Ensure_All_Needed_Input_Exists_Hydro() function Run_Software_Hydro() { + Copy_Hybrid_Handler_Config_Section "Hydro" "${HYBRID_software_output_directory[Hydro]}" cd "${HYBRID_software_output_directory[Hydro]}" local -r \ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 9343416..f5d84df 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -23,7 +23,6 @@ function Prepare_Software_Input_File_IC() Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ 'YAML' "${HYBRID_software_configuration_file[IC]}" "${HYBRID_software_new_input_keys[IC]}" fi - Copy_Hybrid_Handler_Config_Section } function Ensure_All_Needed_Input_Exists_IC() @@ -40,6 +39,7 @@ function Ensure_All_Needed_Input_Exists_IC() function Run_Software_IC() { + Copy_Hybrid_Handler_Config_Section "IC" "${HYBRID_software_output_directory[IC]}" cd "${HYBRID_software_output_directory[IC]}" local ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" "${HYBRID_software_executable[IC]}" \ diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 2b09a69..27c9222 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -46,7 +46,6 @@ function Prepare_Software_Input_File_Sampler() ln -s "${freezeout_path}" \ "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" fi - Copy_Hybrid_Handler_Config_Section } function Ensure_All_Needed_Input_Exists_Sampler() @@ -71,6 +70,7 @@ function Ensure_All_Needed_Input_Exists_Sampler() function Run_Software_Sampler() { + Copy_Hybrid_Handler_Config_Section "Sampler" "${HYBRID_software_output_directory[Sampler]}" local -r sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" cd "${HYBRID_software_output_directory[Sampler]}" diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index f0862a4..1d9cd83 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -147,7 +147,7 @@ function __static__Parse_Section() local yaml_section valid_key if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" - HYBRID_yaml_section["${section_label}"] <<< "${yaml_section}" + HYBRID_yaml_section["${section_label}"]="${yaml_section}" if [[ ${section_label} != 'Hybrid_handler' ]]; then HYBRID_given_software_sections+=("${section_label}") fi diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 842797d..b7e0a37 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -101,7 +101,11 @@ function __static__Replace_Keys_Into_Txt_File() function Copy_Hybrid_Handler_Config_Section() { - + section=$1 + folder=$2 + printf "%s" "${HYBRID_yaml_section["${section}"]}" > "${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" + #touch "${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" + #"${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" << ${HYBRID_yaml_section["${section}"]} } Make_Functions_Defined_In_This_File_Readonly From bef47677f5cea218b2095864ebf1a24e2c36fe4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 23 Jan 2024 12:29:32 +0100 Subject: [PATCH 302/549] Remove unneeded global variables, add git describe --- bash/Afterburner_functionality.bash | 3 ++- bash/Hydro_functionality.bash | 3 ++- bash/IC_functionality.bash | 3 ++- bash/Sampler_functionality.bash | 3 ++- bash/configuration_parser.bash | 1 - bash/global_variables.bash | 6 ------ bash/software_input_functionality.bash | 11 ++++++++--- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 87d3521..6bdfd90 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -81,7 +81,8 @@ function Ensure_All_Needed_Input_Exists_Afterburner() function Run_Software_Afterburner() { - Copy_Hybrid_Handler_Config_Section "Afterburner" "${HYBRID_software_output_directory[Afterburner]}" + Copy_Hybrid_Handler_Config_Section "Afterburner" "${HYBRID_software_output_directory[Afterburner]}" \ + "$(dirname "${HYBRID_software_executable[Afterburner]}")" cd "${HYBRID_software_output_directory[Afterburner]}" local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" "${HYBRID_software_executable[Afterburner]}" \ diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index a07be60..8ba8577 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -88,7 +88,8 @@ function Ensure_All_Needed_Input_Exists_Hydro() function Run_Software_Hydro() { - Copy_Hybrid_Handler_Config_Section "Hydro" "${HYBRID_software_output_directory[Hydro]}" + Copy_Hybrid_Handler_Config_Section "Hydro" "${HYBRID_software_output_directory[Hydro]}" \ + "$(dirname "${HYBRID_software_executable[Hydro]}")" cd "${HYBRID_software_output_directory[Hydro]}" local -r \ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index f5d84df..683aec1 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -39,7 +39,8 @@ function Ensure_All_Needed_Input_Exists_IC() function Run_Software_IC() { - Copy_Hybrid_Handler_Config_Section "IC" "${HYBRID_software_output_directory[IC]}" + Copy_Hybrid_Handler_Config_Section "IC" "${HYBRID_software_output_directory[IC]}" \ + "$(dirname "${HYBRID_software_executable[IC]}")" cd "${HYBRID_software_output_directory[IC]}" local ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" "${HYBRID_software_executable[IC]}" \ diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 27c9222..a81e56b 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -70,7 +70,8 @@ function Ensure_All_Needed_Input_Exists_Sampler() function Run_Software_Sampler() { - Copy_Hybrid_Handler_Config_Section "Sampler" "${HYBRID_software_output_directory[Sampler]}" + Copy_Hybrid_Handler_Config_Section "Sampler" "${HYBRID_software_output_directory[Sampler]}" \ + "$(dirname "${HYBRID_software_executable[Sampler]}")" local -r sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" cd "${HYBRID_software_output_directory[Sampler]}" diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index 1d9cd83..fba1b36 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -147,7 +147,6 @@ function __static__Parse_Section() local yaml_section valid_key if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" - HYBRID_yaml_section["${section_label}"]="${yaml_section}" if [[ ${section_label} != 'Hybrid_handler' ]]; then HYBRID_given_software_sections+=("${section_label}") fi diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 5b881c7..0d1f1a1 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -117,12 +117,6 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) - declare -gA HYBRID_yaml_section=( - [IC]='' - [Hydro]='' - [Sampler]='' - [Afterburner]='' - ) declare -gA HYBRID_optional_feature=( [Add_spectators_from_IC]='FALSE' [Spectators_source]='' diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index b7e0a37..773a53b 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -103,9 +103,14 @@ function Copy_Hybrid_Handler_Config_Section() { section=$1 folder=$2 - printf "%s" "${HYBRID_yaml_section["${section}"]}" > "${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" - #touch "${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" - #"${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" << ${HYBRID_yaml_section["${section}"]} + executable_folder=$3 + git_describe_executable=$(git -C "${executable_folder}" describe --long --always --all) + line_git_describe_executable='# Git describe of executable folder: '"${git_describe_executable}" + git_describe_handler=$(git -C "${HYBRID_top_level_path}" describe --long --always --all) + line_git_describe_handler='# Git describe of handler folder: '"${git_describe_handler}" + section_config=$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${section}"'|)")))' "${HYBRID_configuration_file}") + printf "%s\n%s\n%s" "${line_git_describe_executable}" "${line_git_describe_handler}" \ + "${section_config}" > "${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" } Make_Functions_Defined_In_This_File_Readonly From 1ff7ff16c43dc3bba7534759834e48123a7332ef Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Wed, 24 Jan 2024 10:21:57 +0100 Subject: [PATCH 303/549] Adapt existing tests --- bash/software_input_functionality.bash | 3 ++- tests/functional_tests_Sampler_only.bash | 2 +- tests/functional_tests_full_workflow.bash | 2 +- tests/unit_tests_Afterburner_functionality.bash | 1 + tests/unit_tests_Hydro_functionality.bash | 1 + tests/unit_tests_IC_functionality.bash | 1 + tests/unit_tests_Sampler_functionality.bash | 1 + tests/utility_functions_functional.bash | 6 +++--- 8 files changed, 11 insertions(+), 6 deletions(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 773a53b..53db2f7 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -108,7 +108,8 @@ function Copy_Hybrid_Handler_Config_Section() line_git_describe_executable='# Git describe of executable folder: '"${git_describe_executable}" git_describe_handler=$(git -C "${HYBRID_top_level_path}" describe --long --always --all) line_git_describe_handler='# Git describe of handler folder: '"${git_describe_handler}" - section_config=$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${section}"'|)")))' "${HYBRID_configuration_file}") + section_config=$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${section}"'|)")))'\ + "${HYBRID_configuration_file}") printf "%s\n%s\n%s" "${line_git_describe_executable}" "${line_git_describe_handler}" \ "${section_config}" > "${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" } diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 069d651..13f5e74 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -11,7 +11,7 @@ function Functional_Test__do-Sampler-only() { shopt -s nullglob local -r \ - hybrid_handler_config='hybrid_config' \ + hybrid_handler_config="${HYBRIDT_tests_folder}/hybrid_config" \ run_id='Sampler_only' local output_files mkdir -p "Hydro/${run_id}" diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash index 9e1e9fd..6ba10db 100644 --- a/tests/functional_tests_full_workflow.bash +++ b/tests/functional_tests_full_workflow.bash @@ -21,7 +21,7 @@ function __static__Test_Full_Workflow() { shopt -s nullglob local -r \ - config_filename='Handler_config.yaml' \ + config_filename="${HYBRIDT_tests_folder}/Handler_config.yaml" \ mocks_folder="${HYBRIDT_tests_folder}/mocks" __static__Prepare_Full_Handler_Configuration_File "$1" __static__Create_Auxiliaries_For_Hydro diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index c709e71..eaf6b72 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -182,6 +182,7 @@ function Make_Test_Preliminary_Operations__Afterburner-test-run-software() function Unit_Test__Afterburner-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Afterburner]}" + touch ${HYBRID_configuration_file} local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Afterburner diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index e198d7d..ed64b8c 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -154,6 +154,7 @@ function Make_Test_Preliminary_Operations__Hydro-test-run-software() function Unit_Test__Hydro-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" + touch ${HYBRID_configuration_file} local -r \ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" \ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 2085b99..8a4467a 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -93,6 +93,7 @@ function Make_Test_Preliminary_Operations__IC-test-run-software() function Unit_Test__IC-test-run-software() { local -r ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" + touch ${HYBRID_configuration_file} mkdir -p "${HYBRID_software_output_directory[IC]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_IC diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 774ed52..5a66bf9 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -212,6 +212,7 @@ function Make_Test_Preliminary_Operations__Sampler-test-run-software() function Unit_Test__Sampler-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Sampler]}" + touch ${HYBRID_configuration_file} local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" \ sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local terminal_output_result correct_result diff --git a/tests/utility_functions_functional.bash b/tests/utility_functions_functional.bash index dd52286..71d17f7 100644 --- a/tests/utility_functions_functional.bash +++ b/tests/utility_functions_functional.bash @@ -13,13 +13,13 @@ function Check_If_Software_Produced_Expected_Output() local expected_output_files case "${block}" in IC | Hydro) - expected_output_files=5 + expected_output_files=6 ;; Sampler) - expected_output_files=4 + expected_output_files=5 ;; Afterburner) - expected_output_files=6 + expected_output_files=7 ;; *) Print_Internal_And_Exit 'Invalid case branch entered in ' --emph "${FUNCNAME}." From 3ae99acae2962a5c6ca67d8a62f87872dc389813 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Wed, 24 Jan 2024 12:59:34 +0100 Subject: [PATCH 304/549] Add unit test for copy config --- bash/Hydro_functionality.bash | 2 +- bash/software_input_functionality.bash | 8 ++-- ...it_tests_software_input_functionality.bash | 43 +++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 8ba8577..a828d6f 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -89,7 +89,7 @@ function Ensure_All_Needed_Input_Exists_Hydro() function Run_Software_Hydro() { Copy_Hybrid_Handler_Config_Section "Hydro" "${HYBRID_software_output_directory[Hydro]}" \ - "$(dirname "${HYBRID_software_executable[Hydro]}")" + "$(dirname "${HYBRID_software_executable[Hydro]}")" cd "${HYBRID_software_output_directory[Hydro]}" local -r \ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 53db2f7..dad2fd2 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -104,12 +104,12 @@ function Copy_Hybrid_Handler_Config_Section() section=$1 folder=$2 executable_folder=$3 - git_describe_executable=$(git -C "${executable_folder}" describe --long --always --all) + git_describe_executable=$(git -C "${executable_folder}" describe --long --always --all) line_git_describe_executable='# Git describe of executable folder: '"${git_describe_executable}" - git_describe_handler=$(git -C "${HYBRID_top_level_path}" describe --long --always --all) + git_describe_handler=$(git -C "${HYBRID_top_level_path}" describe --long --always --all) line_git_describe_handler='# Git describe of handler folder: '"${git_describe_handler}" - section_config=$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${section}"'|)")))'\ - "${HYBRID_configuration_file}") + section_config=$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${section}"')")))' \ + "${HYBRID_configuration_file}") printf "%s\n%s\n%s" "${line_git_describe_executable}" "${line_git_describe_handler}" \ "${section_config}" > "${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" } diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 0c8e260..93d4533 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -182,3 +182,46 @@ function Unit_Test__replace-in-software-input-TXT() fi rm "${base_input_file}" } + +function Make_Test_Preliminary_Operations__copy-hybrid-handler-config-section() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'global_variables.bash' + 'software_input_functionality.bash' + 'sanity_checks.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables +} + +function Unit_Test__copy-hybrid-handler-config-section() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + printf 'Hybrid_handler:\n Test: test\nIC:\n' > "${HYBRID_configuration_file}" + printf ' Executable: ex\n Config_file: conf\n' >> "${HYBRID_configuration_file}" + printf 'Hydro:\n Executable: exh\n Config_file: confh\n' >> "${HYBRID_configuration_file}" + Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ + "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_folder_to_run_tests}" &> /dev/null + executable_folder=${HYBRIDT_folder_to_run_tests} + printf -v line1 '# Git describe of executable folder: %s\n' \ + "$(git -C "${executable_folder}" describe --long --always --all)" + printf -v line2 "# Git describe of handler folder: %s\n" \ + "$(git -C "${executable_folder}" describe --long --always --all)" + printf -v section1 'Hybrid_handler:\n Test: test\n' + printf -v section2 'IC:\n Executable: ex\n Config_file: conf' + printf -v expected_result '%s%s%s%s' \ + "${line1}" "${line2}" "${section1}" "${section2}" + echo "$(< "${HYBRID_handler_config_section_filename[IC]}")" + echo "leololeo\n\n" + echo "${expected_result}" + if [[ "$(< "${HYBRID_handler_config_section_filename[IC]}")" != "${expected_result}" ]]; then + Print_Error \ + "Copying of relevant handler config section failed!" \ + '-------------------' + return 1 + fi + rm "${HYBRID_handler_config_section_filename[IC]}" +} From 9c79082dc52cb08d7e231a74f197308be92683a8 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Thu, 25 Jan 2024 13:53:43 +0100 Subject: [PATCH 305/549] Create separate function for copy_config and adapt tests accordingly --- Hybrid-handler | 1 + bash/Afterburner_functionality.bash | 8 +++- bash/Hydro_functionality.bash | 8 +++- bash/IC_functionality.bash | 8 +++- bash/Sampler_functionality.bash | 8 +++- bash/dispatch_functions.bash | 5 +++ bash/software_input_functionality.bash | 37 ++++++++++++++----- tests/functional_tests_Sampler_only.bash | 2 +- tests/functional_tests_full_workflow.bash | 2 +- .../unit_tests_Afterburner_functionality.bash | 1 - tests/unit_tests_Hydro_functionality.bash | 1 - tests/unit_tests_IC_functionality.bash | 1 - tests/unit_tests_Sampler_functionality.bash | 1 - ...it_tests_software_input_functionality.bash | 35 +++++++++--------- 14 files changed, 77 insertions(+), 41 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index 3206c48..c2a1a4f 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -116,6 +116,7 @@ function Make_Needed_Operations_Depending_On_Execution_Mode() for software_section in "${HYBRID_given_software_sections[@]}"; do Prepare_Software_Input_File "${software_section}" Ensure_All_Needed_Input_Exists "${software_section}" + Ensure_Run_Reproducibility "${software_section}" Run_Software "${software_section}" done ;; diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 6bdfd90..1796659 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -79,10 +79,14 @@ function Ensure_All_Needed_Input_Exists_Afterburner() fi } -function Run_Software_Afterburner() +function Ensure_Run_Reproducibility_Afterburner() { Copy_Hybrid_Handler_Config_Section "Afterburner" "${HYBRID_software_output_directory[Afterburner]}" \ - "$(dirname "${HYBRID_software_executable[Afterburner]}")" + "$(dirname "$(realpath "${HYBRID_software_executable[Afterburner]}")")" +} + +function Run_Software_Afterburner() +{ cd "${HYBRID_software_output_directory[Afterburner]}" local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" "${HYBRID_software_executable[Afterburner]}" \ diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index a828d6f..8c841fb 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -86,10 +86,14 @@ function Ensure_All_Needed_Input_Exists_Hydro() fi } -function Run_Software_Hydro() +function Ensure_Run_Reproducibility_Hydro() { Copy_Hybrid_Handler_Config_Section "Hydro" "${HYBRID_software_output_directory[Hydro]}" \ - "$(dirname "${HYBRID_software_executable[Hydro]}")" + "$(dirname "$(realpath "${HYBRID_software_executable[Hydro]}")")" +} + +function Run_Software_Hydro() +{ cd "${HYBRID_software_output_directory[Hydro]}" local -r \ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 683aec1..6a95ef1 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -37,10 +37,14 @@ function Ensure_All_Needed_Input_Exists_IC() fi } -function Run_Software_IC() +function Ensure_Run_Reproducibility_IC() { Copy_Hybrid_Handler_Config_Section "IC" "${HYBRID_software_output_directory[IC]}" \ - "$(dirname "${HYBRID_software_executable[IC]}")" + "$(dirname "$(realpath "${HYBRID_software_executable[IC]}")")" +} + +function Run_Software_IC() +{ cd "${HYBRID_software_output_directory[IC]}" local ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" "${HYBRID_software_executable[IC]}" \ diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index a81e56b..994759c 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -68,10 +68,14 @@ function Ensure_All_Needed_Input_Exists_Sampler() fi } -function Run_Software_Sampler() +function Ensure_Run_Reproducibility_Sampler() { Copy_Hybrid_Handler_Config_Section "Sampler" "${HYBRID_software_output_directory[Sampler]}" \ - "$(dirname "${HYBRID_software_executable[Sampler]}")" + "$(dirname "$(realpath "${HYBRID_software_executable[Sampler]}")")" +} + +function Run_Software_Sampler() +{ local -r sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" cd "${HYBRID_software_output_directory[Sampler]}" diff --git a/bash/dispatch_functions.bash b/bash/dispatch_functions.bash index ffcc256..34428c4 100644 --- a/bash/dispatch_functions.bash +++ b/bash/dispatch_functions.bash @@ -17,6 +17,11 @@ function Ensure_All_Needed_Input_Exists() Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } +function Ensure_Run_Reproducibility() +{ + Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" +} + function Run_Software() { Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index dad2fd2..62358fb 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -99,19 +99,36 @@ function __static__Replace_Keys_Into_Txt_File() awk -i inplace 'BEGIN{FS=":"}{printf("%-20s%s\n", $1, $2)}' "${base_input_file}" } +function __static__Extract_Sections_From_Configuration_File() +{ + printf "%s" "$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' "${HYBRID_configuration_file}")" +} + +function __static__Get_Repository_State() +{ + git_call=$(git -C "${1}" describe --long --always --all) + if [[ $git_call == *"fatal"* ]]; then + printf 'Not a Git repository' + else + printf "%s" ${git_call} + fi +} + function Copy_Hybrid_Handler_Config_Section() { - section=$1 - folder=$2 + local -r \ + section=$1 \ + output_file="$2/${HYBRID_handler_config_section_filename[$1]}" executable_folder=$3 - git_describe_executable=$(git -C "${executable_folder}" describe --long --always --all) - line_git_describe_executable='# Git describe of executable folder: '"${git_describe_executable}" - git_describe_handler=$(git -C "${HYBRID_top_level_path}" describe --long --always --all) - line_git_describe_handler='# Git describe of handler folder: '"${git_describe_handler}" - section_config=$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${section}"')")))' \ - "${HYBRID_configuration_file}") - printf "%s\n%s\n%s" "${line_git_describe_executable}" "${line_git_describe_handler}" \ - "${section_config}" > "${folder}"/"${HYBRID_handler_config_section_filename["${section}"]}" + if [[ -f "${output_file}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'The config copy ' --emph "${output_file}" \ + ' already exists.' + fi + printf '%b' \ + "# Git describe of executable folder: $(__static__Get_Repository_State "${executable_folder}")\n\n" \ + "# Git describe of handler folder: $(__static__Get_Repository_State "${HYBRID_top_level_path}")\n\n" \ + "$(__static__Extract_Sections_From_Configuration_File "${section}")" > "${output_file}" } Make_Functions_Defined_In_This_File_Readonly diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 13f5e74..847b75b 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -11,7 +11,7 @@ function Functional_Test__do-Sampler-only() { shopt -s nullglob local -r \ - hybrid_handler_config="${HYBRIDT_tests_folder}/hybrid_config" \ + hybrid_handler_config="hybrid_config" \ run_id='Sampler_only' local output_files mkdir -p "Hydro/${run_id}" diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash index 6ba10db..e2e2faf 100644 --- a/tests/functional_tests_full_workflow.bash +++ b/tests/functional_tests_full_workflow.bash @@ -21,7 +21,7 @@ function __static__Test_Full_Workflow() { shopt -s nullglob local -r \ - config_filename="${HYBRIDT_tests_folder}/Handler_config.yaml" \ + config_filename="Handler_config.yaml" \ mocks_folder="${HYBRIDT_tests_folder}/mocks" __static__Prepare_Full_Handler_Configuration_File "$1" __static__Create_Auxiliaries_For_Hydro diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index eaf6b72..c709e71 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -182,7 +182,6 @@ function Make_Test_Preliminary_Operations__Afterburner-test-run-software() function Unit_Test__Afterburner-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Afterburner]}" - touch ${HYBRID_configuration_file} local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Afterburner diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index ed64b8c..e198d7d 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -154,7 +154,6 @@ function Make_Test_Preliminary_Operations__Hydro-test-run-software() function Unit_Test__Hydro-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" - touch ${HYBRID_configuration_file} local -r \ hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" \ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 8a4467a..2085b99 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -93,7 +93,6 @@ function Make_Test_Preliminary_Operations__IC-test-run-software() function Unit_Test__IC-test-run-software() { local -r ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" - touch ${HYBRID_configuration_file} mkdir -p "${HYBRID_software_output_directory[IC]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_IC diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 5a66bf9..774ed52 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -212,7 +212,6 @@ function Make_Test_Preliminary_Operations__Sampler-test-run-software() function Unit_Test__Sampler-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Sampler]}" - touch ${HYBRID_configuration_file} local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" \ sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local terminal_output_result correct_result diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 93d4533..4de2555 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -200,27 +200,28 @@ function Make_Test_Preliminary_Operations__copy-hybrid-handler-config-section() function Unit_Test__copy-hybrid-handler-config-section() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml - printf 'Hybrid_handler:\n Test: test\nIC:\n' > "${HYBRID_configuration_file}" - printf ' Executable: ex\n Config_file: conf\n' >> "${HYBRID_configuration_file}" - printf 'Hydro:\n Executable: exh\n Config_file: confh\n' >> "${HYBRID_configuration_file}" + printf 'Hybrid_handler: + Run_ID: test +IC: + Executable: ex + Config_file: conf +Hydro: + Executable: exh + Config_file: confh' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_folder_to_run_tests}" &> /dev/null - executable_folder=${HYBRIDT_folder_to_run_tests} - printf -v line1 '# Git describe of executable folder: %s\n' \ - "$(git -C "${executable_folder}" describe --long --always --all)" - printf -v line2 "# Git describe of handler folder: %s\n" \ - "$(git -C "${executable_folder}" describe --long --always --all)" - printf -v section1 'Hybrid_handler:\n Test: test\n' - printf -v section2 'IC:\n Executable: ex\n Config_file: conf' - printf -v expected_result '%s%s%s%s' \ - "${line1}" "${line2}" "${section1}" "${section2}" - echo "$(< "${HYBRID_handler_config_section_filename[IC]}")" - echo "leololeo\n\n" - echo "${expected_result}" + local -r description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" + printf -v expected_result '%b' \ + "# Git describe of executable folder: ${description}\n\n" \ + "# Git describe of handler folder: ${description}\n\n" \ + 'Hybrid_handler:\n' \ + ' Run_ID: test\n' \ + 'IC:\n' \ + ' Executable: ex\n' \ + ' Config_file: conf' if [[ "$(< "${HYBRID_handler_config_section_filename[IC]}")" != "${expected_result}" ]]; then Print_Error \ - "Copying of relevant handler config section failed!" \ - '-------------------' + "Copying of relevant handler config section failed!" return 1 fi rm "${HYBRID_handler_config_section_filename[IC]}" From cdab89f42f1f1ffce37893023500852a7c973587 Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Thu, 25 Jan 2024 14:00:37 +0100 Subject: [PATCH 306/549] Fix format --- bash/software_input_functionality.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 62358fb..300df50 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -101,7 +101,8 @@ function __static__Replace_Keys_Into_Txt_File() function __static__Extract_Sections_From_Configuration_File() { - printf "%s" "$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' "${HYBRID_configuration_file}")" + printf "%s" "$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' \ + "${HYBRID_configuration_file}")" } function __static__Get_Repository_State() From 9c4bdb9b06abe99b45a5cb5f07c1c76a28c60c2a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 Jan 2024 15:50:20 +0100 Subject: [PATCH 307/549] Purely aesthetical improvements --- bash/Afterburner_functionality.bash | 5 ++-- bash/Hydro_functionality.bash | 5 ++-- bash/IC_functionality.bash | 5 ++-- bash/Sampler_functionality.bash | 5 ++-- bash/software_input_functionality.bash | 10 +++---- tests/functional_tests_Afterburner_only.bash | 8 ++--- tests/functional_tests_Hydro_only.bash | 8 ++--- tests/functional_tests_IC_only.bash | 4 +-- tests/functional_tests_Sampler_only.bash | 6 ++-- tests/functional_tests_full_workflow.bash | 4 +-- ...it_tests_software_input_functionality.bash | 29 +++++++++++-------- 11 files changed, 49 insertions(+), 40 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 1796659..d6f326a 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -81,7 +81,8 @@ function Ensure_All_Needed_Input_Exists_Afterburner() function Ensure_Run_Reproducibility_Afterburner() { - Copy_Hybrid_Handler_Config_Section "Afterburner" "${HYBRID_software_output_directory[Afterburner]}" \ + Copy_Hybrid_Handler_Config_Section 'Afterburner' \ + "${HYBRID_software_output_directory[Afterburner]}" \ "$(dirname "$(realpath "${HYBRID_software_executable[Afterburner]}")")" } diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 8c841fb..35de527 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -88,7 +88,8 @@ function Ensure_All_Needed_Input_Exists_Hydro() function Ensure_Run_Reproducibility_Hydro() { - Copy_Hybrid_Handler_Config_Section "Hydro" "${HYBRID_software_output_directory[Hydro]}" \ + Copy_Hybrid_Handler_Config_Section 'Hydro' \ + "${HYBRID_software_output_directory[Hydro]}" \ "$(dirname "$(realpath "${HYBRID_software_executable[Hydro]}")")" } diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 6a95ef1..49eba78 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -39,7 +39,8 @@ function Ensure_All_Needed_Input_Exists_IC() function Ensure_Run_Reproducibility_IC() { - Copy_Hybrid_Handler_Config_Section "IC" "${HYBRID_software_output_directory[IC]}" \ + Copy_Hybrid_Handler_Config_Section 'IC' \ + "${HYBRID_software_output_directory[IC]}" \ "$(dirname "$(realpath "${HYBRID_software_executable[IC]}")")" } diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 994759c..b32aab2 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -70,7 +70,8 @@ function Ensure_All_Needed_Input_Exists_Sampler() function Ensure_Run_Reproducibility_Sampler() { - Copy_Hybrid_Handler_Config_Section "Sampler" "${HYBRID_software_output_directory[Sampler]}" \ + Copy_Hybrid_Handler_Config_Section 'Sampler' \ + "${HYBRID_software_output_directory[Sampler]}" \ "$(dirname "$(realpath "${HYBRID_software_executable[Sampler]}")")" } diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 300df50..a48d3c7 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -101,7 +101,7 @@ function __static__Replace_Keys_Into_Txt_File() function __static__Extract_Sections_From_Configuration_File() { - printf "%s" "$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' \ + printf '%s' "$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' \ "${HYBRID_configuration_file}")" } @@ -126,9 +126,9 @@ function Copy_Hybrid_Handler_Config_Section() 'The config copy ' --emph "${output_file}" \ ' already exists.' fi - printf '%b' \ - "# Git describe of executable folder: $(__static__Get_Repository_State "${executable_folder}")\n\n" \ - "# Git describe of handler folder: $(__static__Get_Repository_State "${HYBRID_top_level_path}")\n\n" \ + printf '%s\n\n' \ + "# Git describe of executable folder: $(__static__Get_Repository_State "${executable_folder}")" \ + "# Git describe of handler folder: $(__static__Get_Repository_State "${HYBRID_top_level_path}")" \ "$(__static__Extract_Sections_From_Configuration_File "${section}")" > "${output_file}" } diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 94b12f9..b36433d 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -28,7 +28,7 @@ function Functional_Test__do-Afterburner-only() mkdir -p "Sampler/${run_id}" touch "Sampler/${run_id}/particle_lists.oscar" printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py @@ -94,7 +94,7 @@ function Functional_Test__do-Afterburner-only() mv 'Afterburner' 'Afterburner-success-custom-input' # Expect failure when using custom input while also running the sampler printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s Sampler: Executable: echo @@ -117,7 +117,7 @@ function Functional_Test__do-Afterburner-only() mkdir -p "IC/${run_id}" touch "IC/${run_id}/"{config.yaml,SMASH_IC.oscar} "Sampler/${run_id}/particle_lists.oscar" printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py @@ -136,7 +136,7 @@ function Functional_Test__do-Afterburner-only() mkdir -p test "IC/${run_id}" touch 'test/SMASH_IC_2.oscar' "IC/${run_id}/config.yaml" printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s Afterburner: Executable: %s/mocks/smash_afterburner_black-box.py diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 2fb0f2f..d940607 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -18,7 +18,7 @@ function Functional_Test__do-Hydro-only() ln -s "${HYBRIDT_repository_top_level_path}/tests/mocks/vhlle_black-box.py" "vhlle_black-box.py" mkdir 'eos' printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s Hydro: Executable: %s/vhlle_black-box.py @@ -54,7 +54,7 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-invalid-input' #Expect success with custom input file name printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s Hydro: Executable: %s/vhlle_black-box.py @@ -106,7 +106,7 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-crash' #Expect failure with custom input file name while also using IC printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s IC: Executable: echo diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index 7536016..d5781af 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -15,7 +15,7 @@ function Functional_Test__do-IC-only() run_id='IC_only' local unfinished_files output_files terminal_output_file failure_message printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s IC: Executable: %s/tests/mocks/smash_IC_black-box.py diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 847b75b..c0c9823 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -11,13 +11,13 @@ function Functional_Test__do-Sampler-only() { shopt -s nullglob local -r \ - hybrid_handler_config="hybrid_config" \ + hybrid_handler_config='hybrid_config' \ run_id='Sampler_only' local output_files mkdir -p "Hydro/${run_id}" touch "Hydro/${run_id}/freezeout.dat" printf ' - Hybrid_handler: + Hybrid_handler: Run_ID: %s Sampler: Executable: %s/tests/mocks/sampler_black-box.py diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash index e2e2faf..c06c103 100644 --- a/tests/functional_tests_full_workflow.bash +++ b/tests/functional_tests_full_workflow.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -21,7 +21,7 @@ function __static__Test_Full_Workflow() { shopt -s nullglob local -r \ - config_filename="Handler_config.yaml" \ + config_filename='Handler_config.yaml' \ mocks_folder="${HYBRIDT_tests_folder}/mocks" __static__Prepare_Full_Handler_Configuration_File "$1" __static__Create_Auxiliaries_For_Hydro diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 4de2555..7caab6a 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -200,28 +200,33 @@ function Make_Test_Preliminary_Operations__copy-hybrid-handler-config-section() function Unit_Test__copy-hybrid-handler-config-section() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml - printf 'Hybrid_handler: - Run_ID: test -IC: - Executable: ex - Config_file: conf -Hydro: - Executable: exh - Config_file: confh' > "${HYBRID_configuration_file}" + printf ' + Hybrid_handler: + Run_ID: test + IC: + Executable: ex + Config_file: conf + Hydro: + Executable: exh + Config_file: confh + ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_folder_to_run_tests}" &> /dev/null local -r description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" printf -v expected_result '%b' \ "# Git describe of executable folder: ${description}\n\n" \ - "# Git describe of handler folder: ${description}\n\n" \ + "# Git describe of handler folder: ${description}\n\n\n" \ 'Hybrid_handler:\n' \ ' Run_ID: test\n' \ 'IC:\n' \ ' Executable: ex\n' \ - ' Config_file: conf' + ' Config_file: conf' # No trailing endline as "$(< ...)" strips them if [[ "$(< "${HYBRID_handler_config_section_filename[IC]}")" != "${expected_result}" ]]; then Print_Error \ - "Copying of relevant handler config section failed!" + "Copying of relevant handler config sections failed!" \ + "---- OBTAINED: ----\n$(< "${HYBRID_handler_config_section_filename[IC]}")" \ + "---- EXPECTED: ----\n${expected_result}" \ + '-------------------' return 1 fi rm "${HYBRID_handler_config_section_filename[IC]}" From 2f2fbffc295df5a0ab1930079e17805d09ba9b82 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 Jan 2024 16:06:39 +0100 Subject: [PATCH 308/549] Fix subtlity in spacing of strings compared literally --- ...it_tests_software_input_functionality.bash | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 7caab6a..f388844 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -200,22 +200,23 @@ function Make_Test_Preliminary_Operations__copy-hybrid-handler-config-section() function Unit_Test__copy-hybrid-handler-config-section() { HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml - printf ' - Hybrid_handler: - Run_ID: test - IC: - Executable: ex - Config_file: conf - Hydro: - Executable: exh - Config_file: confh - ' > "${HYBRID_configuration_file}" + # Avoid empty lines in the beginning in this test as yq behavior + # might change with different versions (here we compare strings) + printf '%s\n' \ + 'Hybrid_handler:' \ + ' Run_ID: test' \ + 'IC:' \ + ' Executable: ex' \ + ' Config_file: conf' \ + 'Hydro:' \ + ' Executable: exh' \ + ' Config_file: confh' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_folder_to_run_tests}" &> /dev/null local -r description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" printf -v expected_result '%b' \ "# Git describe of executable folder: ${description}\n\n" \ - "# Git describe of handler folder: ${description}\n\n\n" \ + "# Git describe of handler folder: ${description}\n\n" \ 'Hybrid_handler:\n' \ ' Run_ID: test\n' \ 'IC:\n' \ From d5d9caf1bfa96a0f8888290ea3ed19589c40f88c Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Thu, 25 Jan 2024 16:24:19 +0100 Subject: [PATCH 309/549] Fix error behaviour of git describe function --- bash/software_input_functionality.bash | 37 +++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index a48d3c7..2e80ad1 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -99,28 +99,12 @@ function __static__Replace_Keys_Into_Txt_File() awk -i inplace 'BEGIN{FS=":"}{printf("%-20s%s\n", $1, $2)}' "${base_input_file}" } -function __static__Extract_Sections_From_Configuration_File() -{ - printf '%s' "$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' \ - "${HYBRID_configuration_file}")" -} - -function __static__Get_Repository_State() -{ - git_call=$(git -C "${1}" describe --long --always --all) - if [[ $git_call == *"fatal"* ]]; then - printf 'Not a Git repository' - else - printf "%s" ${git_call} - fi -} - function Copy_Hybrid_Handler_Config_Section() { local -r \ section=$1 \ - output_file="$2/${HYBRID_handler_config_section_filename[$1]}" - executable_folder=$3 + output_file="$2/${HYBRID_handler_config_section_filename[$1]}" \ + executable_folder=$3 if [[ -f "${output_file}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'The config copy ' --emph "${output_file}" \ @@ -132,4 +116,21 @@ function Copy_Hybrid_Handler_Config_Section() "$(__static__Extract_Sections_From_Configuration_File "${section}")" > "${output_file}" } +function __static__Extract_Sections_From_Configuration_File() +{ + printf '%s' "$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' \ + "${HYBRID_configuration_file}")" +} + +function __static__Get_Repository_State() +{ + local code=0 + git_call=$(git -C "${1}" describe --long --always --all 2> /dev/null) || code=$? + if [[ ${code} -ne 0 ]]; then + printf 'Not a Git repository' + else + printf "%s" ${git_call} + fi +} + Make_Functions_Defined_In_This_File_Readonly From 50d14f1bdf0b4ee649dd75e5f9d758934a54d757 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 Jan 2024 16:45:05 +0100 Subject: [PATCH 310/549] Add test of reproducibility function with non-git folder --- ...it_tests_software_input_functionality.bash | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index f388844..32a7a3d 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -211,24 +211,32 @@ function Unit_Test__copy-hybrid-handler-config-section() 'Hydro:' \ ' Executable: exh' \ ' Config_file: confh' > "${HYBRID_configuration_file}" - Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ - "${HYBRIDT_folder_to_run_tests}" "${HYBRIDT_folder_to_run_tests}" &> /dev/null - local -r description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" - printf -v expected_result '%b' \ - "# Git describe of executable folder: ${description}\n\n" \ - "# Git describe of handler folder: ${description}\n\n" \ - 'Hybrid_handler:\n' \ - ' Run_ID: test\n' \ - 'IC:\n' \ - ' Executable: ex\n' \ - ' Config_file: conf' # No trailing endline as "$(< ...)" strips them - if [[ "$(< "${HYBRID_handler_config_section_filename[IC]}")" != "${expected_result}" ]]; then - Print_Error \ - "Copying of relevant handler config sections failed!" \ - "---- OBTAINED: ----\n$(< "${HYBRID_handler_config_section_filename[IC]}")" \ - "---- EXPECTED: ----\n${expected_result}" \ - '-------------------' - return 1 - fi - rm "${HYBRID_handler_config_section_filename[IC]}" + local -r git_description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" + local folder description + for folder in "${HYBRIDT_folder_to_run_tests}" ~; do + Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ + "${HYBRIDT_folder_to_run_tests}" "${folder}" #&> /dev/null + if [[ "${folder}" = "${HYBRIDT_folder_to_run_tests}" ]]; then + description="${git_description}" + else + description='Not a Git repository' + fi + printf -v expected_result '%b' \ + "# Git describe of executable folder: ${description}\n\n" \ + "# Git describe of handler folder: ${git_description}\n\n" \ + 'Hybrid_handler:\n' \ + ' Run_ID: test\n' \ + 'IC:\n' \ + ' Executable: ex\n' \ + ' Config_file: conf' # No trailing endline as "$(< ...)" strips them + if [[ "$(< "${HYBRID_handler_config_section_filename[IC]}")" != "${expected_result}" ]]; then + Print_Error \ + "Copying of relevant handler config sections failed!" \ + "---- OBTAINED: ----\n$(< "${HYBRID_handler_config_section_filename[IC]}")" \ + "---- EXPECTED: ----\n${expected_result}" \ + '-------------------' + return 1 + fi + rm "${HYBRID_handler_config_section_filename[IC]}" + done } From 3abf3e0ad7d3c9e533798673424e24f35c16d15c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 Jan 2024 17:00:47 +0100 Subject: [PATCH 311/549] Improve developer exectuion mode to format codebase --- CONTRIBUTING.md | 2 +- Hybrid-handler | 1 + bash/formatter.bash | 13 ++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b42a5f..2fa8d02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,7 +71,7 @@ In particular, be aware the the formatter will not enforce the rules explained b Before opening a PR, make sure all tests pass. One of them will try to check formatting and complain if something has to be adjusted. -The main script has a `format` execution mode which formats the full codebase. +The main script has a `format` execution mode which formats the full codebase and runs the formatting unit test. This is meant for developers only and therefore does not appear in the helper description. ### Some aspects about the codebase diff --git a/Hybrid-handler b/Hybrid-handler index c2a1a4f..c88cdbd 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -96,6 +96,7 @@ function Act_And_Exit_If_User_Ran_Auxiliary_Modes() ;;& format) Format_Codebase + Run_Formatting_Unit_Test ;;& version) Print_Software_Version diff --git a/bash/formatter.bash b/bash/formatter.bash index 5eb3350..8195c76 100644 --- a/bash/formatter.bash +++ b/bash/formatter.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -19,4 +19,15 @@ function Format_Codebase() fi } +function Run_Formatting_Unit_Test() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty HYBRID_top_level_path + source "${HYBRID_top_level_path}/tests/unit_tests_formatting.bash" || exit ${HYBRID_fatal_builtin} + # The following variable definition is just a patch to be able to reuse the test code from here + HYBRIDT_repository_top_level_path="${HYBRID_top_level_path}" + if Unit_Test__codebase-formatting; then + Print_Info 'The codebase was correctly formatted and the formatting test passes.' + fi +} + Make_Functions_Defined_In_This_File_Readonly From 26bc348a3e8a2d9941948dd75817f9b2afe075a1 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 Jan 2024 18:04:11 +0100 Subject: [PATCH 312/549] Fix subtle bug about invalidating exit on error behavior --- bash/software_input_functionality.bash | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/bash/software_input_functionality.bash b/bash/software_input_functionality.bash index 2e80ad1..80fd22b 100644 --- a/bash/software_input_functionality.bash +++ b/bash/software_input_functionality.bash @@ -110,16 +110,24 @@ function Copy_Hybrid_Handler_Config_Section() 'The config copy ' --emph "${output_file}" \ ' already exists.' fi + local executable_metadata handler_metadata try_extract_section + # NOTE: Using command substitution $(...) directly in printf arguments invalidates the error on + # exit behavior and let the script continue even if functions have non-zero exit code. + # Therefore we temporary store the resulting string in local variables. + executable_metadata=$(__static__Get_Repository_State "${executable_folder}") + handler_metadata=$(__static__Get_Repository_State "${HYBRID_top_level_path}") + try_extract_section="$(__static__Extract_Sections_From_Configuration_File "${section}")" printf '%s\n\n' \ - "# Git describe of executable folder: $(__static__Get_Repository_State "${executable_folder}")" \ - "# Git describe of handler folder: $(__static__Get_Repository_State "${HYBRID_top_level_path}")" \ - "$(__static__Extract_Sections_From_Configuration_File "${section}")" > "${output_file}" + "# Git describe of executable folder: ${executable_metadata}" \ + "# Git describe of handler folder: ${handler_metadata}" \ + "${try_extract_section}" > "${output_file}" } function __static__Extract_Sections_From_Configuration_File() { - printf '%s' "$(yq eval 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' \ - "${HYBRID_configuration_file}")" + if ! yq 'with_entries(select(.key | test("(Hybrid_handler|'"${1}"')")))' "${HYBRID_configuration_file}"; then + Print_Internal_And_Exit 'Failure extracting sections from configuration file for reproducibility.' + fi } function __static__Get_Repository_State() From 06b0b56669ff59f15b6a4188271890eb1573b743 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 25 Jan 2024 18:05:37 +0100 Subject: [PATCH 313/549] Fix potential bug due to missing enforcing of global path The handler configuration file given via the -c option was not stored as a global path and the handler could have failed to use it when changing directory in the several phases. --- bash/command_line_parsers/main_parser.bash | 4 ++-- tests/unit_tests_command_line_parser.bash | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 49b7cee..173f18f 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -69,7 +69,7 @@ function Parse_Command_Line_Options() if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else - readonly HYBRID_configuration_file=$2 + readonly HYBRID_configuration_file="$(realpath "$2")" fi shift 2 ;; diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index 7620b7b..621a886 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -111,6 +111,6 @@ function Unit_Test__parse-command-line-options() fi HYBRID_command_line_options_to_parse=(-o "${HOME}") __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_output_directory "${HOME}" || return 1 - HYBRID_command_line_options_to_parse=(-c /path/to/file) - __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_configuration_file '/path/to/file' || return 1 + HYBRID_command_line_options_to_parse=(-c "${HOME}") # Here it does not matter we use a folder instead of a file + __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_configuration_file "${HOME}" || return 1 } From 8063071de03feca6d857b1377fbc7d310903a8a8 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 23 Jan 2024 13:25:54 +0100 Subject: [PATCH 314/549] Move do functionality to own file, add informational output --- Hybrid-handler | 9 ++------- bash/dispatch_functions.bash | 6 +++++- bash/execution_mode_do.bash | 21 +++++++++++++++++++++ bash/source_codebase_files.bash | 3 ++- 4 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 bash/execution_mode_do.bash diff --git a/Hybrid-handler b/Hybrid-handler index c88cdbd..d59250d 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -2,7 +2,7 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -114,12 +114,7 @@ function Make_Needed_Operations_Depending_On_Execution_Mode() local software_section Validate_And_Parse_Configuration_File Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables - for software_section in "${HYBRID_given_software_sections[@]}"; do - Prepare_Software_Input_File "${software_section}" - Ensure_All_Needed_Input_Exists "${software_section}" - Ensure_Run_Reproducibility "${software_section}" - Run_Software "${software_section}" - done + Do_Needed_Operations_For_Given_Software ;; *) Print_Internal_And_Exit "Unexpected execution mode at top-level." diff --git a/bash/dispatch_functions.bash b/bash/dispatch_functions.bash index 34428c4..c9ae3a2 100644 --- a/bash/dispatch_functions.bash +++ b/bash/dispatch_functions.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -9,21 +9,25 @@ function Prepare_Software_Input_File() { + Print_Info 'Preparing software input file for ' --emph "$1" ' stage' Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } function Ensure_All_Needed_Input_Exists() { + Print_Info 'Ensuring all needed input for ' --emph "$1" ' stage exists' Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } function Ensure_Run_Reproducibility() { + Print_Info 'Preparing ' --emph "$1" ' reproducibility metadata' Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } function Run_Software() { + Print_Info 'Running ' --emph "$1" ' software' Call_Function_If_Existing_Or_Exit ${FUNCNAME}_$1 "${@:2}" } diff --git a/bash/execution_mode_do.bash b/bash/execution_mode_do.bash new file mode 100644 index 0000000..9ea3798 --- /dev/null +++ b/bash/execution_mode_do.bash @@ -0,0 +1,21 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Do_Needed_Operations_For_Given_Software() +{ + printf '\e[1A' # Just aesthetics to 'skip' newline for first loop iteration + for software_section in "${HYBRID_given_software_sections[@]}"; do + # Here abuse the logger to simply indent date + Print_Info -l -- "\n$(printf '\e[0m%s' "$(date)")" + Prepare_Software_Input_File "${software_section}" + Ensure_All_Needed_Input_Exists "${software_section}" + Ensure_Run_Reproducibility "${software_section}" + Run_Software "${software_section}" + done +} diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 37b6b0a..64474f7 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -25,6 +25,7 @@ function __static__Source_Codebase_Files() 'command_line_parsers/sub_parser.bash' 'configuration_parser.bash' 'dispatch_functions.bash' + 'execution_mode_do.bash' 'formatter.bash' 'global_variables.bash' 'Hydro_functionality.bash' From a0152399b4cd2be0e7ecc5e09899709a5e739ca1 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 Jan 2024 12:59:46 +0100 Subject: [PATCH 315/549] Add utility functions about files existence --- bash/utility_functions.bash | 54 ++++++++++++++++++++++++- tests/unit_tests_utility_functions.bash | 18 ++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index afe5750..043e84b 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -160,6 +160,58 @@ function Strip_ANSI_Color_Codes_From_String() sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]//g" <<< "$1" } +function Ensure_Given_Files_Do_Not_Exist() +{ + __static__Check_File_With '-f' "$@" +} + +function Ensure_Given_Files_Exist() +{ + __static__Check_File_With '! -f' "$@" +} + +function __static__Check_File_With() +{ + local -r test_to_use=$1 + shift + local filename list_of_files negations string + list_of_files=() + string='The following file' + case "${test_to_use}" in + -f ) + negations=('' 'NOT ') + ;; + "! -f" ) + negations=('NOT ' '') + ;; + *) + Print_Internal_And_Exit 'Wrong call of ' --emph "${FUNCNAME}" ' function.' + ;; + esac + for filename in "$@"; do + if [ ${test_to_use} "${filename}" ]; then + list_of_files+=( "${filename}" ) + fi + done + case ${#list_of_files[@]} in + 0) + return + ;; + 1) + string+=" was ${negations[0]}found but is expected ${negations[1]}to exist:" + ;; + *) + string+="s were ${negations[0]}found but are expected ${negations[1]}to exist:" + ;; + esac + Print_Error "${string}" + for filename in "${list_of_files[@]}"; do + Print_Error -l -- ' - ' --emph "${filename}" + done + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + '\nUnable to continue.' +} + function Call_Function_If_Existing_Or_Exit() { local name_of_the_function=$1 diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index f98efe2..fb6be4c 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -292,3 +292,19 @@ function Unit_Test__utility-strip-ANSI-codes() expected_output='Complex bold-color text' __static__Test_ANSI_Code_Removal || return 1 } + +function Unit_Test__utility-files-existence() +{ + Call_Codebase_Function Ensure_Given_Files_Do_Not_Exist 'aaa' 'not-existing' 'xcafblskdfa' + Call_Codebase_Function Ensure_Given_Files_Exist "${BASH_SOURCE[0]}" + Call_Codebase_Function_In_Subshell Ensure_Given_Files_Do_Not_Exist "${BASH_SOURCE[@]}" &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Function to ensure non existent files unexpectedly succeed.' + return 1 + fi + Call_Codebase_Function_In_Subshell Ensure_Given_Files_Exist 'not-existing' &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Function to ensure existent files unexpectedly succeed.' + return 1 + fi +} From 7194f35902302f474fd2e99e4fe02eff9c64a5ff Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 Jan 2024 13:18:12 +0100 Subject: [PATCH 316/549] Extend file existence check utility function to folders --- bash/utility_functions.bash | 16 +++++++++++++++- tests/unit_tests_utility_functions.bash | 10 ++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 043e84b..1e79893 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -170,25 +170,39 @@ function Ensure_Given_Files_Exist() __static__Check_File_With '! -f' "$@" } +function Ensure_Given_Folders_Exist() +{ + __static__Check_File_With '! -d' "$@" +} + function __static__Check_File_With() { local -r test_to_use=$1 shift local filename list_of_files negations string list_of_files=() - string='The following file' + string='The following' case "${test_to_use}" in -f ) negations=('' 'NOT ') + string+=' file' ;; "! -f" ) negations=('NOT ' '') + string+=' file' + ;; + "! -d" ) + negations=('NOT ' '') + string+=' folder' ;; *) Print_Internal_And_Exit 'Wrong call of ' --emph "${FUNCNAME}" ' function.' ;; esac for filename in "$@"; do + # NOTE: In the following if-clause the [ test command is used and not the [[ + # keyword because then it is possible to use the operator stored in the + # test_to_use variable (keywords are parsed before expanding arguments). if [ ${test_to_use} "${filename}" ]; then list_of_files+=( "${filename}" ) fi diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index fb6be4c..69c1432 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -296,15 +296,21 @@ function Unit_Test__utility-strip-ANSI-codes() function Unit_Test__utility-files-existence() { Call_Codebase_Function Ensure_Given_Files_Do_Not_Exist 'aaa' 'not-existing' 'xcafblskdfa' - Call_Codebase_Function Ensure_Given_Files_Exist "${BASH_SOURCE[0]}" Call_Codebase_Function_In_Subshell Ensure_Given_Files_Do_Not_Exist "${BASH_SOURCE[@]}" &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Function to ensure non existent files unexpectedly succeed.' return 1 fi - Call_Codebase_Function_In_Subshell Ensure_Given_Files_Exist 'not-existing' &> /dev/null + Call_Codebase_Function Ensure_Given_Files_Exist "${BASH_SOURCE[0]}" + Call_Codebase_Function_In_Subshell Ensure_Given_Files_Exist 'not-existing-file' &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Function to ensure existent files unexpectedly succeed.' return 1 fi + Call_Codebase_Function Ensure_Given_Folders_Exist "${HOME}" + Call_Codebase_Function_In_Subshell Ensure_Given_Folders_Exist 'not-existing-folder' &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Function to ensure existent folders unexpectedly succeed.' + return 1 + fi } From 853360087462a6e5a8273dc661f35a46bf483028 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 Jan 2024 18:23:17 +0100 Subject: [PATCH 317/549] Improve functionality to check existence of files or folders --- bash/utility_functions.bash | 67 +++++++++++++++++++------ tests/unit_tests_utility_functions.bash | 9 +++- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 1e79893..4cf7a31 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -160,51 +160,81 @@ function Strip_ANSI_Color_Codes_From_String() sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]//g" <<< "$1" } +# NOTE: In the following functions that check existence of files or folders, +# symbolic links are accepted and the entity of what they resolve to is +# what is tested. Links resolution is done using 'realpath -m' before +# testing the entity (the option -m accepts non existing paths). +# +# NOTE: The arguments passed to these functions are interpreted as file/folder +# names, but if an argument is '--', then the arguments before can be +# used as add-on messages to be printed in case of error (one per line). function Ensure_Given_Files_Do_Not_Exist() { - __static__Check_File_With '-f' "$@" + __static__Check_File_With '-f' 'FATAL' "$@" } function Ensure_Given_Files_Exist() { - __static__Check_File_With '! -f' "$@" + __static__Check_File_With '! -f' 'FATAL' "$@" } function Ensure_Given_Folders_Exist() { - __static__Check_File_With '! -d' "$@" + __static__Check_File_With '! -d' 'FATAL' "$@" +} + +function Internally_Ensure_Given_Files_Exist() +{ + __static__Check_File_With '! -f' 'INTERNAL' "$@" } function __static__Check_File_With() { - local -r test_to_use=$1 - shift - local filename list_of_files negations string + local -r test_to_use=$1 error=$2 + shift 2 + local add_on_message list_of_files negations string filename + add_on_message=() + if Element_In_Array_Equals_To '--' "$@"; then + for string in "$@"; do + if [[ "${string}" = '--' ]]; then + shift + break + fi + add_on_message+=("$1") + shift + done + fi list_of_files=() string='The following' case "${test_to_use}" in - -f ) + -f) negations=('' 'NOT ') string+=' file' ;; - "! -f" ) + "! -f") negations=('NOT ' '') string+=' file' ;; - "! -d" ) + "! -d") negations=('NOT ' '') string+=' folder' ;; *) - Print_Internal_And_Exit 'Wrong call of ' --emph "${FUNCNAME}" ' function.' + Print_Internal_And_Exit 'Wrong test passed to ' --emph "${FUNCNAME}" ' function.' + ;; + esac + case "${error}" in + FATAL | INTERNAL) ;; + *) + Print_Internal_And_Exit 'Wrong error passed to ' --emph "${FUNCNAME}" ' function.' ;; esac for filename in "$@"; do # NOTE: In the following if-clause the [ test command is used and not the [[ # keyword because then it is possible to use the operator stored in the # test_to_use variable (keywords are parsed before expanding arguments). - if [ ${test_to_use} "${filename}" ]; then - list_of_files+=( "${filename}" ) + if [ ${test_to_use} "$(realpath -m "${filename}")" ]; then + list_of_files+=("${filename}") fi done case ${#list_of_files[@]} in @@ -220,10 +250,17 @@ function __static__Check_File_With() esac Print_Error "${string}" for filename in "${list_of_files[@]}"; do - Print_Error -l -- ' - ' --emph "${filename}" + Print_Error -l -- ' - ' --emph "${filename}" done - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - '\nUnable to continue.' + if [[ "${#add_on_message[@]}" -ne 0 ]]; then + Print_Error -l -- "${add_on_message[@]}" + fi + if [[ "${error}" = 'INTERNAL' ]]; then + Print_Internal_And_Exit 'This should not have happened.' + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + '\nUnable to continue.' + fi } function Call_Function_If_Existing_Or_Exit() diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 69c1432..da27e5d 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -301,7 +301,8 @@ function Unit_Test__utility-files-existence() Print_Error 'Function to ensure non existent files unexpectedly succeed.' return 1 fi - Call_Codebase_Function Ensure_Given_Files_Exist "${BASH_SOURCE[0]}" + ln -s "${BASH_SOURCE[0]}" link_test + Call_Codebase_Function Ensure_Given_Files_Exist 'link_test' Call_Codebase_Function_In_Subshell Ensure_Given_Files_Exist 'not-existing-file' &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Function to ensure existent files unexpectedly succeed.' @@ -313,4 +314,10 @@ function Unit_Test__utility-files-existence() Print_Error 'Function to ensure existent folders unexpectedly succeed.' return 1 fi + Call_Codebase_Function_In_Subshell Ensure_Given_Folders_Exist 'Add-on' 'test' '--' 'link_test' &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Function to ensure existent folders unexpectedly succeed on a file.' + return 1 + fi + rm 'link_test' } From f20bf5084a926f0397f5a5106c8e0ebaad7baec9 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 26 Jan 2024 18:24:25 +0100 Subject: [PATCH 318/549] Refactor functionality applying IOSP principle All stages functionality have now become integrations and this should give an easier entry point for any reader. --- bash/Afterburner_functionality.bash | 108 ++++++------- bash/Hydro_functionality.bash | 118 +++++++------- bash/IC_functionality.bash | 29 +--- bash/Sampler_functionality.bash | 147 +++++++++--------- bash/common_functionality.bash | 41 +++++ bash/source_codebase_files.bash | 1 + .../unit_tests_Afterburner_functionality.bash | 9 +- tests/unit_tests_Hydro_functionality.bash | 3 +- tests/unit_tests_IC_functionality.bash | 3 +- tests/unit_tests_Sampler_functionality.bash | 3 +- 10 files changed, 234 insertions(+), 228 deletions(-) create mode 100644 bash/common_functionality.bash diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index d6f326a..d1b665c 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -9,74 +9,21 @@ function Prepare_Software_Input_File_Afterburner() { - mkdir -p "${HYBRID_software_output_directory[Afterburner]}" || exit ${HYBRID_fatal_builtin} - if [[ -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" ' is already existing.' - elif [[ ! -f "${HYBRID_software_base_config_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Afterburner]}" ' was not found.' - fi - cp "${HYBRID_software_base_config_file[Afterburner]}" \ - "${HYBRID_software_configuration_file[Afterburner]}" || exit ${HYBRID_fatal_builtin} - if [[ "${HYBRID_software_new_input_keys[Afterburner]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ - 'YAML' "${HYBRID_software_configuration_file[Afterburner]}" "${HYBRID_software_new_input_keys[Afterburner]}" - fi - local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampling0" - if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then - if [[ -f "${target_link_name}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The input file for the afterburner ' --emph "${target_link_name}" \ - ' already exists.' - # Here the config.yaml file is expected to be produced by SMASH in the output folder - # during the IC run. It is used to determine the initial number of particles. - elif [[ ! -f "${HYBRID_software_output_directory[IC]}/config.yaml" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Initial condition configuration file ' --emph "${HYBRID_software_output_directory[IC]}/config.yaml" \ - '\ndoes not exist, but is needed to check number of initial nucleons.' \ - 'This file is expected to be produced by the IC software run.' - elif [[ ! -f "${HYBRID_software_input_file[Spectators]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Spectator file ' --emph "${HYBRID_software_input_file[Spectators]}" \ - ' does not exist.' - fi - "${HYBRID_external_python_scripts[Add_spectators_from_IC]}" \ - '--sampled_particle_list' "${HYBRID_software_input_file[Afterburner]}" \ - '--initial_particle_list' "${HYBRID_software_input_file[Spectators]}" \ - '--output_file' "${target_link_name}" \ - '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" - else - if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then - ln -s -f "${HYBRID_software_input_file[Afterburner]}" "${target_link_name}" - elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'File ' --emph "${target_link_name}" ' exists but it is not the Afterburner input file ' \ - --emph "${HYBRID_software_input_file[Afterburner]}" ' to be used.' - fi - fi + Create_Output_Directory_For 'Afterburner' + Ensure_Given_Files_Do_Not_Exist "${HYBRID_software_configuration_file[Afterburner]}" + Ensure_Given_Files_Exist "${HYBRID_software_base_config_file[Afterburner]}" + Copy_Base_Configuration_To_Output_Folder_For 'Afterburner' + Replace_Keys_In_Configuration_File_If_Needed_For 'Afterburner' + __static__Create_Sampled_Particles_List_File_Or_Symbolic_Link_With_Or_Without_Spectators } function Ensure_All_Needed_Input_Exists_Afterburner() { - if [[ ! -d "${HYBRID_software_output_directory[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Folder ' --emph "${HYBRID_software_output_directory[Afterburner]}" ' does not exist.' - fi - if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The configuration file ' --emph "${HYBRID_software_configuration_file[Afterburner]}" \ - ' does not exist.' - fi - # sampling0 could be either a symlink or an actual file, therefore the check for existence is necessary. - if [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The input file ' --emph "${HYBRID_software_output_directory[Afterburner]}/sampling0" \ - ' was not found.' - elif [[ ! -e "${HYBRID_software_output_directory[Afterburner]}/sampling0" ]]; then - Print_Internal_And_Exit \ - 'Something went wrong when creating the Afterburner symbolic link.' - fi + Ensure_Given_Folders_Exist "${HYBRID_software_output_directory[Afterburner]}" + Ensure_Given_Files_Exist \ + "${HYBRID_software_configuration_file[Afterburner]}" \ + "${HYBRID_software_input_file[Afterburner]}" + Internally_Ensure_Given_Files_Exist "${HYBRID_software_output_directory[Afterburner]}/sampling0" } function Ensure_Run_Reproducibility_Afterburner() @@ -97,4 +44,37 @@ function Run_Software_Afterburner() >> "${afterburner_terminal_output}" } +#=============================================================================== + +function __static__Create_Sampled_Particles_List_File_Or_Symbolic_Link_With_Or_Without_Spectators() +{ + local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampling0" + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then + Ensure_Given_Files_Do_Not_Exist "${target_link_name}" + # Here the config.yaml file is expected to be produced by SMASH in the output folder + # during the IC run. It is used to determine the initial number of particles. + Ensure_Given_Files_Exist \ + 'This file is expected to be produced by the IC software run' \ + 'and is needed to check number of initial nucleons.' '--' \ + "${HYBRID_software_output_directory[IC]}/config.yaml" + Ensure_Given_Files_Exist \ + "${HYBRID_software_input_file[Afterburner]}" \ + "${HYBRID_software_input_file[Spectators]}" + # Run Python script to add spectators + "${HYBRID_external_python_scripts[Add_spectators_from_IC]}" \ + '--sampled_particle_list' "${HYBRID_software_input_file[Afterburner]}" \ + '--initial_particle_list' "${HYBRID_software_input_file[Spectators]}" \ + '--output_file' "${target_link_name}" \ + '--smash_config' "${HYBRID_software_output_directory[IC]}/config.yaml" + else + if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then + ln -s -f "${HYBRID_software_input_file[Afterburner]}" "${target_link_name}" + elif [[ ! "${target_link_name}" -ef "${HYBRID_software_input_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'File ' --emph "${target_link_name}" ' exists but it is not the Afterburner input file ' \ + --emph "${HYBRID_software_input_file[Afterburner]}" ' to be used.' + fi + fi +} + Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 35de527..5e2d2da 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -9,22 +9,51 @@ function Prepare_Software_Input_File_Hydro() { - mkdir -p "${HYBRID_software_output_directory[Hydro]}" || exit ${HYBRID_fatal_builtin} - if [[ -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' is already existing.' - elif [[ ! -f "${HYBRID_software_base_config_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Hydro]}" ' was not found.' - fi - cp "${HYBRID_software_base_config_file[Hydro]}" \ - "${HYBRID_software_configuration_file[Hydro]}" || exit ${HYBRID_fatal_builtin} - if [[ "${HYBRID_software_new_input_keys[Hydro]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ - 'TXT' "${HYBRID_software_configuration_file[Hydro]}" "${HYBRID_software_new_input_keys[Hydro]}" - fi - # Create symbolic link to IC file, which is assumed to exist here (its existence is checked later). - # If the file exists we will just use it; if it exists as a broken link we overwrite it with 'ln -f'. + Create_Output_Directory_For 'Hydro' + Ensure_Given_Files_Do_Not_Exist "${HYBRID_software_configuration_file[Hydro]}" + Ensure_Given_Files_Exist "${HYBRID_software_base_config_file[Hydro]}" + Copy_Base_Configuration_To_Output_Folder_For 'Hydro' + Replace_Keys_In_Configuration_File_If_Needed_For 'Hydro' + __static__Create_Symbolic_Link_To_IC_File + __static__Create_Symbolic_Link_To_EOS_Folder +} + +function Ensure_All_Needed_Input_Exists_Hydro() +{ + Ensure_Given_Folders_Exist "${HYBRID_software_output_directory[Hydro]}" + Ensure_Given_Files_Exist \ + "${HYBRID_software_configuration_file[Hydro]}" \ + "${HYBRID_software_input_file[Hydro]}" + Internally_Ensure_Given_Files_Exist "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" +} + +function Ensure_Run_Reproducibility_Hydro() +{ + Copy_Hybrid_Handler_Config_Section 'Hydro' \ + "${HYBRID_software_output_directory[Hydro]}" \ + "$(dirname "$(realpath "${HYBRID_software_executable[Hydro]}")")" +} + +function Run_Software_Hydro() +{ + cd "${HYBRID_software_output_directory[Hydro]}" + local -r \ + hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ + ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ + hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" + "${HYBRID_software_executable[Hydro]}" \ + "-params" "${hydro_config_file_path}" \ + "-ISinput" "${ic_output_file_path}" \ + "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" +} + +#=============================================================================== + +# NOTE: The IC file is assumed to exist here (its existence is checked later). +# If the file exists we will just use it; if it exists as a broken link +# we overwrite it with 'ln -f'. +function __static__Create_Symbolic_Link_To_IC_File() +{ local -r target_link_name="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" if [[ ! -f "${target_link_name}" || -L "${target_link_name}" ]]; then ln -s -f "${HYBRID_software_input_file[Hydro]}" "${target_link_name}" @@ -33,9 +62,14 @@ function Prepare_Software_Input_File_Hydro() 'File ' --emph "${target_link_name}" ' exists but it is not the Hydro input file ' \ --emph "${HYBRID_software_input_file[Hydro]}" ' to be used.' fi - # Create a symbolic link to the eos folder, which is assumed to exist in the hydro software - # folder. The user-specified software executable is guaranteed to be either a command name - # or a global path and in both cases 'type -P' is expected to succeed and print a global path. +} + +# NOTE: The 'eos' folder is assumed to exist in the hydro software folder. +# The user-specified software executable is guaranteed to be either a +# command name or a global path and in both cases 'type -P' is expected +# to succeed and print a global path. +function __static__Create_Symbolic_Link_To_EOS_Folder() +{ local eos_folder eos_folder="$(dirname $(type -P "${HYBRID_software_executable[Hydro]}"))/eos" if [[ ! -d "${eos_folder}" ]]; then @@ -46,7 +80,8 @@ function Prepare_Software_Input_File_Hydro() if [[ -d "${link_to_eos_folder}" ]]; then if [[ ! "${link_to_eos_folder}" -ef "${eos_folder}" ]]; then if [[ -L "${link_to_eos_folder}" ]]; then - Print_Warning 'Found a symlink ' --emph "${HYBRID_software_output_directory[Hydro]}/eos" \ + Print_Warning \ + 'Found a symlink ' --emph "${HYBRID_software_output_directory[Hydro]}/eos" \ '\npointing to a different eos folder. Unlink and link again!\n' unlink "${HYBRID_software_output_directory[Hydro]}/eos" ln -s "${eos_folder}" "${link_to_eos_folder}" @@ -59,51 +94,12 @@ function Prepare_Software_Input_File_Hydro() fi elif [[ -e "${link_to_eos_folder}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'A ' --emph 'eos' ' file already exists at ' --emph "${HYBRID_software_output_directory[Hydro]}" \ + 'A ' --emph 'eos' ' file already exists at ' \ + --emph "${HYBRID_software_output_directory[Hydro]}" \ '.' 'Please remove it and run the hybrid handler again.' else ln -s "${eos_folder}" "${link_to_eos_folder}" fi } -function Ensure_All_Needed_Input_Exists_Hydro() -{ - if [[ ! -d "${HYBRID_software_output_directory[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Folder ' --emph "${HYBRID_software_output_directory[Hydro]}" ' does not exist.' - fi - if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The configuration file ' --emph "${HYBRID_software_configuration_file[Hydro]}" ' was not found.' - fi - if [[ ! -e "${HYBRID_software_input_file[Hydro]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The input file ' --emph "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ - ' was not found.' - elif [[ ! -e "${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" ]]; then - Print_Internal_And_Exit \ - 'Something went wrong when creating the Hydro symbolic link.' - fi -} - -function Ensure_Run_Reproducibility_Hydro() -{ - Copy_Hybrid_Handler_Config_Section 'Hydro' \ - "${HYBRID_software_output_directory[Hydro]}" \ - "$(dirname "$(realpath "${HYBRID_software_executable[Hydro]}")")" -} - -function Run_Software_Hydro() -{ - cd "${HYBRID_software_output_directory[Hydro]}" - local -r \ - hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ - ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ - hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" - "${HYBRID_software_executable[Hydro]}" \ - "-params" "${hydro_config_file_path}" \ - "-ISinput" "${ic_output_file_path}" \ - "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" -} - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index 49eba78..bdfef7b 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -9,32 +9,17 @@ function Prepare_Software_Input_File_IC() { - mkdir -p "${HYBRID_software_output_directory[IC]}" || exit ${HYBRID_fatal_builtin} - if [[ -f "${HYBRID_software_configuration_file[IC]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[IC]}" ' is already existing.' - elif [[ ! -f "${HYBRID_software_base_config_file[IC]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Base configuration file ' --emph "${HYBRID_software_base_config_file[IC]}" ' was not found.' - fi - cp "${HYBRID_software_base_config_file[IC]}" \ - "${HYBRID_software_configuration_file[IC]}" || exit ${HYBRID_fatal_builtin} - if [[ "${HYBRID_software_new_input_keys[IC]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ - 'YAML' "${HYBRID_software_configuration_file[IC]}" "${HYBRID_software_new_input_keys[IC]}" - fi + Create_Output_Directory_For 'IC' + Ensure_Given_Files_Do_Not_Exist "${HYBRID_software_configuration_file[IC]}" + Ensure_Given_Files_Exist "${HYBRID_software_base_config_file[IC]}" + Copy_Base_Configuration_To_Output_Folder_For 'IC' + Replace_Keys_In_Configuration_File_If_Needed_For 'IC' } function Ensure_All_Needed_Input_Exists_IC() { - if [[ ! -d "${HYBRID_software_output_directory[IC]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Folder ' --emph "${HYBRID_software_output_directory[IC]}" ' does not exist.' - fi - if [[ ! -f "${HYBRID_software_configuration_file[IC]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'The configuration file ' --emph "${HYBRID_software_configuration_file[IC]}" ' was not found.' - fi + Ensure_Given_Folders_Exist "${HYBRID_software_output_directory[IC]}" + Ensure_Given_Files_Exist "${HYBRID_software_configuration_file[IC]}" } function Ensure_Run_Reproducibility_IC() diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index b32aab2..16b53cb 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -9,63 +9,23 @@ function Prepare_Software_Input_File_Sampler() { - mkdir -p "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} - if [[ -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}" \ - ' is already existing.' - elif [[ ! -f "${HYBRID_software_base_config_file[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Base configuration file ' --emph "${HYBRID_software_base_config_file[Sampler]}" \ - ' was not found.' - fi - cp "${HYBRID_software_base_config_file[Sampler]}" \ - "${HYBRID_software_configuration_file[Sampler]}" || exit ${HYBRID_fatal_builtin} - if [[ "${HYBRID_software_new_input_keys[Sampler]}" != '' ]]; then - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ - 'TXT' "${HYBRID_software_configuration_file[Sampler]}" \ - "${HYBRID_software_new_input_keys[Sampler]}" - fi - if ! __static__Is_Sampler_Config_Valid; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - "Sampler configuration file invalid." - fi - # Replace potentially relative paths in Sampler config with absolute paths - local freezeout_path output_directory - freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') - output_directory=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'spectra_dir') - Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ - 'TXT' "${HYBRID_software_configuration_file[Sampler]}" \ - "$(printf "%s: %s\n" \ - 'surface' "${freezeout_path}" \ - 'spectra_dir' "${output_directory}")" - # The following symbolic link is not needed by the sampler, as the sampler only refers to information - # specified in its input file. However, we want to have all input for a software in the output folder - # for future easier reproducibility (and we do so for all software handled in the codebase). - if [[ "$(dirname "${freezeout_path}")" != "${HYBRID_software_output_directory[Sampler]}" ]]; then - ln -s "${freezeout_path}" \ - "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" - fi + Create_Output_Directory_For 'Sampler' + Ensure_Given_Files_Do_Not_Exist "${HYBRID_software_configuration_file[Sampler]}" + Ensure_Given_Files_Exist "${HYBRID_software_base_config_file[Sampler]}" + Copy_Base_Configuration_To_Output_Folder_For 'Sampler' + Replace_Keys_In_Configuration_File_If_Needed_For 'Sampler' + __static__Validate_Sampler_Config_File + __static__Transform_Relative_Paths_In_Sampler_Config_File + __static__Create_Superfluous_Symbolic_Link_To_Freezeout_File } function Ensure_All_Needed_Input_Exists_Sampler() { - if [[ ! -d "${HYBRID_software_output_directory[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Folder ' --emph "${HYBRID_software_output_directory[Sampler]}" \ - ' does not exist.' - fi - if [[ ! -f "${HYBRID_software_configuration_file[Sampler]}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Configuration file ' --emph "${HYBRID_software_configuration_file[Sampler]}" \ - ' was not found.' - fi - # This is already done preparing the input file, but it's logically belonging here, too. + Ensure_Given_Folders_Exist "${HYBRID_software_output_directory[Sampler]}" + Ensure_Given_Files_Exist "${HYBRID_software_configuration_file[Sampler]}" + # This is already done preparing the input file, but it's logically belonging here. # Therefore, we repeat the validation, as its cost is substantially negligible. - if ! __static__Is_Sampler_Config_Valid; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - "Sampler configuration file validation failed when ensuring existence of all input." - fi + __static__Validate_Sampler_Config_File } function Ensure_Run_Reproducibility_Sampler() @@ -84,33 +44,43 @@ function Run_Software_Sampler() "${sampler_config_file_path}" >> "${sampler_terminal_output}" } -function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() +#=============================================================================== + +function __static__Validate_Sampler_Config_File() { - local field value - field="$1" - # We assume here that the configuration file is fine as it was validated before - value=$(awk -v name="${field}" '$1 == name {print $2; exit}' \ - "${HYBRID_software_configuration_file[Sampler]}") - if [[ "${value}" = '=DEFAULT=' ]]; then - case "${field}" in - surface) - printf "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" - ;; - spectra_dir) - printf "${HYBRID_software_output_directory[Sampler]}" - ;; - esac - else - cd "${HYBRID_software_output_directory[Sampler]}" - # If realpath succeeds, it prints the path that is the result of the function - if ! realpath "${value}" 2> /dev/null; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Unable to transform relative path ' --emph "${value}" ' into global one.' - fi - cd - > /dev/null + if ! __static__Is_Sampler_Config_Valid; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + "The sampler configuration file is invalid." fi } +function __static__Transform_Relative_Paths_In_Sampler_Config_File() +{ + local freezeout_path output_directory + freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') + output_directory=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'spectra_dir') + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ + 'TXT' "${HYBRID_software_configuration_file[Sampler]}" \ + "$(printf "%s: %s\n" \ + 'surface' "${freezeout_path}" \ + 'spectra_dir' "${output_directory}")" +} + +# The following symbolic link is not needed by the sampler, as the sampler only refers to information +# specified in its input file. However, we want to have all input for a software in the output folder +# for future easier reproducibility (and we do so for all software handled in the codebase). +function __static__Create_Superfluous_Symbolic_Link_To_Freezeout_File() +{ + local freezeout_path + freezeout_path=$(__static__Get_Path_Field_From_Sampler_Config_As_Global_Path 'surface') + if [[ "$(dirname "${freezeout_path}")" != "${HYBRID_software_output_directory[Sampler]}" ]]; then + ln -s "${freezeout_path}" \ + "${HYBRID_software_output_directory[Sampler]}/freezeout.dat" + fi +} + +#=============================================================================== + function __static__Is_Sampler_Config_Valid() { local -r config_file="${HYBRID_software_configuration_file[Sampler]}" @@ -207,4 +177,31 @@ function __static__Is_Sampler_Config_Valid() fi } +function __static__Get_Path_Field_From_Sampler_Config_As_Global_Path() +{ + local field value + field="$1" + # We assume here that the configuration file is fine as it was validated before + value=$(awk -v name="${field}" '$1 == name {print $2; exit}' \ + "${HYBRID_software_configuration_file[Sampler]}") + if [[ "${value}" = '=DEFAULT=' ]]; then + case "${field}" in + surface) + printf "${HYBRID_software_output_directory[Hydro]}/freezeout.dat" + ;; + spectra_dir) + printf "${HYBRID_software_output_directory[Sampler]}" + ;; + esac + else + cd "${HYBRID_software_output_directory[Sampler]}" || exit ${HYBRID_fatal_builtin} + # If realpath succeeds, it prints the path that is the result of the function + if ! realpath "${value}" 2> /dev/null; then + exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ + 'Unable to transform relative path ' --emph "${value}" ' into global one.' + fi + cd - > /dev/null || exit ${HYBRID_fatal_builtin} + fi +} + Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/common_functionality.bash b/bash/common_functionality.bash new file mode 100644 index 0000000..f93a098 --- /dev/null +++ b/bash/common_functionality.bash @@ -0,0 +1,41 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Create_Output_Directory_For() +{ + mkdir -p "${HYBRID_software_output_directory[$1]}" || exit ${HYBRID_fatal_builtin} +} + +function Copy_Base_Configuration_To_Output_Folder_For() +{ + cp "${HYBRID_software_base_config_file[$1]}" \ + "${HYBRID_software_configuration_file[$1]}" || exit ${HYBRID_fatal_builtin} +} + +function Replace_Keys_In_Configuration_File_If_Needed_For() +{ + local file_type + case "$1" in + IC | Afterburner) + file_type='YAML' + ;; + Hydro | Sampler) + file_type='TXT' + ;; + *) + Print_Internal_And_Exit 'Wrong call of ' --emph "${FUNCNAME}" ' function.' + ;; + esac + if [[ "${HYBRID_software_new_input_keys[$1]}" != '' ]]; then + Remove_Comments_And_Replace_Provided_Keys_In_Provided_Input_File \ + "${file_type}" \ + "${HYBRID_software_configuration_file[$1]}" \ + "${HYBRID_software_new_input_keys[$1]}" + fi +} diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 64474f7..043ff66 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -23,6 +23,7 @@ function __static__Source_Codebase_Files() 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' 'command_line_parsers/sub_parser.bash' + 'common_functionality.bash' 'configuration_parser.bash' 'dispatch_functions.bash' 'execution_mode_do.bash' diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index c709e71..c3f0355 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -11,6 +11,7 @@ __static__Do_Preliminary_Setup_Operations() { local file_to_be_sourced list_of_files list_of_files=( + 'common_functionality.bash' 'Afterburner_functionality.bash' 'global_variables.bash' 'software_input_functionality.bash' @@ -144,12 +145,14 @@ function Unit_Test__Afterburner-check-all-input() Print_Error 'Ensuring existence of auxiliary input data file succeeded.' return 1 fi - touch "${HYBRID_software_output_directory[Afterburner]}/sampling0" + touch \ + "${HYBRID_software_output_directory[Afterburner]}/sampling0" \ + "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then Print_Error \ 'Ensuring existence of existing folder/file unexpectedly failed,' \ - ' although all files were provided.' + 'although all files were provided.' return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/sampling0" diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index e198d7d..f21a921 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -11,6 +11,7 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() { local file_to_be_sourced list_of_files list_of_files=( + 'common_functionality.bash' 'Hydro_functionality.bash' 'global_variables.bash' 'software_input_functionality.bash' diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index 2085b99..b231977 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -11,6 +11,7 @@ function Make_Test_Preliminary_Operations__IC-create-input-file() { local file_to_be_sourced list_of_files list_of_files=( + 'common_functionality.bash' 'IC_functionality.bash' 'global_variables.bash' 'software_input_functionality.bash' diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 774ed52..f178127 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -11,6 +11,7 @@ function Make_Test_Preliminary_Operations__Sampler-create-input-file() { local file_to_be_sourced list_of_files list_of_files=( + 'common_functionality.bash' 'Sampler_functionality.bash' 'global_variables.bash' 'software_input_functionality.bash' From 9c27cf8c33f656637669d4d058d2af11c8aa56da Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Feb 2024 09:08:25 +0100 Subject: [PATCH 319/549] Add amplification comment and fix tests error messages --- bash/utility_functions.bash | 9 +++++++++ tests/unit_tests_utility_functions.bash | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 4cf7a31..4c1ad72 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -188,6 +188,15 @@ function Internally_Ensure_Given_Files_Exist() __static__Check_File_With '! -f' 'INTERNAL' "$@" } +# Since the few functions above differ in small aspects, it is possible to have +# a core common implementation. The following static function takes: +# $1 -> the test operator to be used in [[ ... ]] keyword +# $2 -> whether to print a fatal or an internal error +# ${@:3} -> the names of the files to be tested. +# +# NOTE: If among the names of the files the argument '--' is used, then +# this is ignored and the arguments before are an add on message to +# be printed in case of error (one argument per line). function __static__Check_File_With() { local -r test_to_use=$1 error=$2 diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index da27e5d..3d5ba6f 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -298,25 +298,25 @@ function Unit_Test__utility-files-existence() Call_Codebase_Function Ensure_Given_Files_Do_Not_Exist 'aaa' 'not-existing' 'xcafblskdfa' Call_Codebase_Function_In_Subshell Ensure_Given_Files_Do_Not_Exist "${BASH_SOURCE[@]}" &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Function to ensure non existent files unexpectedly succeed.' + Print_Error 'Function to ensure non existent files unexpectedly succeeded.' return 1 fi ln -s "${BASH_SOURCE[0]}" link_test Call_Codebase_Function Ensure_Given_Files_Exist 'link_test' Call_Codebase_Function_In_Subshell Ensure_Given_Files_Exist 'not-existing-file' &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Function to ensure existent files unexpectedly succeed.' + Print_Error 'Function to ensure existent files unexpectedly succeeded.' return 1 fi Call_Codebase_Function Ensure_Given_Folders_Exist "${HOME}" Call_Codebase_Function_In_Subshell Ensure_Given_Folders_Exist 'not-existing-folder' &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Function to ensure existent folders unexpectedly succeed.' + Print_Error 'Function to ensure existent folders unexpectedly succeeded.' return 1 fi Call_Codebase_Function_In_Subshell Ensure_Given_Folders_Exist 'Add-on' 'test' '--' 'link_test' &> /dev/null if [[ $? -eq 0 ]]; then - Print_Error 'Function to ensure existent folders unexpectedly succeed on a file.' + Print_Error 'Function to ensure existent folders unexpectedly succeeded on a file.' return 1 fi rm 'link_test' From ac66ebffb00e61993d6af94fdb48a20477e4e5d2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Feb 2024 09:10:22 +0100 Subject: [PATCH 320/549] Rename function with more explicit name --- bash/utility_functions.bash | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 4c1ad72..8fa5ba7 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -170,22 +170,22 @@ function Strip_ANSI_Color_Codes_From_String() # used as add-on messages to be printed in case of error (one per line). function Ensure_Given_Files_Do_Not_Exist() { - __static__Check_File_With '-f' 'FATAL' "$@" + __static__Check_Given_Files_With '-f' 'FATAL' "$@" } function Ensure_Given_Files_Exist() { - __static__Check_File_With '! -f' 'FATAL' "$@" + __static__Check_Given_Files_With '! -f' 'FATAL' "$@" } function Ensure_Given_Folders_Exist() { - __static__Check_File_With '! -d' 'FATAL' "$@" + __static__Check_Given_Files_With '! -d' 'FATAL' "$@" } function Internally_Ensure_Given_Files_Exist() { - __static__Check_File_With '! -f' 'INTERNAL' "$@" + __static__Check_Given_Files_With '! -f' 'INTERNAL' "$@" } # Since the few functions above differ in small aspects, it is possible to have @@ -197,7 +197,7 @@ function Internally_Ensure_Given_Files_Exist() # NOTE: If among the names of the files the argument '--' is used, then # this is ignored and the arguments before are an add on message to # be printed in case of error (one argument per line). -function __static__Check_File_With() +function __static__Check_Given_Files_With() { local -r test_to_use=$1 error=$2 shift 2 From 9c28d5a1a6735e57db012048dbfb41b782b43b03 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Feb 2024 17:55:29 +0100 Subject: [PATCH 321/549] Adjust mailmap file according to present repository history --- .mailmap | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index e398695..0a2e8cc 100644 --- a/.mailmap +++ b/.mailmap @@ -1,12 +1,18 @@ -Alessandro Sciarra +Alessandro Sciarra +Alessandro Sciarra Anna Schäfer Anna Schäfer <36138176+akschaefer@users.noreply.github.com> +Gabriele Inghirami <71900834+gabriele-inghirami@users.noreply.github.com> +Gabriele Inghirami gabriele-inghirami Niklas Götz Niklas Götz Niklas Götz Niklas Götz Niklas Goetz +Nils Saß +Nils Saß <90659054+nilssass@users.noreply.github.com> Jan Hammelmann Jan Hammelmann <33685583+JanHammelmann@users.noreply.github.com> Renan Hirayama Renan Hirayama RenHirayama <79031685+RenHirayama@users.noreply.github.com> Zuzana Paulinyova +Zuzana Paulinyova zpauliny <103072930+zpauliny@users.noreply.github.com> From 6012d23ca54b7b5f3e0d8a283a51493bb64c6a0c Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Thu, 25 Jan 2024 10:30:57 +0100 Subject: [PATCH 322/549] Create target configs - 12 new config files for specific systems and energies with suitable hydro parameters - test config file intended for test runs of the hybrid handler - minor changes in the base config files --- bash/global_variables.bash | 2 +- configs/hadron_sampler | 2 +- configs/smash_afterburner.yaml | 4 +-- ...uAu.yaml => smash_initial_conditions.yaml} | 0 configs/smash_initial_conditions_PbPb.yaml | 28 --------------- configs/smash_initial_conditions_custom.yaml | 28 --------------- configs/targets/config_AuAu_130.0.yaml | 27 ++++++++++++++ configs/targets/config_AuAu_200.0.yaml | 25 +++++++++++++ configs/targets/config_AuAu_27.0.yaml | 25 +++++++++++++ configs/targets/config_AuAu_39.0.yaml | 25 +++++++++++++ configs/targets/config_AuAu_4.3.yaml | 26 ++++++++++++++ configs/targets/config_AuAu_62.4.yaml | 25 +++++++++++++ configs/targets/config_AuAu_7.7.yaml | 26 ++++++++++++++ configs/targets/config_PbPb_17.3.yaml | 25 +++++++++++++ configs/targets/config_PbPb_2760.0.yaml | 25 +++++++++++++ configs/targets/config_PbPb_5020.0.yaml | 25 +++++++++++++ configs/targets/config_PbPb_6.4.yaml | 25 +++++++++++++ configs/targets/config_PbPb_8.8.yaml | 25 +++++++++++++ configs/targets/config_TEST.yaml | 35 +++++++++++++++++++ 19 files changed, 343 insertions(+), 60 deletions(-) rename configs/{smash_initial_conditions_AuAu.yaml => smash_initial_conditions.yaml} (100%) delete mode 100644 configs/smash_initial_conditions_PbPb.yaml delete mode 100644 configs/smash_initial_conditions_custom.yaml create mode 100644 configs/targets/config_AuAu_130.0.yaml create mode 100644 configs/targets/config_AuAu_200.0.yaml create mode 100644 configs/targets/config_AuAu_27.0.yaml create mode 100644 configs/targets/config_AuAu_39.0.yaml create mode 100644 configs/targets/config_AuAu_4.3.yaml create mode 100644 configs/targets/config_AuAu_62.4.yaml create mode 100644 configs/targets/config_AuAu_7.7.yaml create mode 100644 configs/targets/config_PbPb_17.3.yaml create mode 100644 configs/targets/config_PbPb_2760.0.yaml create mode 100644 configs/targets/config_PbPb_5020.0.yaml create mode 100644 configs/targets/config_PbPb_6.4.yaml create mode 100644 configs/targets/config_PbPb_8.8.yaml create mode 100644 configs/targets/config_TEST.yaml diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 0d1f1a1..757841a 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -106,7 +106,7 @@ function Define_Further_Global_Variables() [Afterburner]='' ) declare -gA HYBRID_software_base_config_file=( - [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions_AuAu.yaml" + [IC]="${HYBRID_default_configurations_folder}/smash_initial_conditions.yaml" [Hydro]="${HYBRID_default_configurations_folder}/vhlle_hydro" [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" diff --git a/configs/hadron_sampler b/configs/hadron_sampler index 2d97e94..0cdf99a 100644 --- a/configs/hadron_sampler +++ b/configs/hadron_sampler @@ -1,6 +1,6 @@ surface =DEFAULT= spectra_dir =DEFAULT= -number_of_events 100 +number_of_events 1000 weakContribution 0 shear 1 ecrit 0.5 diff --git a/configs/smash_afterburner.yaml b/configs/smash_afterburner.yaml index c8bd388..7d99606 100644 --- a/configs/smash_afterburner.yaml +++ b/configs/smash_afterburner.yaml @@ -7,7 +7,7 @@ General: Delta_Time: 0.1 End_Time: 10000.0 # make sure runtime is long enough for back-propagation Randomseed: -1 - Nevents: 2000 + Nevents: 1000 Output: Particles: @@ -15,6 +15,6 @@ Output: Modi: List: - File_Directory: "../build/" + File_Directory: "." File_Prefix: "sampling" Shift_Id: 0 diff --git a/configs/smash_initial_conditions_AuAu.yaml b/configs/smash_initial_conditions.yaml similarity index 100% rename from configs/smash_initial_conditions_AuAu.yaml rename to configs/smash_initial_conditions.yaml diff --git a/configs/smash_initial_conditions_PbPb.yaml b/configs/smash_initial_conditions_PbPb.yaml deleted file mode 100644 index 1cc1fe7..0000000 --- a/configs/smash_initial_conditions_PbPb.yaml +++ /dev/null @@ -1,28 +0,0 @@ -Logging: - default: INFO - -General: - Modus: Collider - Time_Step_Mode: Fixed - Delta_Time: 1.0 - End_Time: 2000.0 - Randomseed: -1 - Nevents: 1 - -Output: - Initial_Conditions: - Format: ["ASCII", "Binary", "Oscar2013"] - Extended: True - - -Modi: - Collider: - Projectile: - Particles: {2212: 82, 2112: 126} #Lead208 - Target: - Particles: {2212: 82, 2112: 126} #Lead208 - - Sqrtsnn: 200.0 - Fermi_Motion: frozen - Impact: - Max: 2.3 diff --git a/configs/smash_initial_conditions_custom.yaml b/configs/smash_initial_conditions_custom.yaml deleted file mode 100644 index ffa8271..0000000 --- a/configs/smash_initial_conditions_custom.yaml +++ /dev/null @@ -1,28 +0,0 @@ -Logging: - default: INFO - -General: - Modus: Collider - Time_Step_Mode: Fixed - Delta_Time: 1.0 - End_Time: 2000.0 - Randomseed: -1 - Nevents: 1 - -Output: - Initial_Conditions: - Format: ["ASCII", "Binary", "Oscar2013"] - Extended: True - - -Modi: - Collider: - Projectile: - Particles: {2212: 79, 2112: 118} #Gold197 - Target: - Particles: {2212: 79, 2112: 118} #Gold197 - - Sqrtsnn: 200.0 - Fermi_Motion: frozen - Impact: - Max: 5.2 diff --git a/configs/targets/config_AuAu_130.0.yaml b/configs/targets/config_AuAu_130.0.yaml new file mode 100644 index 0000000..d64307c --- /dev/null +++ b/configs/targets/config_AuAu_130.0.yaml @@ -0,0 +1,27 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 79, 2112: 118} #Gold197 + Target: + Particles: {2212: 79, 2112: 118} #Gold197 + + Sqrtsnn: 130.0 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.08 + Rg: 1.0 + Rgz: 0.8 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE + + diff --git a/configs/targets/config_AuAu_200.0.yaml b/configs/targets/config_AuAu_200.0.yaml new file mode 100644 index 0000000..b0fa882 --- /dev/null +++ b/configs/targets/config_AuAu_200.0.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 79, 2112: 118} #Gold197 + Target: + Particles: {2212: 79, 2112: 118} #Gold197 + + Sqrtsnn: 200.0 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.08 + Rg: 1.0 + Rgz: 1.1 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE \ No newline at end of file diff --git a/configs/targets/config_AuAu_27.0.yaml b/configs/targets/config_AuAu_27.0.yaml new file mode 100644 index 0000000..bfb6885 --- /dev/null +++ b/configs/targets/config_AuAu_27.0.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 79, 2112: 118} #Gold197 + Target: + Particles: {2212: 79, 2112: 118} #Gold197 + + Sqrtsnn: 27.0 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.12 + Rg: 1.0 + Rgz: 0.4 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE \ No newline at end of file diff --git a/configs/targets/config_AuAu_39.0.yaml b/configs/targets/config_AuAu_39.0.yaml new file mode 100644 index 0000000..6fc45e2 --- /dev/null +++ b/configs/targets/config_AuAu_39.0.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 79, 2112: 118} #Gold197 + Target: + Particles: {2212: 79, 2112: 118} #Gold197 + + Sqrtsnn: 39.0 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.08 + Rg: 1.0 + Rgz: 0.3 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE \ No newline at end of file diff --git a/configs/targets/config_AuAu_4.3.yaml b/configs/targets/config_AuAu_4.3.yaml new file mode 100644 index 0000000..0a1f190 --- /dev/null +++ b/configs/targets/config_AuAu_4.3.yaml @@ -0,0 +1,26 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 79, 2112: 118} #Gold197 + Target: + Particles: {2212: 79, 2112: 118} #Gold197 + + Sqrtsnn: 4.3 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.2 + Rg: 1.4 + Rgz: 1.3 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE + diff --git a/configs/targets/config_AuAu_62.4.yaml b/configs/targets/config_AuAu_62.4.yaml new file mode 100644 index 0000000..5091466 --- /dev/null +++ b/configs/targets/config_AuAu_62.4.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 79, 2112: 118} #Gold197 + Target: + Particles: {2212: 79, 2112: 118} #Gold197 + + Sqrtsnn: 62.4 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.08 + Rg: 1.0 + Rgz: 0.6 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE \ No newline at end of file diff --git a/configs/targets/config_AuAu_7.7.yaml b/configs/targets/config_AuAu_7.7.yaml new file mode 100644 index 0000000..5d6cc25 --- /dev/null +++ b/configs/targets/config_AuAu_7.7.yaml @@ -0,0 +1,26 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 79, 2112: 118} #Gold197 + Target: + Particles: {2212: 79, 2112: 118} #Gold197 + + Sqrtsnn: 7.7 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.2 + Rg: 1.4 + Rgz: 1.2 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE + diff --git a/configs/targets/config_PbPb_17.3.yaml b/configs/targets/config_PbPb_17.3.yaml new file mode 100644 index 0000000..eb7463b --- /dev/null +++ b/configs/targets/config_PbPb_17.3.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 82, 2112: 126} #Lead208 + Target: + Particles: {2212: 82, 2112: 126} #Lead208 + + Sqrtsnn: 17.3 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.15 + Rg: 1.4 + Rgz: 0.7 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE \ No newline at end of file diff --git a/configs/targets/config_PbPb_2760.0.yaml b/configs/targets/config_PbPb_2760.0.yaml new file mode 100644 index 0000000..8335d35 --- /dev/null +++ b/configs/targets/config_PbPb_2760.0.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 82, 2112: 126} #Lead208 + Target: + Particles: {2212: 82, 2112: 126} #Lead208 + + Sqrtsnn: 2760.0 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.08 + Rg: 1.2 + Rgz: 1.3 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE \ No newline at end of file diff --git a/configs/targets/config_PbPb_5020.0.yaml b/configs/targets/config_PbPb_5020.0.yaml new file mode 100644 index 0000000..5cb71d7 --- /dev/null +++ b/configs/targets/config_PbPb_5020.0.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 82, 2112: 126} #Lead208 + Target: + Particles: {2212: 82, 2112: 126} #Lead208 + + Sqrtsnn: 5020.0 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.1 + Rg: 1.0 + Rgz: 1.3 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE \ No newline at end of file diff --git a/configs/targets/config_PbPb_6.4.yaml b/configs/targets/config_PbPb_6.4.yaml new file mode 100644 index 0000000..6adebee --- /dev/null +++ b/configs/targets/config_PbPb_6.4.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 82, 2112: 126} #Lead208 + Target: + Particles: {2212: 82, 2112: 126} #Lead208 + + Sqrtsnn: 6.4 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.2 + Rg: 1.4 + Rgz: 1.2 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE diff --git a/configs/targets/config_PbPb_8.8.yaml b/configs/targets/config_PbPb_8.8.yaml new file mode 100644 index 0000000..5f4e30c --- /dev/null +++ b/configs/targets/config_PbPb_8.8.yaml @@ -0,0 +1,25 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 82, 2112: 126} #Lead208 + Target: + Particles: {2212: 82, 2112: 126} #Lead208 + + Sqrtsnn: 8.8 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.2 + Rg: 1.4 + Rgz: 1.0 + +Sampler: + Executable: /path/to/hadron-sampler + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE \ No newline at end of file diff --git a/configs/targets/config_TEST.yaml b/configs/targets/config_TEST.yaml new file mode 100644 index 0000000..85ca6e8 --- /dev/null +++ b/configs/targets/config_TEST.yaml @@ -0,0 +1,35 @@ +IC: + Executable: /path/to/smash + Software_keys: + Modi: + Collider: + Projectile: + Particles: {2212: 79, 2112: 118} #Gold197 + Target: + Particles: {2212: 79, 2112: 118} #Gold197 + + Sqrtsnn: 7.7 + +Hydro: + Executable: /path/to/vHLLE + Software_keys: + etaS: 0.2 + Rg: 1.4 + Rgz: 1.2 + + nx: 81 + ny: 81 + nz: 151 + +Sampler: + Executable: /path/to/hadron-sampler + Software_keys: + number_of_events: 50 + +Afterburner: + Executable: /path/to/smash + Add_spectators_from_IC: TRUE + Software_keys: + General: + Nevents: 50 + From c07359d95d634f5ae318664469b23f57a48ad2ab Mon Sep 17 00:00:00 2001 From: Jan Hammelmann Date: Mon, 20 Nov 2023 11:03:29 +0100 Subject: [PATCH 323/549] Fix reading of number of particles from config and commented lines. --- python/add_spectators.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/python/add_spectators.py b/python/add_spectators.py index bccdb96..0cf6890 100755 --- a/python/add_spectators.py +++ b/python/add_spectators.py @@ -3,6 +3,8 @@ import numpy as np import argparse import os +import yaml +import sys ''' This script adds the spectators of the collision, that were not included in @@ -19,20 +21,23 @@ def get_initial_nucleons_from_config(): We need to find the indented configuration of the projectile and target nuclei and read off the number of constituents. ''' - N_initial_nucleons = 0 - with open(args.smash_config) as config: - for line in config: - if line.startswith(' Particles:'): - split_line = line.split(':') - if len(split_line) != 4: - print ('ERROR: Cannot cope with more than two particle ' - 'species forming the initial nucleons. Pleae modify ' - 'the \'python_scripts/add_spectators.py\' script.') - break - N_initial_nucleons += int(line.split(':')[2].split(',')[0]) - N_initial_nucleons += int(line.split(':')[3].split('}')[0]) - + with open(args.smash_config, 'r') as file: + data_config = yaml.safe_load(file) + + try: + projectile_particles = data_config['Modi']['Collider']['Projectile']['Particles'] + target_particles = data_config['Modi']['Collider']['Projectile']['Particles'] + + for particle in projectile_particles.keys(): + N_initial_nucleons += projectile_particles[particle] + + for particle in target_particles.keys(): + N_initial_nucleons += target_particles[particle] + except: + print("The config file does not contain the necessary information about the projectile and target nuclei.") + sys.exit() + return N_initial_nucleons @@ -49,7 +54,7 @@ def extract_spectators(): spectator_list = [] with open(args.initial_particle_list, 'r') as f: for line in f: - if (len(line.split()) != 20): continue # Comment line + if line[0] == "#": continue # Comment line # Is initial nucleon and has not interacted if ((int(line.split()[10]) <= N_nucleons) and (int(line.split()[12]) == 0) ): # To properly determine the spectators, we need the extended output. From aaf0c4f7ef301d0d611a3a0b9eea1e045827dc84 Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Mon, 5 Feb 2024 14:29:40 +0100 Subject: [PATCH 324/549] Add documentation --- .../config_AuAu_130.0.yaml | 0 .../config_AuAu_200.0.yaml | 0 .../config_AuAu_27.0.yaml | 0 .../config_AuAu_39.0.yaml | 0 .../config_AuAu_4.3.yaml | 0 .../config_AuAu_62.4.yaml | 0 .../config_AuAu_7.7.yaml | 0 .../config_PbPb_17.3.yaml | 0 .../config_PbPb_2760.0.yaml | 0 .../config_PbPb_5020.0.yaml | 0 .../config_PbPb_6.4.yaml | 0 .../config_PbPb_8.8.yaml | 0 .../config_TEST.yaml | 0 docs/user/configuration-file.md | 3 ++ docs/user/index.md | 2 + docs/user/predef-configs.md | 41 +++++++++++++++++++ mkdocs.yml | 1 + 17 files changed, 47 insertions(+) rename configs/{targets => predef_configs}/config_AuAu_130.0.yaml (100%) rename configs/{targets => predef_configs}/config_AuAu_200.0.yaml (100%) rename configs/{targets => predef_configs}/config_AuAu_27.0.yaml (100%) rename configs/{targets => predef_configs}/config_AuAu_39.0.yaml (100%) rename configs/{targets => predef_configs}/config_AuAu_4.3.yaml (100%) rename configs/{targets => predef_configs}/config_AuAu_62.4.yaml (100%) rename configs/{targets => predef_configs}/config_AuAu_7.7.yaml (100%) rename configs/{targets => predef_configs}/config_PbPb_17.3.yaml (100%) rename configs/{targets => predef_configs}/config_PbPb_2760.0.yaml (100%) rename configs/{targets => predef_configs}/config_PbPb_5020.0.yaml (100%) rename configs/{targets => predef_configs}/config_PbPb_6.4.yaml (100%) rename configs/{targets => predef_configs}/config_PbPb_8.8.yaml (100%) rename configs/{targets => predef_configs}/config_TEST.yaml (100%) create mode 100644 docs/user/predef-configs.md diff --git a/configs/targets/config_AuAu_130.0.yaml b/configs/predef_configs/config_AuAu_130.0.yaml similarity index 100% rename from configs/targets/config_AuAu_130.0.yaml rename to configs/predef_configs/config_AuAu_130.0.yaml diff --git a/configs/targets/config_AuAu_200.0.yaml b/configs/predef_configs/config_AuAu_200.0.yaml similarity index 100% rename from configs/targets/config_AuAu_200.0.yaml rename to configs/predef_configs/config_AuAu_200.0.yaml diff --git a/configs/targets/config_AuAu_27.0.yaml b/configs/predef_configs/config_AuAu_27.0.yaml similarity index 100% rename from configs/targets/config_AuAu_27.0.yaml rename to configs/predef_configs/config_AuAu_27.0.yaml diff --git a/configs/targets/config_AuAu_39.0.yaml b/configs/predef_configs/config_AuAu_39.0.yaml similarity index 100% rename from configs/targets/config_AuAu_39.0.yaml rename to configs/predef_configs/config_AuAu_39.0.yaml diff --git a/configs/targets/config_AuAu_4.3.yaml b/configs/predef_configs/config_AuAu_4.3.yaml similarity index 100% rename from configs/targets/config_AuAu_4.3.yaml rename to configs/predef_configs/config_AuAu_4.3.yaml diff --git a/configs/targets/config_AuAu_62.4.yaml b/configs/predef_configs/config_AuAu_62.4.yaml similarity index 100% rename from configs/targets/config_AuAu_62.4.yaml rename to configs/predef_configs/config_AuAu_62.4.yaml diff --git a/configs/targets/config_AuAu_7.7.yaml b/configs/predef_configs/config_AuAu_7.7.yaml similarity index 100% rename from configs/targets/config_AuAu_7.7.yaml rename to configs/predef_configs/config_AuAu_7.7.yaml diff --git a/configs/targets/config_PbPb_17.3.yaml b/configs/predef_configs/config_PbPb_17.3.yaml similarity index 100% rename from configs/targets/config_PbPb_17.3.yaml rename to configs/predef_configs/config_PbPb_17.3.yaml diff --git a/configs/targets/config_PbPb_2760.0.yaml b/configs/predef_configs/config_PbPb_2760.0.yaml similarity index 100% rename from configs/targets/config_PbPb_2760.0.yaml rename to configs/predef_configs/config_PbPb_2760.0.yaml diff --git a/configs/targets/config_PbPb_5020.0.yaml b/configs/predef_configs/config_PbPb_5020.0.yaml similarity index 100% rename from configs/targets/config_PbPb_5020.0.yaml rename to configs/predef_configs/config_PbPb_5020.0.yaml diff --git a/configs/targets/config_PbPb_6.4.yaml b/configs/predef_configs/config_PbPb_6.4.yaml similarity index 100% rename from configs/targets/config_PbPb_6.4.yaml rename to configs/predef_configs/config_PbPb_6.4.yaml diff --git a/configs/targets/config_PbPb_8.8.yaml b/configs/predef_configs/config_PbPb_8.8.yaml similarity index 100% rename from configs/targets/config_PbPb_8.8.yaml rename to configs/predef_configs/config_PbPb_8.8.yaml diff --git a/configs/targets/config_TEST.yaml b/configs/predef_configs/config_TEST.yaml similarity index 100% rename from configs/targets/config_TEST.yaml rename to configs/predef_configs/config_TEST.yaml diff --git a/docs/user/configuration-file.md b/docs/user/configuration-file.md index 9991da8..c533bda 100644 --- a/docs/user/configuration-file.md +++ b/docs/user/configuration-file.md @@ -152,6 +152,9 @@ Sampler: Afterburner: Executable: /path/to/smash ``` +**Note:** Such a configuration file will execute all the modules in production mode, involving a fine hydrodynamic grid and a large statistic of sampled events. +It is therefore better suited to be executed at a computer cluster. To test your set-up locally, we suggest using config_TEST.yaml, for more read the section [Predefined configuration files](predef-configs.md). + ??? question "What if I want to omit some stages?" diff --git a/docs/user/index.md b/docs/user/index.md index 342760b..489f63c 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -7,9 +7,11 @@ The hybrid handler is a :simple-gnubash: **Bash script** and therefore it does n - :white_check_mark:   __Ready to go?__ – [Check it out!] - :sunrise_over_mountains:   __What's the main idea?__ - [Here you go!] - :screwdriver:   __Wanna run?__ - [Write your config file!] +- :bulb:   __Let's try it out!__ - [Use the ready-to-go config files!] [Check it out!]: prerequisites.md [Here you go!]: overview.md [Write your config file!]: configuration-file.md + [Use the ready-to-go config files!]: predef-configs.md diff --git a/docs/user/predef-configs.md b/docs/user/predef-configs.md new file mode 100644 index 0000000..4458201 --- /dev/null +++ b/docs/user/predef-configs.md @@ -0,0 +1,41 @@ +# The predefined configuration files + +## Configuring the collision setups +There are several complete handler configuration files prepared for the user to run the hybrid handler in its entirety for different collision systems and energies. +The shear viscosities applied are taken from *Karpenko et al.: Phys.Rev.C 91 (2015)* and the longitudinal and transversal smearing parameters are adjusted to improve agreement with experimental data. +The supported collision systems are: + +| System | $\mathbf{s_{NN} \; (GeV)}$ | +| :---------: | :----------------------------------: | +| Au + Au | 4.3 | +| Pb + Pb | 6.4 | +| Au + Au | 7.7 | +| Pb + Pb | 8.8 | +| Pb + Pb | 17.3 | +| Au + Au | 27.0 | +| Au + Au | 39.0 | +| Au + Au | 62.4 | +| Au + Au | 130.0 | +| Au + Au | 200.0 | +| Pb + Pb | 2760.0 | +| Pb + Pb | 5020.0 | + +They can be found in **predef_configs** subdirectory of the configs folder and the user is only required to insert the paths of the executables in the individual software sections. +They can be executed in the standard manner using *-c* option in the execution mode of the handler, i.e.: + +``` title="Example about running hybrid handler for a predefined setup: Au+Au collision @ 4.3 GeV" +./Hybrid-handler do -c configs/predef_configs/config_AuAu_4.3.yaml +``` +**Note:** The default predefined setup is meant to be executed on a computing cluster, we do not recommend executing it locally. + +## Running a test setup + +To test the functionality and to also run it on a local computer, there is the possibility to execute a test setup of the hybrid handler, which is a Au+Au collision at sqrt(s) = 7.7 GeV. +The statistics are significantly reduced however and the grid for the hydrodynamic evolution is characterized by large cells, too large for a realistic simulation scenario.
+**This test setup is hence only meant to be used in order to test the functionality of the framework and the embedded scripts, but not for production runs.** + +To execute the test, a predefined configuration file is prepared in the same **predef_configs** subdirectory of the configs folder. Again, the user needs to insert the paths to the executables in all the software sections. + +``` title="Example about running the test setup of the hybrid handler" +./Hybrid-handler do -c configs/predef_configs/config_TEST.yaml +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 02a6a5e..223b4ad 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,6 +24,7 @@ nav: - Getting started: user/prerequisites.md - The hybrid handler: user/overview.md - Handler configuration file: user/configuration-file.md + - Predefined config files: user/predef-configs.md - Developer Guide: - developer/index.md - Overview: developer/overview.md From e55a3bb9bb14ad964a9d3247c74ecf8896d53cff Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 10:13:37 +0100 Subject: [PATCH 325/549] Fix trailing spaces and end of line at end of config files --- configs/predef_configs/config_AuAu_130.0.yaml | 7 ++----- configs/predef_configs/config_AuAu_200.0.yaml | 7 +++---- configs/predef_configs/config_AuAu_27.0.yaml | 7 +++---- configs/predef_configs/config_AuAu_39.0.yaml | 7 +++---- configs/predef_configs/config_AuAu_4.3.yaml | 6 ++---- configs/predef_configs/config_AuAu_62.4.yaml | 7 +++---- configs/predef_configs/config_AuAu_7.7.yaml | 6 ++---- configs/predef_configs/config_PbPb_17.3.yaml | 7 +++---- configs/predef_configs/config_PbPb_2760.0.yaml | 7 +++---- configs/predef_configs/config_PbPb_5020.0.yaml | 7 +++---- configs/predef_configs/config_PbPb_6.4.yaml | 5 ++--- configs/predef_configs/config_PbPb_8.8.yaml | 5 ++--- configs/predef_configs/config_TEST.yaml | 7 ++----- 13 files changed, 33 insertions(+), 52 deletions(-) diff --git a/configs/predef_configs/config_AuAu_130.0.yaml b/configs/predef_configs/config_AuAu_130.0.yaml index d64307c..e524171 100644 --- a/configs/predef_configs/config_AuAu_130.0.yaml +++ b/configs/predef_configs/config_AuAu_130.0.yaml @@ -7,15 +7,14 @@ IC: Particles: {2212: 79, 2112: 118} #Gold197 Target: Particles: {2212: 79, 2112: 118} #Gold197 - Sqrtsnn: 130.0 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.08 Rg: 1.0 - Rgz: 0.8 + Rgz: 0.8 Sampler: Executable: /path/to/hadron-sampler @@ -23,5 +22,3 @@ Sampler: Afterburner: Executable: /path/to/smash Add_spectators_from_IC: TRUE - - diff --git a/configs/predef_configs/config_AuAu_200.0.yaml b/configs/predef_configs/config_AuAu_200.0.yaml index b0fa882..2bfa7b3 100644 --- a/configs/predef_configs/config_AuAu_200.0.yaml +++ b/configs/predef_configs/config_AuAu_200.0.yaml @@ -7,19 +7,18 @@ IC: Particles: {2212: 79, 2112: 118} #Gold197 Target: Particles: {2212: 79, 2112: 118} #Gold197 - Sqrtsnn: 200.0 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.08 Rg: 1.0 - Rgz: 1.1 + Rgz: 1.1 Sampler: Executable: /path/to/hadron-sampler Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE \ No newline at end of file + Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_27.0.yaml b/configs/predef_configs/config_AuAu_27.0.yaml index bfb6885..87483e0 100644 --- a/configs/predef_configs/config_AuAu_27.0.yaml +++ b/configs/predef_configs/config_AuAu_27.0.yaml @@ -7,19 +7,18 @@ IC: Particles: {2212: 79, 2112: 118} #Gold197 Target: Particles: {2212: 79, 2112: 118} #Gold197 - Sqrtsnn: 27.0 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.12 Rg: 1.0 - Rgz: 0.4 + Rgz: 0.4 Sampler: Executable: /path/to/hadron-sampler Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE \ No newline at end of file + Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_39.0.yaml b/configs/predef_configs/config_AuAu_39.0.yaml index 6fc45e2..e4033a6 100644 --- a/configs/predef_configs/config_AuAu_39.0.yaml +++ b/configs/predef_configs/config_AuAu_39.0.yaml @@ -7,19 +7,18 @@ IC: Particles: {2212: 79, 2112: 118} #Gold197 Target: Particles: {2212: 79, 2112: 118} #Gold197 - Sqrtsnn: 39.0 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.08 Rg: 1.0 - Rgz: 0.3 + Rgz: 0.3 Sampler: Executable: /path/to/hadron-sampler Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE \ No newline at end of file + Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_4.3.yaml b/configs/predef_configs/config_AuAu_4.3.yaml index 0a1f190..7772832 100644 --- a/configs/predef_configs/config_AuAu_4.3.yaml +++ b/configs/predef_configs/config_AuAu_4.3.yaml @@ -7,15 +7,14 @@ IC: Particles: {2212: 79, 2112: 118} #Gold197 Target: Particles: {2212: 79, 2112: 118} #Gold197 - Sqrtsnn: 4.3 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.2 Rg: 1.4 - Rgz: 1.3 + Rgz: 1.3 Sampler: Executable: /path/to/hadron-sampler @@ -23,4 +22,3 @@ Sampler: Afterburner: Executable: /path/to/smash Add_spectators_from_IC: TRUE - diff --git a/configs/predef_configs/config_AuAu_62.4.yaml b/configs/predef_configs/config_AuAu_62.4.yaml index 5091466..5756820 100644 --- a/configs/predef_configs/config_AuAu_62.4.yaml +++ b/configs/predef_configs/config_AuAu_62.4.yaml @@ -7,19 +7,18 @@ IC: Particles: {2212: 79, 2112: 118} #Gold197 Target: Particles: {2212: 79, 2112: 118} #Gold197 - Sqrtsnn: 62.4 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.08 Rg: 1.0 - Rgz: 0.6 + Rgz: 0.6 Sampler: Executable: /path/to/hadron-sampler Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE \ No newline at end of file + Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_7.7.yaml b/configs/predef_configs/config_AuAu_7.7.yaml index 5d6cc25..114bf5f 100644 --- a/configs/predef_configs/config_AuAu_7.7.yaml +++ b/configs/predef_configs/config_AuAu_7.7.yaml @@ -7,15 +7,14 @@ IC: Particles: {2212: 79, 2112: 118} #Gold197 Target: Particles: {2212: 79, 2112: 118} #Gold197 - Sqrtsnn: 7.7 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.2 Rg: 1.4 - Rgz: 1.2 + Rgz: 1.2 Sampler: Executable: /path/to/hadron-sampler @@ -23,4 +22,3 @@ Sampler: Afterburner: Executable: /path/to/smash Add_spectators_from_IC: TRUE - diff --git a/configs/predef_configs/config_PbPb_17.3.yaml b/configs/predef_configs/config_PbPb_17.3.yaml index eb7463b..b75cc02 100644 --- a/configs/predef_configs/config_PbPb_17.3.yaml +++ b/configs/predef_configs/config_PbPb_17.3.yaml @@ -7,19 +7,18 @@ IC: Particles: {2212: 82, 2112: 126} #Lead208 Target: Particles: {2212: 82, 2112: 126} #Lead208 - Sqrtsnn: 17.3 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.15 Rg: 1.4 - Rgz: 0.7 + Rgz: 0.7 Sampler: Executable: /path/to/hadron-sampler Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE \ No newline at end of file + Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_PbPb_2760.0.yaml b/configs/predef_configs/config_PbPb_2760.0.yaml index 8335d35..728bf41 100644 --- a/configs/predef_configs/config_PbPb_2760.0.yaml +++ b/configs/predef_configs/config_PbPb_2760.0.yaml @@ -7,19 +7,18 @@ IC: Particles: {2212: 82, 2112: 126} #Lead208 Target: Particles: {2212: 82, 2112: 126} #Lead208 - Sqrtsnn: 2760.0 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.08 Rg: 1.2 - Rgz: 1.3 + Rgz: 1.3 Sampler: Executable: /path/to/hadron-sampler Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE \ No newline at end of file + Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_PbPb_5020.0.yaml b/configs/predef_configs/config_PbPb_5020.0.yaml index 5cb71d7..855b4d9 100644 --- a/configs/predef_configs/config_PbPb_5020.0.yaml +++ b/configs/predef_configs/config_PbPb_5020.0.yaml @@ -7,19 +7,18 @@ IC: Particles: {2212: 82, 2112: 126} #Lead208 Target: Particles: {2212: 82, 2112: 126} #Lead208 - Sqrtsnn: 5020.0 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.1 Rg: 1.0 - Rgz: 1.3 + Rgz: 1.3 Sampler: Executable: /path/to/hadron-sampler Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE \ No newline at end of file + Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_PbPb_6.4.yaml b/configs/predef_configs/config_PbPb_6.4.yaml index 6adebee..c0b7681 100644 --- a/configs/predef_configs/config_PbPb_6.4.yaml +++ b/configs/predef_configs/config_PbPb_6.4.yaml @@ -7,15 +7,14 @@ IC: Particles: {2212: 82, 2112: 126} #Lead208 Target: Particles: {2212: 82, 2112: 126} #Lead208 - Sqrtsnn: 6.4 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.2 Rg: 1.4 - Rgz: 1.2 + Rgz: 1.2 Sampler: Executable: /path/to/hadron-sampler diff --git a/configs/predef_configs/config_PbPb_8.8.yaml b/configs/predef_configs/config_PbPb_8.8.yaml index 5f4e30c..14d0058 100644 --- a/configs/predef_configs/config_PbPb_8.8.yaml +++ b/configs/predef_configs/config_PbPb_8.8.yaml @@ -7,7 +7,6 @@ IC: Particles: {2212: 82, 2112: 126} #Lead208 Target: Particles: {2212: 82, 2112: 126} #Lead208 - Sqrtsnn: 8.8 Hydro: @@ -15,11 +14,11 @@ Hydro: Software_keys: etaS: 0.2 Rg: 1.4 - Rgz: 1.0 + Rgz: 1.0 Sampler: Executable: /path/to/hadron-sampler Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE \ No newline at end of file + Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_TEST.yaml b/configs/predef_configs/config_TEST.yaml index 85ca6e8..501d98d 100644 --- a/configs/predef_configs/config_TEST.yaml +++ b/configs/predef_configs/config_TEST.yaml @@ -7,16 +7,14 @@ IC: Particles: {2212: 79, 2112: 118} #Gold197 Target: Particles: {2212: 79, 2112: 118} #Gold197 - Sqrtsnn: 7.7 - + Hydro: Executable: /path/to/vHLLE Software_keys: etaS: 0.2 Rg: 1.4 - Rgz: 1.2 - + Rgz: 1.2 nx: 81 ny: 81 nz: 151 @@ -32,4 +30,3 @@ Afterburner: Software_keys: General: Nevents: 50 - From d29d904c31983539556b8f688185df903f05443e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 10:36:14 +0100 Subject: [PATCH 326/549] Fix afterburner test with spectators Since now the python script extracts the information of target and projectile from the IC configuration file, this cannot be simply an empty file in the unit test. The IC base file is used then and copied to the IC output folder. --- tests/unit_tests_Afterburner_functionality.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index c3f0355..f364abc 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -99,7 +99,8 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() return 1 fi rm "${HYBRID_software_output_directory[Afterburner]}/"* - touch "${HYBRID_software_output_directory[IC]}/config.yaml" + cp "${HYBRID_default_configurations_folder}/smash_initial_conditions.yaml" \ + "${HYBRID_software_output_directory[IC]}/config.yaml" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then Print_Error \ From 775ce99681818d3c1ab060557879ea460f001558 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 11:32:21 +0100 Subject: [PATCH 327/549] Add CSS class to documentation to center tables --- docs/stylesheets/extra.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 37e64b9..955c236 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -15,3 +15,13 @@ -webkit-mask-image: var(--md-admonition-icon--config-key); mask-image: var(--md-admonition-icon--config-key); } + +/* Center Markdown Tables (requires md_in_html extension) */ +.center-table { + text-align: center; +} + +.md-typeset .center-table :is(td,th):not([align]) { + /* Reset alignment for table cells */ + text-align: initial; +} From 37fafa680c74ce4553c5db3f25dc4f8c360e5c56 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 11:32:55 +0100 Subject: [PATCH 328/549] Enable content tables in documentation although not yet used --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 223b4ad..e4277f6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,6 +55,8 @@ markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true # Customize tables of contents - toc: permalink: true From 5904d7500d4557e2569dec91bac8592e15d07fe7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 11:33:35 +0100 Subject: [PATCH 329/549] Restyle documentation about predefined configuration files --- docs/user/index.md | 4 +-- docs/user/predef-configs.md | 60 ++++++++++++++++++++----------------- mkdocs.yml | 2 +- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/docs/user/index.md b/docs/user/index.md index 489f63c..4c3fae8 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -7,11 +7,11 @@ The hybrid handler is a :simple-gnubash: **Bash script** and therefore it does n - :white_check_mark:   __Ready to go?__ – [Check it out!] - :sunrise_over_mountains:   __What's the main idea?__ - [Here you go!] - :screwdriver:   __Wanna run?__ - [Write your config file!] -- :bulb:   __Let's try it out!__ - [Use the ready-to-go config files!] +- :bulb:   __Let's try it out!__ - [Use these config files!] [Check it out!]: prerequisites.md [Here you go!]: overview.md [Write your config file!]: configuration-file.md - [Use the ready-to-go config files!]: predef-configs.md + [Use these config files!]: predef-configs.md diff --git a/docs/user/predef-configs.md b/docs/user/predef-configs.md index 4458201..83569ca 100644 --- a/docs/user/predef-configs.md +++ b/docs/user/predef-configs.md @@ -1,41 +1,45 @@ # The predefined configuration files ## Configuring the collision setups -There are several complete handler configuration files prepared for the user to run the hybrid handler in its entirety for different collision systems and energies. -The shear viscosities applied are taken from *Karpenko et al.: Phys.Rev.C 91 (2015)* and the longitudinal and transversal smearing parameters are adjusted to improve agreement with experimental data. -The supported collision systems are: - -| System | $\mathbf{s_{NN} \; (GeV)}$ | -| :---------: | :----------------------------------: | -| Au + Au | 4.3 | -| Pb + Pb | 6.4 | -| Au + Au | 7.7 | -| Pb + Pb | 8.8 | -| Pb + Pb | 17.3 | -| Au + Au | 27.0 | -| Au + Au | 39.0 | -| Au + Au | 62.4 | -| Au + Au | 130.0 | -| Au + Au | 200.0 | -| Pb + Pb | 2760.0 | -| Pb + Pb | 5020.0 | - -They can be found in **predef_configs** subdirectory of the configs folder and the user is only required to insert the paths of the executables in the individual software sections. -They can be executed in the standard manner using *-c* option in the execution mode of the handler, i.e.: +There are several complete handler configuration files prepared for the user to run the hybrid handler in its entirety for different collision systems and energies. +The shear viscosities applied are taken from [:newspaper: *Karpenko et al.: Phys.Rev.C 91 (2015)*](https://journals.aps.org/prc/abstract/10.1103/PhysRevC.91.014906) and the longitudinal and transversal smearing parameters are adjusted to improve agreement with experimental data. +The supported collision systems are listed in the following table. + +
+ +| System | $\mathbf{\sqrt{s_{NN}} \;\: \{GeV\}}$ | System | $\mathbf{\sqrt{s_{NN}} \;\: \{GeV\}}$ | +| :---------: | :------------------------------------: | :---------: | :------------------------------------: | +| Au + Au | 4.3 | Pb + Pb | 6.4 | +| Au + Au | 7.7 | Pb + Pb | 8.8 | +| Au + Au | 27.0 | Pb + Pb | 17.3 | +| Au + Au | 39.0 | Pb + Pb | 2760.0 | +| Au + Au | 62.4 | Pb + Pb | 5020.0 | +| Au + Au | 130.0 ||| +| Au + Au | 200.0 ||| + +
+ +They can be found in :file_folder: **configs/predef_configs** folder and the user is only required to insert the paths of the executables in the individual software sections. +They can be executed in the standard manner using `-c` option in the execution mode of the handler. ``` title="Example about running hybrid handler for a predefined setup: Au+Au collision @ 4.3 GeV" ./Hybrid-handler do -c configs/predef_configs/config_AuAu_4.3.yaml ``` -**Note:** The default predefined setup is meant to be executed on a computing cluster, we do not recommend executing it locally. + +!!! warning "Be aware of computational cost!" + The default predefined setup is meant to be executed on a computing cluster, we do not recommend executing it locally. + If you want to test a simple setup on your local machine, refer to the test setup explained here below. ## Running a test setup -To test the functionality and to also run it on a local computer, there is the possibility to execute a test setup of the hybrid handler, which is a Au+Au collision at sqrt(s) = 7.7 GeV. -The statistics are significantly reduced however and the grid for the hydrodynamic evolution is characterized by large cells, too large for a realistic simulation scenario.
-**This test setup is hence only meant to be used in order to test the functionality of the framework and the embedded scripts, but not for production runs.** +To test the functionality and to also run it on a local computer, there is the possibility to execute a test setup of the hybrid handler, which is a Au+Au collision at $\small\sqrt{s} = 7.7\;\mathrm{GeV}$. +The statistics are significantly reduced and the grid for the hydrodynamic evolution is characterized by large cells, too large for a realistic simulation scenario. + +!!! danger "Don't use this setup for production!" + This test setup is only meant to be used in order to test the functionality of the framework and the embedded scripts, but not to study any realistic physical system. -To execute the test, a predefined configuration file is prepared in the same **predef_configs** subdirectory of the configs folder. Again, the user needs to insert the paths to the executables in all the software sections. +To execute the test, a predefined configuration file is prepared in the same :file_folder: **predef_configs** folder. Again, the user needs to insert the paths to the executables in all the software sections. ``` title="Example about running the test setup of the hybrid handler" -./Hybrid-handler do -c configs/predef_configs/config_TEST.yaml -``` \ No newline at end of file +./Hybrid-handler do -c configs/predef_configs/config_TEST.yaml +``` diff --git a/mkdocs.yml b/mkdocs.yml index e4277f6..093d28a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,7 +24,7 @@ nav: - Getting started: user/prerequisites.md - The hybrid handler: user/overview.md - Handler configuration file: user/configuration-file.md - - Predefined config files: user/predef-configs.md + - Predefined configuration files: user/predef-configs.md - Developer Guide: - developer/index.md - Overview: developer/overview.md From b9ec0b0d50308f9b62c2becd950901b7f639694e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 12:39:33 +0100 Subject: [PATCH 330/549] Make spectator python script fail with non-zero exit code --- python/add_spectators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/add_spectators.py b/python/add_spectators.py index 0cf6890..69a4174 100755 --- a/python/add_spectators.py +++ b/python/add_spectators.py @@ -36,8 +36,8 @@ def get_initial_nucleons_from_config(): N_initial_nucleons += target_particles[particle] except: print("The config file does not contain the necessary information about the projectile and target nuclei.") - sys.exit() - + sys.exit(1) + return N_initial_nucleons From e399ac0f8d46e0c68279ec57b5daf4031764062a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 12:41:02 +0100 Subject: [PATCH 331/549] Make IC black box copy input config to output folder This makes the output configuration realistic (if the input is) and allows the Afterburner stage to run the python script to add spectators, which retrieves projectile and target information from the IC configuration file. --- tests/mocks/smash_IC_black-box.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/mocks/smash_IC_black-box.py b/tests/mocks/smash_IC_black-box.py index 4a18d9a..561f566 100755 --- a/tests/mocks/smash_IC_black-box.py +++ b/tests/mocks/smash_IC_black-box.py @@ -2,6 +2,7 @@ import argparse import os +import shutil import sys import textwrap import time @@ -49,9 +50,8 @@ def validate_output_folder(): print(fatal_error + "Output directory would get overwritten. Select a different output directory, clean up, or tell SMASH to ignore existing files.") sys.exit(1) else: - # create config file - f = open(f_config, "w") - f.close() + # create config file copying input one to output folder + shutil.copy(args.i, f_config) return def run_smash(finalize): From b9334e9d2536c8676fde3b41f91994841088208f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 12:43:20 +0100 Subject: [PATCH 332/549] Fix afterburner functional test using real IC configuration --- tests/functional_tests_Afterburner_only.bash | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index b36433d..3529fad 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -115,7 +115,8 @@ function Functional_Test__do-Afterburner-only() # Expect success and test the add_spectator functionality Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' mkdir -p "IC/${run_id}" - touch "IC/${run_id}/"{config.yaml,SMASH_IC.oscar} "Sampler/${run_id}/particle_lists.oscar" + touch "IC/${run_id}/SMASH_IC.oscar" "Sampler/${run_id}/particle_lists.oscar" + cp "${HYBRIDT_repository_top_level_path}/configs/smash_initial_conditions.yaml" "IC/${run_id}/config.yaml" printf ' Hybrid_handler: Run_ID: %s @@ -133,8 +134,9 @@ function Functional_Test__do-Afterburner-only() # Expect success and test the add_spectator functionality with custom spectator input Print_Info 'Running Hybrid-handler expecting success with the custom add_spectator option' rm -r "IC"/* - mkdir -p test "IC/${run_id}" - touch 'test/SMASH_IC_2.oscar' "IC/${run_id}/config.yaml" + mkdir -p 'test' "IC/${run_id}" + touch 'test/SMASH_IC_2.oscar' + cp "${HYBRIDT_repository_top_level_path}/configs/smash_initial_conditions.yaml" "IC/${run_id}/config.yaml" printf ' Hybrid_handler: Run_ID: %s From 0076a9be9af5c6cb80acb05e10f4f57f65d43f10 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 16:50:22 +0100 Subject: [PATCH 333/549] Use utility function to check file existence, hence fix bug In an error message an unset variable was used. --- bash/configuration_parser.bash | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index fba1b36..e6518d4 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -12,10 +12,9 @@ # and deleting the key from the variable content. function Validate_And_Parse_Configuration_File() { - if [[ ! -f "${HYBRID_configuration_file}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Fatal_And_Exit \ - 'Handler configuration file ' --emph "${filename}" ' not found.' - fi + Ensure_Given_Files_Exist \ + 'A configuration file is needed to run the hybrid handler.' '--' \ + "${HYBRID_configuration_file}" __static__Abort_If_Configuration_File_Is_Not_A_Valid_YAML_File __static__Abort_If_Sections_Are_Violating_Any_Requirement __static__Abort_If_Invalid_Keys_Were_Used From 2e0ba1f4863c99e6ed1e01e1c96fd76399664aa5 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 17:25:42 +0100 Subject: [PATCH 334/549] Add missing end of line --- bash/utility_functions.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 8fa5ba7..ab00951 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -265,7 +265,7 @@ function __static__Check_Given_Files_With() Print_Error -l -- "${add_on_message[@]}" fi if [[ "${error}" = 'INTERNAL' ]]; then - Print_Internal_And_Exit 'This should not have happened.' + Print_Internal_And_Exit '\nThis should not have happened.' else exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ '\nUnable to continue.' From cbf6b22ff052c02809cb63703568f8e5dbf32158 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 17:26:49 +0100 Subject: [PATCH 335/549] Fix missing space in error message before --emph option --- bash/sanity_checks.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index b56281f..826c751 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -100,7 +100,7 @@ function __static__Ensure_Executable_Exists() 'The command ' --emph "${executable}" ' specified for the ' \ --emph "${label}" ' run was not located by the shell.' \ 'Please check your ' --emph 'PATH' ' environment variable and make sure' \ - 'that '--emph "type -P \"${executable}\"" ' succeeds in your terminal.' + 'that ' --emph "type -P \"${executable}\"" ' succeeds in your terminal.' fi } From 073f7eacefc335fc4a52d053780655e3ef807482 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 17:27:17 +0100 Subject: [PATCH 336/549] Improve and actually use function for internal sanity checks --- Hybrid-handler | 1 + bash/sanity_checks.bash | 14 +++++++------- tests/unit_tests_Afterburner_functionality.bash | 2 -- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Hybrid-handler b/Hybrid-handler index d59250d..b113055 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -112,6 +112,7 @@ function Make_Needed_Operations_Depending_On_Execution_Mode() case "${HYBRID_execution_mode}" in do) local software_section + Perform_Internal_Sanity_Checks Validate_And_Parse_Configuration_File Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables Do_Needed_Operations_For_Given_Software diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 826c751..a84827d 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -65,14 +65,14 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var HYBRID_software_input_file } -function Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts() +function Perform_Internal_Sanity_Checks() { - for external_file in "${HYBRID_external_python_scripts[@]}"; do - if [[ ! -f "${external_file}" ]]; then - exit_code=${HYBRID_fatal_file_not_found} Print_Internal_And_Exit \ - 'The python script ' --emph "${external_file}" ' was not found.' - fi - done + Internally_Ensure_Given_Files_Exist \ + 'These Python scripts should be shipped within the hybrid handler codebase.' '--' \ + "${HYBRID_external_python_scripts[@]}" + Internally_Ensure_Given_Files_Exist \ + 'These base configuration files should be shipped within the hybrid handler codebase.' '--' \ + "${HYBRID_software_base_config_file[@]}" } function __static__Ensure_Executable_Exists() diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index f364abc..d9b34a1 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -32,7 +32,6 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() __static__Do_Preliminary_Setup_Operations HYBRID_optional_feature[Add_spectators_from_IC]='FALSE' Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables - Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts } function Unit_Test__Afterburner-create-input-file() @@ -69,7 +68,6 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file-with-sp __static__Do_Preliminary_Setup_Operations HYBRID_optional_feature[Add_spectators_from_IC]='TRUE' Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables - Perform_Sanity_Checks_On_Existence_Of_External_Python_Scripts } function Unit_Test__Afterburner-create-input-file-with-spectators() From 4990a6c7c1bc1db04c97893c62230f544d34a4dc Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 17:38:41 +0100 Subject: [PATCH 337/549] Refactored sanity checks extracting smaller functions --- bash/sanity_checks.bash | 95 +++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index a84827d..77dd040 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -16,49 +16,11 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var HYBRID_software_output_directory[${key}]="${HYBRID_output_directory}/${key}/${HYBRID_run_id}" if Element_In_Array_Equals_To "${key}" "${HYBRID_given_software_sections[@]}"; then __static__Ensure_Executable_Exists "${key}" - printf -v HYBRID_software_configuration_file[${key}] \ - "${HYBRID_software_output_directory[${key}]}/${HYBRID_software_configuration_filename[${key}]}" - # Set here input data file of software if it was not set by user - if [[ ${key} =~ ^(Hydro|Afterburner)$ ]]; then - local filename relative_key - filename="${HYBRID_software_user_custom_input_file[${key}]}" - case "${key}" in - Hydro) - relative_key='IC' - ;; - Afterburner) - relative_key='Sampler' - ;; - esac - if [[ "${filename}" = '' ]]; then - printf -v filename '%s/%s' \ - "${HYBRID_software_output_directory[${relative_key}]}" \ - "${HYBRID_software_default_input_filename[${key}]}" - else - if Element_In_Array_Equals_To "${relative_key}" "${HYBRID_given_software_sections[@]}"; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Requesting custom ' --emph "${key}" ' input file although executing ' \ - --emph "${relative_key}" ' with default output name.' - fi - fi - HYBRID_software_input_file[${key}]="${filename}" - fi + __static__Set_Software_Configuration_File "${key}" + __static__Set_Software_Input_Data_File_If_Not_Set_By_User "${key}" fi done - if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then - if [[ "${HYBRID_optional_feature[Spectators_source]}" != '' ]]; then - HYBRID_software_input_file['Spectators']="${HYBRID_optional_feature[Spectators_source]}" - if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Requesting custom ' --emph 'Spectators' ' input file although executing ' \ - --emph 'IC' ' with default output name.' - fi - else - printf -v HYBRID_software_input_file[Spectators] '%s/%s' \ - "${HYBRID_software_output_directory[IC]}" \ - "${HYBRID_software_default_input_filename[Spectators]}" - fi - fi + __static__Set_Software_Input_Data_File_If_Not_Set_By_User 'Spectators' readonly \ HYBRID_software_output_directory \ HYBRID_software_configuration_file \ @@ -104,4 +66,55 @@ function __static__Ensure_Executable_Exists() fi } +function __static__Set_Software_Configuration_File() +{ + local label=$1 + printf -v HYBRID_software_configuration_file[${label}] \ + "${HYBRID_software_output_directory[${label}]}/${HYBRID_software_configuration_filename[${label}]}" +} + +function __static__Set_Software_Input_Data_File_If_Not_Set_By_User() +{ + local key=$1 + if [[ ${key} =~ ^(Hydro|Afterburner)$ ]]; then + local filename relative_key + filename="${HYBRID_software_user_custom_input_file[${key}]}" + case "${key}" in + Hydro) + relative_key='IC' + ;; + Afterburner) + relative_key='Sampler' + ;; + esac + if [[ "${filename}" = '' ]]; then + printf -v filename '%s/%s' \ + "${HYBRID_software_output_directory[${relative_key}]}" \ + "${HYBRID_software_default_input_filename[${key}]}" + else + if Element_In_Array_Equals_To "${relative_key}" "${HYBRID_given_software_sections[@]}"; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'Requesting custom ' --emph "${key}" ' input file although executing ' \ + --emph "${relative_key}" ' with default output name.' + fi + fi + HYBRID_software_input_file[${key}]="${filename}" + elif [[ "${key}" = 'Spectators' ]]; then + if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then + if [[ "${HYBRID_optional_feature[Spectators_source]}" != '' ]]; then + HYBRID_software_input_file['Spectators']="${HYBRID_optional_feature[Spectators_source]}" + if Element_In_Array_Equals_To "IC" "${HYBRID_given_software_sections[@]}"; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'Requesting custom ' --emph 'Spectators' ' input file although executing ' \ + --emph 'IC' ' with default output name.' + fi + else + printf -v HYBRID_software_input_file[Spectators] '%s/%s' \ + "${HYBRID_software_output_directory[IC]}" \ + "${HYBRID_software_default_input_filename[Spectators]}" + fi + fi + fi +} + Make_Functions_Defined_In_This_File_Readonly From 15d6856ba49bbf99f99879a55ad51c94bb7a8299 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 6 Feb 2024 17:52:41 +0100 Subject: [PATCH 338/549] Remove unused local variable --- bash/sanity_checks.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 77dd040..3816a2b 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -9,7 +9,7 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables() { - local key base_file + local key for key in "${HYBRID_valid_software_configuration_sections[@]}"; do # The software output directories are always ALL set, even if not all software is run. This # is important as some software might rely on files in directories of other workflow blocks. From 76868c83864462763ef5abb7196e198252295149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 8 Feb 2024 12:04:23 +0100 Subject: [PATCH 339/549] Adapt Afterburner input file treatment --- bash/Afterburner_functionality.bash | 5 +++-- bash/sanity_checks.bash | 15 ++++++++++++++ configs/smash_afterburner.yaml | 3 +-- docs/user/configuration-file.md | 2 ++ tests/functional_tests_Afterburner_only.bash | 20 +++++++++++++++++++ tests/mocks/smash_afterburner_black-box.py | 15 +++++++++----- .../unit_tests_Afterburner_functionality.bash | 16 +++++++-------- 7 files changed, 59 insertions(+), 17 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index d1b665c..a7546fa 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -9,6 +9,7 @@ function Prepare_Software_Input_File_Afterburner() { + Ensure_Consistency_Of_Afterburner_Input Create_Output_Directory_For 'Afterburner' Ensure_Given_Files_Do_Not_Exist "${HYBRID_software_configuration_file[Afterburner]}" Ensure_Given_Files_Exist "${HYBRID_software_base_config_file[Afterburner]}" @@ -23,7 +24,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() Ensure_Given_Files_Exist \ "${HYBRID_software_configuration_file[Afterburner]}" \ "${HYBRID_software_input_file[Afterburner]}" - Internally_Ensure_Given_Files_Exist "${HYBRID_software_output_directory[Afterburner]}/sampling0" + Internally_Ensure_Given_Files_Exist "${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" } function Ensure_Run_Reproducibility_Afterburner() @@ -48,7 +49,7 @@ function Run_Software_Afterburner() function __static__Create_Sampled_Particles_List_File_Or_Symbolic_Link_With_Or_Without_Spectators() { - local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampling0" + local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then Ensure_Given_Files_Do_Not_Exist "${target_link_name}" # Here the config.yaml file is expected to be produced by SMASH in the output folder diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 3816a2b..2fd9c39 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -117,4 +117,19 @@ function __static__Set_Software_Input_Data_File_If_Not_Set_By_User() fi } +function Ensure_Consistency_Of_Afterburner_Input() +{ + local config_section_software_keys="$(yq eval 'select(.Afterburner.Software_keys != null \ + and .Afterburner.Software_keys.Modi.List != null and .Afterburner.Software_keys.Modi.List.Filename != null) \ + | .Afterburner.Software_keys.Modi.List.Filename' ${HYBRID_configuration_file})" + local config_section_input="$(yq eval 'select(.Afterburner.Input_file != null) \ + .Afterburner.Input_file' ${HYBRID_configuration_file})" + if [[ "${config_section_software_keys}" != '' && + "${config_section_software_keys}" != 'sampled_particles_list.oscar' && + "${config_section_software_keys}" != "${config_section_input}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_key!' + fi +} + Make_Functions_Defined_In_This_File_Readonly diff --git a/configs/smash_afterburner.yaml b/configs/smash_afterburner.yaml index 7d99606..905e748 100644 --- a/configs/smash_afterburner.yaml +++ b/configs/smash_afterburner.yaml @@ -16,5 +16,4 @@ Output: Modi: List: File_Directory: "." - File_Prefix: "sampling" - Shift_Id: 0 + Filename: "sampled_particles_list.oscar" \ No newline at end of file diff --git a/docs/user/configuration-file.md b/docs/user/configuration-file.md index c533bda..63bd42d 100644 --- a/docs/user/configuration-file.md +++ b/docs/user/configuration-file.md @@ -112,6 +112,8 @@ Sampler: As other stages, the afterburner run needs an additional input file as well, one which contains the sampled particles list. This is the main output of the previous sampler stage and, therefore, if not specified, a *particle_lists.oscar* file is expected to exist in the ***Sampler*** output sub-folder with the same `Run_ID`. However, using this key, any file can be specified and used. + Note that although it is possible to specify the input for the List Modus in SMASH via the `Software_keys`, this is not allowed here + and will throw an error. Always specify the input to the Afterburner stage here, if needed. ???+ config-key "`Add_spectators_from_IC`" diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 3529fad..0f67ea5 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -112,6 +112,26 @@ function Functional_Test__do-Afterburner-only() Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi + mv 'Afterburner' 'Afterburner-failure-custom-input' + # Expect failure when wrongly specifying custom input + printf ' + Hybrid_handler: + Run_ID: %s + Afterburner: + Executable: %s/mocks/smash_afterburner_black-box.py + Software_keys: + Modi: + List: + File_Directory: "." + Filename: "particle_lists_2.oscar" + ' "${run_id}" "${HYBRIDT_tests_folder}" > "${config_filename}" + Print_Info 'Running Hybrid-handler expecting failure' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null + if [[ $? -ne 110 ]]; then + Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' + return 1 + fi + mv 'Afterburner' 'Afterburner-failure-custom-input' # Expect success and test the add_spectator functionality Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' mkdir -p "IC/${run_id}" diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 622fdae..92e2b23 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -59,8 +59,7 @@ def ensure_no_output_is_overwritten(): def run_smash(finalize,SMASH_input_file_with_participants_and_spectators,sampler_dir): # create smash.lock file f = open(args.o+file_name_is_running, "w") - extension_pattern = r"\d+" # Matches one or more digits at the end of the filename - regex=re.compile(SMASH_input_file_with_participants_and_spectators+extension_pattern) + regex=re.compile(SMASH_input_file_with_participants_and_spectators) # Get a list of files in the current directory files = os.listdir(sampler_dir) # Filter files that match the base name and have only integer extensions @@ -134,13 +133,19 @@ def parse_command_line_config_options(): print("File directory could not be parsed") sys.exit(1) + try: + file=data_config['Modi']['List']['Filename'] + except: + print("Filename could not be parsed") + sys.exit(1) + if not os.path.isdir(sampler_dir): print("Directory '{0}' not found".format(sampler_dir)) sys.exit(1) if sampler_dir != "/": sampler_dir += "/" - return sampler_dir + return sampler_dir, file if __name__ == '__main__': @@ -170,11 +175,11 @@ def parse_command_line_config_options(): # initialize the system check_config(config_is_valid) - sampler_dir=parse_command_line_config_options() + sampler_dir, file =parse_command_line_config_options() create_folders_structure() ensure_no_output_is_overwritten() - SMASH_input_file_with_participants_and_spectators = sampler_dir+"sampling" + SMASH_input_file_with_participants_and_spectators = sampler_dir+file SMASH_output_file_with_participants_and_spectators = args.o+name_particles_file+name_oscar SMASH_special_output_file_for_vHLLE_with_participants_only = args.o+name_particles_file+name_bin diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index d9b34a1..944e1d4 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -40,7 +40,7 @@ function Unit_Test__Afterburner-create-input-file() mkdir -p "${HYBRID_software_output_directory[Sampler]}" local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ - plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" + plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" touch "${plist_Sampler}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then @@ -79,7 +79,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" \ - plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampling0" + plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then @@ -145,7 +145,7 @@ function Unit_Test__Afterburner-check-all-input() return 1 fi touch \ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" \ + "${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" \ "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then @@ -154,16 +154,16 @@ function Unit_Test__Afterburner-check-all-input() 'although all files were provided.' return 1 fi - rm "${HYBRID_software_output_directory[Afterburner]}/sampling0" - touch "${HYBRID_software_output_directory[Sampler]}/original_sampling0" - ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampling0" \ - "${HYBRID_software_output_directory[Afterburner]}/sampling0" + rm "${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" + touch "${HYBRID_software_output_directory[Sampler]}/original_sampled_particles_list.oscar" + ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampled_particles_list.oscar" \ + "${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing file unexpectedly failed.' return 1 fi - rm "${HYBRID_software_output_directory[Sampler]}/original_sampling0" + rm "${HYBRID_software_output_directory[Sampler]}/original_sampled_particles_list.oscar" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of a link to a non-existing file unexpectedly succeeded.' From bf5b83ae0c1edf7326847330e7de836a2ab5e4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 8 Feb 2024 12:06:33 +0100 Subject: [PATCH 340/549] Adapt needed SMASH version --- docs/user/prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/prerequisites.md b/docs/user/prerequisites.md index 8bb0966..cd73b5b 100644 --- a/docs/user/prerequisites.md +++ b/docs/user/prerequisites.md @@ -4,7 +4,7 @@ | Software | Required version | | :------: | :--------------: | -| [SMASH](https://github.com/smash-transport/smash) | 1.8 or higher | +| [SMASH](https://github.com/smash-transport/smash) | 3.1 or higher | | [vHLLE](https://github.com/yukarpenko/vhlle) | - | | [vHLLE parameters](https://github.com/yukarpenko/vhlle_params) | - | | [Hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) | 1.0 or higher | From 2255e7c2c32b78497dd400c74b029da08148980d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 8 Feb 2024 15:36:10 +0100 Subject: [PATCH 341/549] Rename terminal output, deal with standard error, append on overwriting --- bash/Afterburner_functionality.bash | 4 +-- bash/Hydro_functionality.bash | 7 +++-- bash/IC_functionality.bash | 4 +-- bash/Sampler_functionality.bash | 5 ++-- bash/common_functionality.bash | 9 ++++++ bash/global_variables.bash | 6 ++++ tests/functional_tests_Afterburner_only.bash | 2 +- tests/functional_tests_Hydro_only.bash | 4 +-- tests/functional_tests_IC_only.bash | 2 +- tests/functional_tests_Sampler_only.bash | 2 +- .../unit_tests_Afterburner_functionality.bash | 2 +- tests/unit_tests_Hydro_functionality.bash | 2 +- tests/unit_tests_IC_functionality.bash | 2 +- tests/unit_tests_Sampler_functionality.bash | 2 +- ...it_tests_software_input_functionality.bash | 30 +++++++++++++++++++ 15 files changed, 65 insertions(+), 18 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index d1b665c..c4ea35d 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -15,6 +15,7 @@ function Prepare_Software_Input_File_Afterburner() Copy_Base_Configuration_To_Output_Folder_For 'Afterburner' Replace_Keys_In_Configuration_File_If_Needed_For 'Afterburner' __static__Create_Sampled_Particles_List_File_Or_Symbolic_Link_With_Or_Without_Spectators + Separate_Terminal_Output_For 'Afterburner' } function Ensure_All_Needed_Input_Exists_Afterburner() @@ -36,12 +37,11 @@ function Ensure_Run_Reproducibility_Afterburner() function Run_Software_Afterburner() { cd "${HYBRID_software_output_directory[Afterburner]}" - local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" "${HYBRID_software_executable[Afterburner]}" \ '-i' "${HYBRID_software_configuration_file[Afterburner]}" \ '-o' "${HYBRID_software_output_directory[Afterburner]}" \ '-n' \ - >> "${afterburner_terminal_output}" + >> "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_terminal_output[Afterburner]}" 2>&1 } #=============================================================================== diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 5e2d2da..86df3e0 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -16,6 +16,7 @@ function Prepare_Software_Input_File_Hydro() Replace_Keys_In_Configuration_File_If_Needed_For 'Hydro' __static__Create_Symbolic_Link_To_IC_File __static__Create_Symbolic_Link_To_EOS_Folder + Separate_Terminal_Output_For 'Hydro' } function Ensure_All_Needed_Input_Exists_Hydro() @@ -39,12 +40,12 @@ function Run_Software_Hydro() cd "${HYBRID_software_output_directory[Hydro]}" local -r \ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ - ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" \ - hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" + ic_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" "${HYBRID_software_executable[Hydro]}" \ "-params" "${hydro_config_file_path}" \ "-ISinput" "${ic_output_file_path}" \ - "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> "${hydro_terminal_output}" + "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> \ + "${HYBRID_software_output_directory[Hydro]}/${HYBRID_terminal_output[Hydro]}" 2>&1 } #=============================================================================== diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index bdfef7b..faf4f46 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -14,6 +14,7 @@ function Prepare_Software_Input_File_IC() Ensure_Given_Files_Exist "${HYBRID_software_base_config_file[IC]}" Copy_Base_Configuration_To_Output_Folder_For 'IC' Replace_Keys_In_Configuration_File_If_Needed_For 'IC' + Separate_Terminal_Output_For 'IC' } function Ensure_All_Needed_Input_Exists_IC() @@ -32,12 +33,11 @@ function Ensure_Run_Reproducibility_IC() function Run_Software_IC() { cd "${HYBRID_software_output_directory[IC]}" - local ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" "${HYBRID_software_executable[IC]}" \ '-i' "${HYBRID_software_configuration_file[IC]}" \ '-o' "${HYBRID_software_output_directory[IC]}" \ '-n' \ - >> "${ic_terminal_output}" + >> "${HYBRID_software_output_directory[IC]}/${HYBRID_terminal_output[IC]}" 2>&1 } Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 16b53cb..1edaabf 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -17,6 +17,7 @@ function Prepare_Software_Input_File_Sampler() __static__Validate_Sampler_Config_File __static__Transform_Relative_Paths_In_Sampler_Config_File __static__Create_Superfluous_Symbolic_Link_To_Freezeout_File + Separate_Terminal_Output_For 'Sampler' } function Ensure_All_Needed_Input_Exists_Sampler() @@ -38,10 +39,10 @@ function Ensure_Run_Reproducibility_Sampler() function Run_Software_Sampler() { local -r sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" - local sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" cd "${HYBRID_software_output_directory[Sampler]}" "${HYBRID_software_executable[Sampler]}" 'events' '1' \ - "${sampler_config_file_path}" >> "${sampler_terminal_output}" + "${sampler_config_file_path}" >> \ + "${HYBRID_software_output_directory[Sampler]}/${HYBRID_terminal_output[Sampler]}" 2>&1 } #=============================================================================== diff --git a/bash/common_functionality.bash b/bash/common_functionality.bash index f93a098..1b1e3ff 100644 --- a/bash/common_functionality.bash +++ b/bash/common_functionality.bash @@ -39,3 +39,12 @@ function Replace_Keys_In_Configuration_File_If_Needed_For() "${HYBRID_software_new_input_keys[$1]}" fi } + +function Separate_Terminal_Output_For() +{ + if [[ -e "${HYBRID_software_output_directory[$1]}/${HYBRID_terminal_output[$1]}" ]]; then + local timestamp=$(date +'%d-%m-%Y %H:%M:%S') + printf '\n\n\n===== NEW RUN OUTPUT =====\n===== %s =====\n\n\n' "${timestamp}" >> \ + "${HYBRID_software_output_directory[$1]}/${HYBRID_terminal_output[$1]}" + fi +} diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 757841a..90ada3a 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -128,6 +128,12 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) + declare -gA HYBRID_terminal_output=( + [IC]='IC.log' + [Hydro]='Hydro.log' + [Sampler]='Sampler.log' + [Afterburner]='Afterburner.log' + ) declare -gA HYBRID_software_configuration_file=( [IC]='' [Hydro]='' diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 3529fad..21fdd6b 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -44,7 +44,7 @@ function Functional_Test__do-Afterburner-only() mv 'Afterburner' 'Afterburner-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' - terminal_output_file="Afterburner/${run_id}/Terminal_Output.txt" + terminal_output_file="Afterburner/${run_id}/Afterburner.log" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index d940607..341fbaf 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -36,7 +36,7 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-success' # Expect failure when giving an invalid IC output Print_Info 'Running Hybrid-handler expecting invalid IC argument' - terminal_output_file="Hydro/${run_id}/Terminal_Output.txt" + terminal_output_file="Hydro/${run_id}/Hydro.log" BLACK_BOX_FAIL='invalid_input' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then @@ -74,7 +74,7 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-success-custom-input' # Expect failure when an invalid config was supplied Print_Info 'Running Hybrid-handler expecting invalid config argument' - terminal_output_file="Hydro/${run_id}/Terminal_Output.txt" + terminal_output_file="Hydro/${run_id}/Hydro.log" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index d5781af..c9de8ee 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -31,7 +31,7 @@ function Functional_Test__do-IC-only() mv 'IC' 'IC-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid IC input file failure' - terminal_output_file="IC/${run_id}/Terminal_Output.txt" + terminal_output_file="IC/${run_id}/IC.log" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index c0c9823..24f73c8 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -33,7 +33,7 @@ function Functional_Test__do-Sampler-only() mv 'Sampler' 'Sampler-success' # Expect failure and test terminal output local terminal_output_file error_message - terminal_output_file="Sampler/${run_id}/Terminal_Output.txt" + terminal_output_file="Sampler/${run_id}/Sampler.log" Print_Info 'Running Hybrid-handler expecting crash in Sampler' BLACK_BOX_FAIL='true' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index d9b34a1..5cdd440 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -184,7 +184,7 @@ function Make_Test_Preliminary_Operations__Afterburner-test-run-software() function Unit_Test__Afterburner-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Afterburner]}" - local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Terminal_Output.txt" + local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Afterburner.log" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Afterburner if [[ ! -f "${afterburner_terminal_output}" ]]; then diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index f21a921..6891bf1 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -156,7 +156,7 @@ function Unit_Test__Hydro-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" local -r \ - hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Terminal_Output.txt" \ + hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Hydro.log" \ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ IC_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" local terminal_output_result correct_result diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index b231977..c1562f0 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -93,7 +93,7 @@ function Make_Test_Preliminary_Operations__IC-test-run-software() function Unit_Test__IC-test-run-software() { - local -r ic_terminal_output="${HYBRID_software_output_directory[IC]}/Terminal_Output.txt" + local -r ic_terminal_output="${HYBRID_software_output_directory[IC]}/IC.log" mkdir -p "${HYBRID_software_output_directory[IC]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_IC diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index f178127..dce0f62 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -213,7 +213,7 @@ function Make_Test_Preliminary_Operations__Sampler-test-run-software() function Unit_Test__Sampler-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Sampler]}" - local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Terminal_Output.txt" \ + local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Sampler.log" \ sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Sampler diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 32a7a3d..b7e0d00 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -240,3 +240,33 @@ function Unit_Test__copy-hybrid-handler-config-section() rm "${HYBRID_handler_config_section_filename[IC]}" done } + +function Unit_Test__add-section-terminal-output() +{ + local -r \ + config_filename='IC_config.yaml' \ + run_id='IC_only' + printf ' + Hybrid_handler: + Run_ID: %s + IC: + Executable: %s/tests/mocks/smash_IC_black-box.py + ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" + mkdir -p "IC/${run_id}" + touch "IC/${run_id}/IC.log" + printf "first line" >> "IC/${run_id}/IC.log" + Print_Info 'Running Hybrid-handler expecting success' + ("${HYBRIDT_repository_top_level_path}/Hybrid-handler" 'do' '-c' "${config_filename}") + if [[ $? -ne 0 ]]; then + Print_Error 'Hybrid-handler unexpectedly failed.' + return 1 + fi + local -r \ + string_to_be_found="first line===== NEW RUN OUTPUT =====" \ + string_to_search=$(head -n 5 "IC/${run_id}/IC.log" | tr -d '\n') + + if ! [[ "$string_to_search" == "$string_to_be_found"*"=====" ]]; then + Print_Error 'Terminal output file not properly separated.' + return 1 + fi +} From 3b1fce138301b39b21c1c25ed94436ffc0ef44ca Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Feb 2024 17:23:07 +0100 Subject: [PATCH 342/549] Add Git as requirement with very old version The minimum version that was required is 1.8.5 as this is the first which supports the -C command line option which we heavily use in the codebase. --- bash/system_requirements.bash | 10 ++++++++-- tests/unit_tests_system_requirements.bash | 11 +++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index a536e09..23806bc 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -21,6 +21,7 @@ function __static__Declare_System_Requirements() declare -rgA HYBRID_versions_requirements=( [awk]='4.1' [bash]='4.4' + [git]='1.8.5' [sed]='4.2.1' [tput]='5.7' [yq]='4.18.1' @@ -310,8 +311,10 @@ function __static__Try_Find_Version() fi local found_version case "$1" in - awk | sed) + awk | git | sed) found_version=$($1 --version) + ;;& # Continue matching other cases + awk | sed) found_version=$(__static__Get_First_Line_From_String "${found_version}") found_version=$(grep -oE "${HYBRID_version_regex}" <<< "${found_version}") found_version=$(__static__Get_First_Line_From_String "${found_version}") @@ -320,6 +323,9 @@ function __static__Try_Find_Version() found_version="${BASH_VERSINFO[@]:0:3}" found_version="${found_version// /.}" ;; + git) + found_version=$(grep -oE "${HYBRID_version_regex}" <<< "${found_version}") + ;; tput) found_version=$(tput -V | grep -oE "${HYBRID_version_regex}") found_version=(${found_version//./ }) # Use word split to separate version numbers diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index ab680e1..69d8e75 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -30,6 +30,11 @@ function __static__Inhibit_Commands_Version() __static__Fake_Command_Version \ '--version' "${gnu} Awk ${awk_version}, API: 3.0 (${gnu} MPFR 4.1.0, ${gnu} MP 6.2.1)" "$@" } + function git() + { + __static__Fake_Command_Version \ + '--version' "git version ${git_version}" "$@" + } function sed() { __static__Fake_Command_Version \ @@ -50,9 +55,10 @@ function __static__Inhibit_Commands_Version() function Unit_Test__system-requirements() { __static__Inhibit_Commands_Version - local gnu {awk,sed,tput,yq}_version + local gnu {awk,git,sed,tput,yq}_version gnu='GNU' awk_version=4.1 + git_version=2.0 sed_version=4.2.1 tput_version=5.9 yq_version=4.18.1 @@ -68,6 +74,7 @@ function Unit_Test__system-requirements() fi gnu='BSD' awk_version=4.1.0 + git_version=1.8.3 sed_version=4.2.0 tput_version='' yq_version=3.9.98 From 7b9152a781fb3e5061960dd1efa7fed4adb566d8 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 1 Feb 2024 17:24:02 +0100 Subject: [PATCH 343/549] Simplify code due to new Git system requirement --- bash/version.bash | 9 +++------ tests/functional_tests_version.bash | 24 ++++++++++-------------- tests/unit_tests_version.bash | 14 ++++++-------- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/bash/version.bash b/bash/version.bash index 67eaead..050d23b 100644 --- a/bash/version.bash +++ b/bash/version.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -10,11 +10,8 @@ function Print_Software_Version() { Ensure_That_Given_Variables_Are_Set HYBRID_codebase_version - # First handle cases where git is not available, or codebase downloaded as archive and not cloned - # NOTE: Git introduced -C option in version 1.8.5 - if ! hash git &> /dev/null \ - || __static__Is_Git_Version_Older_Than '1.8.3' \ - || ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null; then + # First handle if the codebase was downloaded as archive and not cloned + if ! git -C "${HYBRID_top_level_path}" rev-parse --is-inside-work-tree &> /dev/null; then __static__Print_Pretty_Version_Line "${HYBRID_codebase_version}" return 0 fi diff --git a/tests/functional_tests_version.bash b/tests/functional_tests_version.bash index dfda8d1..9843ab5 100644 --- a/tests/functional_tests_version.bash +++ b/tests/functional_tests_version.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -14,19 +14,15 @@ function Functional_Test__version() if [[ $? -ne 0 ]]; then Print_Fatal_And_Exit 'Execution of version mode unexpectedly failed.' fi - if ! hash git &> /dev/null; then - Print_Warning 'Command ' --emph 'git' ' not available, part of test cannot be run.' + git_describe=$( + cd "${HYBRIDT_repository_top_level_path}" + git describe --abbrev=0 + ) + printf "${version_output}\n" + if [[ $(grep -c "${git_describe}" <<< "${version_output}") -gt 0 ]]; then + return 0 else - git_describe=$( - cd "${HYBRIDT_repository_top_level_path}" - git describe --abbrev=0 - ) - printf "${version_output}\n" - if [[ $(grep -c "${git_describe}" <<< "${version_output}") -gt 0 ]]; then - return 0 - else - Print_Error 'Version string is not containing ' --emph "${git_describe}" ' as expected.' - return 1 - fi + Print_Error 'Version string is not containing ' --emph "${git_describe}" ' as expected.' + return 1 fi } diff --git a/tests/unit_tests_version.bash b/tests/unit_tests_version.bash index a81ec87..38397e3 100644 --- a/tests/unit_tests_version.bash +++ b/tests/unit_tests_version.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -26,12 +26,10 @@ function Unit_Test__version() Print_Error "Version printing without git available failed." return 1 fi - if hash git &> /dev/null; then - # We want to capture here a logger message that goes to fd 9 - std_output=$(Call_Codebase_Function Print_Software_Version 9>&1) - if [[ $? -ne 0 || $(grep -c "${HYBRID_codebase_version}" <<< "${std_output}") -eq 0 ]]; then - Print_Error "Version printing with git failed." - return 1 - fi + # We want to capture here a logger message that goes to fd 9 + std_output=$(Call_Codebase_Function Print_Software_Version 9>&1) + if [[ $? -ne 0 || $(grep -c "${HYBRID_codebase_version}" <<< "${std_output}") -eq 0 ]]; then + Print_Error "Version printing with git failed." + return 1 fi } From 2b3e5620d4150affc276ff4fc79176e5e84bfd46 Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Wed, 14 Feb 2024 14:07:45 +0100 Subject: [PATCH 344/549] Add G.Inghirami to Authors list --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index cafd8a4..fa944a0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -3,6 +3,7 @@ | Name | E-mail | | :---: | :----: | | Alessandro Sciarra | asciarra@fias.uni-frankfurt.de | +| Gabriele Inghirami | g.inghirami@gsi.de | | Jan Hammelmann | hammelmann@fias.uni-frankfurt.de | | Niklas Götz | goetz@fias.uni-frankfurt.de | | Nils Saß | nsass@fias.uni-frankfurt.de | From 4952aebef398d2b373cfe152904a44ae6f07302d Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Thu, 15 Feb 2024 10:06:34 +0100 Subject: [PATCH 345/549] Update documentation for ROOT requirement --- docs/user/prerequisites.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/user/prerequisites.md b/docs/user/prerequisites.md index 8bb0966..afdce53 100644 --- a/docs/user/prerequisites.md +++ b/docs/user/prerequisites.md @@ -12,8 +12,10 @@ Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. -The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in the :material-file: _CMakeLists.txt_ file of each submodule. -It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. +!!! warning "Be consistent w.r.t dependencies" + The above prerequisites have in general additional dependencies and it is important to be consistent in compiler options when compiling these and the software itself. + We particularly highlight that the newer versions of ROOT require C++17 bindings or higher, which calls for proper treatment of compiler options in SMASH and the hadron sampler. + It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. # Unix system requirements From 2b7e244404945ffae8a380f0db217369fec7875f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Feb 2024 10:55:35 +0100 Subject: [PATCH 346/549] Fix indentation in test predefined config file --- configs/predef_configs/config_TEST.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/predef_configs/config_TEST.yaml b/configs/predef_configs/config_TEST.yaml index 501d98d..5501e7a 100644 --- a/configs/predef_configs/config_TEST.yaml +++ b/configs/predef_configs/config_TEST.yaml @@ -28,5 +28,5 @@ Afterburner: Executable: /path/to/smash Add_spectators_from_IC: TRUE Software_keys: - General: - Nevents: 50 + General: + Nevents: 50 From d087f915cfc060ea45b08d3a55acbbb482754dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 14:33:11 +0100 Subject: [PATCH 347/549] Rephrase documentation. Co-authored-by: Alessandro Sciarra --- docs/user/configuration-file.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/configuration-file.md b/docs/user/configuration-file.md index 63bd42d..ead5c66 100644 --- a/docs/user/configuration-file.md +++ b/docs/user/configuration-file.md @@ -112,8 +112,8 @@ Sampler: As other stages, the afterburner run needs an additional input file as well, one which contains the sampled particles list. This is the main output of the previous sampler stage and, therefore, if not specified, a *particle_lists.oscar* file is expected to exist in the ***Sampler*** output sub-folder with the same `Run_ID`. However, using this key, any file can be specified and used. - Note that although it is possible to specify the input for the List Modus in SMASH via the `Software_keys`, this is not allowed here - and will throw an error. Always specify the input to the Afterburner stage here, if needed. + Note that although it is possible to specify the input for the list modus in SMASH via the `Software_keys`, this is not allowed here and will result in an error. + Always specify the customized input file for the afterburner stage using this key, if needed. ???+ config-key "`Add_spectators_from_IC`" From adc965f6fff96c91974d6dafb76a045bdbe35020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 14:46:46 +0100 Subject: [PATCH 348/549] Small cleanup in documentation and mocks --- configs/smash_afterburner.yaml | 2 +- docs/user/prerequisites.md | 4 +++- tests/mocks/smash_afterburner_black-box.py | 22 ++++++---------------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/configs/smash_afterburner.yaml b/configs/smash_afterburner.yaml index 905e748..3c78131 100644 --- a/configs/smash_afterburner.yaml +++ b/configs/smash_afterburner.yaml @@ -16,4 +16,4 @@ Output: Modi: List: File_Directory: "." - Filename: "sampled_particles_list.oscar" \ No newline at end of file + Filename: "sampled_particles_list.oscar" diff --git a/docs/user/prerequisites.md b/docs/user/prerequisites.md index cd73b5b..f892579 100644 --- a/docs/user/prerequisites.md +++ b/docs/user/prerequisites.md @@ -4,12 +4,14 @@ | Software | Required version | | :------: | :--------------: | -| [SMASH](https://github.com/smash-transport/smash) | 3.1 or higher | +| [SMASH](https://github.com/smash-transport/smash) | 3.1 or higher[^1] | | [vHLLE](https://github.com/yukarpenko/vhlle) | - | | [vHLLE parameters](https://github.com/yukarpenko/vhlle_params) | - | | [Hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) | 1.0 or higher | | [Python](https://www.python.org) | 3.0 or higher | +[^1]: 3.1 is only needed for Afterburner functionalities. Otherwise 1.8 is sufficient. + Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. The newer versions of ROOT require C++17 bindings or higher, so please make sure to compile SMASH, ROOT, and the sampler with the same compiler utilizing the same compiler flags, which can be adjusted in the :material-file: _CMakeLists.txt_ file of each submodule. diff --git a/tests/mocks/smash_afterburner_black-box.py b/tests/mocks/smash_afterburner_black-box.py index 92e2b23..783e064 100755 --- a/tests/mocks/smash_afterburner_black-box.py +++ b/tests/mocks/smash_afterburner_black-box.py @@ -59,22 +59,12 @@ def ensure_no_output_is_overwritten(): def run_smash(finalize,SMASH_input_file_with_participants_and_spectators,sampler_dir): # create smash.lock file f = open(args.o+file_name_is_running, "w") - regex=re.compile(SMASH_input_file_with_participants_and_spectators) - # Get a list of files in the current directory - files = os.listdir(sampler_dir) - # Filter files that match the base name and have only integer extensions - matching_files = [file_in for file_in in files if regex.match(sampler_dir+file_in)] - if(len(matching_files)>0): - try: - for match in matching_files: - f_in=open(sampler_dir+match,"r") - f_in.close() - print("File read") - except: - print(fatal_error+"Sampled particle list could not be opened") - sys.exit(1) - else: - print(fatal_error+"Sampled particle list could not be found") + try: + f_in=open(sampler_dir+SMASH_input_file_with_participants_and_spectators,"r") + f_in.close() + print("File read") + except: + print(fatal_error+"Sampled particle list could not be opened") sys.exit(1) f.close() # open unfinished particle files From 01ae41f25579adb7e8b662280a53a573c143b58f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 15:48:56 +0100 Subject: [PATCH 349/549] Rephrase sanity check of Afterburner config and create global variable for input file name --- bash/Afterburner_functionality.bash | 5 +++-- bash/global_variables.bash | 1 + bash/sanity_checks.bash | 16 +++++++------- tests/functional_tests_Afterburner_only.bash | 22 ++++++++++++++++++- .../unit_tests_Afterburner_functionality.bash | 16 +++++++------- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index a7546fa..835a981 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -24,7 +24,8 @@ function Ensure_All_Needed_Input_Exists_Afterburner() Ensure_Given_Files_Exist \ "${HYBRID_software_configuration_file[Afterburner]}" \ "${HYBRID_software_input_file[Afterburner]}" - Internally_Ensure_Given_Files_Exist "${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" + Internally_Ensure_Given_Files_Exist \ + "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" } function Ensure_Run_Reproducibility_Afterburner() @@ -49,7 +50,7 @@ function Run_Software_Afterburner() function __static__Create_Sampled_Particles_List_File_Or_Symbolic_Link_With_Or_Without_Spectators() { - local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" + local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then Ensure_Given_Files_Do_Not_Exist "${target_link_name}" # Here the config.yaml file is expected to be produced by SMASH in the output folder diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 757841a..c1802c2 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -28,6 +28,7 @@ function Define_Further_Global_Variables() ) readonly HYBRID_default_configurations_folder="${HYBRID_top_level_path}/configs" readonly HYBRID_python_folder="${HYBRID_top_level_path}/python" + readonly HYBRID_Afterburner_list_filename="sampled_particles_list.oscar" declare -rgA HYBRID_external_python_scripts=( [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" ) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 2fd9c39..a243750 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -119,14 +119,14 @@ function __static__Set_Software_Input_Data_File_If_Not_Set_By_User() function Ensure_Consistency_Of_Afterburner_Input() { - local config_section_software_keys="$(yq eval 'select(.Afterburner.Software_keys != null \ - and .Afterburner.Software_keys.Modi.List != null and .Afterburner.Software_keys.Modi.List.Filename != null) \ - | .Afterburner.Software_keys.Modi.List.Filename' ${HYBRID_configuration_file})" - local config_section_input="$(yq eval 'select(.Afterburner.Input_file != null) \ - .Afterburner.Input_file' ${HYBRID_configuration_file})" - if [[ "${config_section_software_keys}" != '' && - "${config_section_software_keys}" != 'sampled_particles_list.oscar' && - "${config_section_software_keys}" != "${config_section_input}" ]]; then + if Has_YAML_String_Given_Key 'Afterburner' 'Software_keys.Modi.List.Filename' "${HYBRID_configuration_file}"; then + if [[ $(Read_From_YAML_String_Given_Key 'Afterburner' 'Software_keys.Modi.List.Filename' \ + "${HYBRID_configuration_file}") != "${config_section_input}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_key!' + fi + fi + if Has_YAML_String_Given_Key 'Afterburner' 'Software_keys.Modi.List.Shift_ID' "${HYBRID_configuration_file}"; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_key!' fi diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 0f67ea5..311510c 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -131,7 +131,27 @@ function Functional_Test__do-Afterburner-only() Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi - mv 'Afterburner' 'Afterburner-failure-custom-input' + mv 'Afterburner' 'Afterburner-failure-wrong-specified-custom-input-shift-id' + # Expect failure when wrongly specifying custom input + printf ' + Hybrid_handler: + Run_ID: %s + Afterburner: + Executable: %s/mocks/smash_afterburner_black-box.py + Software_keys: + Modi: + List: + File_Directory: "." + File_Prefix: "sampling" + Shift_Id: 0 + ' "${run_id}" "${HYBRIDT_tests_folder}" > "${config_filename}" + Print_Info 'Running Hybrid-handler expecting failure' + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null + if [[ $? -ne 110 ]]; then + Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' + return 1 + fi + mv 'Afterburner' 'Afterburner-failure-wrong-specified-custom-input-shift-id' # Expect success and test the add_spectator functionality Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' mkdir -p "IC/${run_id}" diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 944e1d4..330bc58 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -40,7 +40,7 @@ function Unit_Test__Afterburner-create-input-file() mkdir -p "${HYBRID_software_output_directory[Sampler]}" local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ - plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" + plist_Final="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" touch "${plist_Sampler}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then @@ -79,7 +79,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" \ - plist_Final="${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" + plist_Final="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then @@ -145,7 +145,7 @@ function Unit_Test__Afterburner-check-all-input() return 1 fi touch \ - "${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" \ + "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" \ "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then @@ -154,16 +154,16 @@ function Unit_Test__Afterburner-check-all-input() 'although all files were provided.' return 1 fi - rm "${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" - touch "${HYBRID_software_output_directory[Sampler]}/original_sampled_particles_list.oscar" - ln -s "${HYBRID_software_output_directory[Sampler]}/original_sampled_particles_list.oscar" \ - "${HYBRID_software_output_directory[Afterburner]}/sampled_particles_list.oscar" + rm "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" + touch "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_Afterburner_list_filename}" + ln -s "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_Afterburner_list_filename}" \ + "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing file unexpectedly failed.' return 1 fi - rm "${HYBRID_software_output_directory[Sampler]}/original_sampled_particles_list.oscar" + rm "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_Afterburner_list_filename}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of a link to a non-existing file unexpectedly succeeded.' From 00779d402653c7b39e3d27fc7839d284f252dbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 15:58:17 +0100 Subject: [PATCH 350/549] Use Bash specific redirection operator Co-authored-by: Alessandro Sciarra --- bash/Afterburner_functionality.bash | 2 +- bash/Hydro_functionality.bash | 4 ++-- bash/IC_functionality.bash | 2 +- bash/Sampler_functionality.bash | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index c4ea35d..454415e 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -41,7 +41,7 @@ function Run_Software_Afterburner() '-i' "${HYBRID_software_configuration_file[Afterburner]}" \ '-o' "${HYBRID_software_output_directory[Afterburner]}" \ '-n' \ - >> "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_terminal_output[Afterburner]}" 2>&1 + &>> "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_terminal_output[Afterburner]}" } #=============================================================================== diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 86df3e0..6140acf 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -44,8 +44,8 @@ function Run_Software_Hydro() "${HYBRID_software_executable[Hydro]}" \ "-params" "${hydro_config_file_path}" \ "-ISinput" "${ic_output_file_path}" \ - "-outputDir" "${HYBRID_software_output_directory[Hydro]}" >> \ - "${HYBRID_software_output_directory[Hydro]}/${HYBRID_terminal_output[Hydro]}" 2>&1 + "-outputDir" "${HYBRID_software_output_directory[Hydro]}" &>> \ + "${HYBRID_software_output_directory[Hydro]}/${HYBRID_terminal_output[Hydro]}" } #=============================================================================== diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index faf4f46..d7c57e4 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -37,7 +37,7 @@ function Run_Software_IC() '-i' "${HYBRID_software_configuration_file[IC]}" \ '-o' "${HYBRID_software_output_directory[IC]}" \ '-n' \ - >> "${HYBRID_software_output_directory[IC]}/${HYBRID_terminal_output[IC]}" 2>&1 + &>> "${HYBRID_software_output_directory[IC]}/${HYBRID_terminal_output[IC]}" } Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 1edaabf..233cb8b 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -41,8 +41,8 @@ function Run_Software_Sampler() local -r sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" cd "${HYBRID_software_output_directory[Sampler]}" "${HYBRID_software_executable[Sampler]}" 'events' '1' \ - "${sampler_config_file_path}" >> \ - "${HYBRID_software_output_directory[Sampler]}/${HYBRID_terminal_output[Sampler]}" 2>&1 + "${sampler_config_file_path}" &>> \ + "${HYBRID_software_output_directory[Sampler]}/${HYBRID_terminal_output[Sampler]}" } #=============================================================================== From 18394d3075c31bbffa55347b5d29ab1b90e98bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 15:59:10 +0100 Subject: [PATCH 351/549] Make terminal output variable local Co-authored-by: Alessandro Sciarra --- tests/functional_tests_Afterburner_only.bash | 2 +- tests/functional_tests_Hydro_only.bash | 2 +- tests/functional_tests_IC_only.bash | 2 +- tests/functional_tests_Sampler_only.bash | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 21fdd6b..8aef44d 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -44,7 +44,7 @@ function Functional_Test__do-Afterburner-only() mv 'Afterburner' 'Afterburner-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' - terminal_output_file="Afterburner/${run_id}/Afterburner.log" + local -r terminal_output_file="Afterburner/${run_id}/Afterburner.log" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 341fbaf..4106ec0 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -36,7 +36,7 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-success' # Expect failure when giving an invalid IC output Print_Info 'Running Hybrid-handler expecting invalid IC argument' - terminal_output_file="Hydro/${run_id}/Hydro.log" + local -r terminal_output_file="Hydro/${run_id}/Hydro.log" BLACK_BOX_FAIL='invalid_input' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index c9de8ee..6986e6f 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -31,7 +31,7 @@ function Functional_Test__do-IC-only() mv 'IC' 'IC-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid IC input file failure' - terminal_output_file="IC/${run_id}/IC.log" + local -r terminal_output_file="IC/${run_id}/IC.log" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index 24f73c8..1f768ed 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -33,7 +33,7 @@ function Functional_Test__do-Sampler-only() mv 'Sampler' 'Sampler-success' # Expect failure and test terminal output local terminal_output_file error_message - terminal_output_file="Sampler/${run_id}/Sampler.log" + local -r terminal_output_file="Sampler/${run_id}/Sampler.log" Print_Info 'Running Hybrid-handler expecting crash in Sampler' BLACK_BOX_FAIL='true' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" From 8bd20c1e723095c0f9c4d39b0920a95c83c1cfa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 15:59:36 +0100 Subject: [PATCH 352/549] Use utility formatting functions Co-authored-by: Alessandro Sciarra --- bash/common_functionality.bash | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/bash/common_functionality.bash b/bash/common_functionality.bash index 1b1e3ff..07b2e39 100644 --- a/bash/common_functionality.bash +++ b/bash/common_functionality.bash @@ -43,8 +43,16 @@ function Replace_Keys_In_Configuration_File_If_Needed_For() function Separate_Terminal_Output_For() { if [[ -e "${HYBRID_software_output_directory[$1]}/${HYBRID_terminal_output[$1]}" ]]; then - local timestamp=$(date +'%d-%m-%Y %H:%M:%S') - printf '\n\n\n===== NEW RUN OUTPUT =====\n===== %s =====\n\n\n' "${timestamp}" >> \ - "${HYBRID_software_output_directory[$1]}/${HYBRID_terminal_output[$1]}" + local -r \ + timestamp=$(date +'%d-%m-%Y %H:%M:%S') \ + width=80 + { + printf '\n\n\n' + Print_Line_of_Equals ${width} + Print_Centered_Line 'NEW RUN OUTPUT' ${width} '' '=' + Print_Centered_Line "${timestamp}" ${width} '' '=' + Print_Line_of_Equals ${width} + printf '\n\n' + } >> "${HYBRID_software_output_directory[$1]}/${HYBRID_terminal_output[$1]}" fi } From 077035af4b636690a7b71bcef7388cbd91fdc04e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 16:11:28 +0100 Subject: [PATCH 353/549] Make terminal output filename readonly --- bash/global_variables.bash | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 90ada3a..c19635a 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -44,6 +44,12 @@ function Define_Further_Global_Variables() [Sampler]='sampler_config.txt' [Afterburner]='afterburner_config.yaml' ) + declare -rgA HYBRID_terminal_output=( + [IC]='IC.log' + [Hydro]='Hydro.log' + [Sampler]='Sampler.log' + [Afterburner]='Afterburner.log' + ) declare -rgA HYBRID_handler_config_section_filename=( [IC]='Hybrid_handler_IC_config.yaml' [Hydro]='Hybrid_handler_Hydro_config.yaml' @@ -128,12 +134,6 @@ function Define_Further_Global_Variables() [Sampler]='' [Afterburner]='' ) - declare -gA HYBRID_terminal_output=( - [IC]='IC.log' - [Hydro]='Hydro.log' - [Sampler]='Sampler.log' - [Afterburner]='Afterburner.log' - ) declare -gA HYBRID_software_configuration_file=( [IC]='' [Hydro]='' From 4253878224e0f401d3e82d3f802a4f7eb068494d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 16:11:47 +0100 Subject: [PATCH 354/549] Cleanup variable duplication --- tests/functional_tests_Hydro_only.bash | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index 4106ec0..6150474 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -74,7 +74,6 @@ function Functional_Test__do-Hydro-only() mv 'Hydro' 'Hydro-success-custom-input' # Expect failure when an invalid config was supplied Print_Info 'Running Hybrid-handler expecting invalid config argument' - terminal_output_file="Hydro/${run_id}/Hydro.log" BLACK_BOX_FAIL='invalid_config' \ Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" if [[ $? -eq 0 ]]; then From 5a203f45a082e992167ed7682dbf7f6b78f16bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 16:14:03 +0100 Subject: [PATCH 355/549] Move the terminal separation to the Run function --- bash/Afterburner_functionality.bash | 2 +- bash/Hydro_functionality.bash | 2 +- bash/IC_functionality.bash | 2 +- bash/Sampler_functionality.bash | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 454415e..be9db2b 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -15,7 +15,6 @@ function Prepare_Software_Input_File_Afterburner() Copy_Base_Configuration_To_Output_Folder_For 'Afterburner' Replace_Keys_In_Configuration_File_If_Needed_For 'Afterburner' __static__Create_Sampled_Particles_List_File_Or_Symbolic_Link_With_Or_Without_Spectators - Separate_Terminal_Output_For 'Afterburner' } function Ensure_All_Needed_Input_Exists_Afterburner() @@ -36,6 +35,7 @@ function Ensure_Run_Reproducibility_Afterburner() function Run_Software_Afterburner() { + Separate_Terminal_Output_For 'Afterburner' cd "${HYBRID_software_output_directory[Afterburner]}" "${HYBRID_software_executable[Afterburner]}" \ '-i' "${HYBRID_software_configuration_file[Afterburner]}" \ diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 6140acf..98bf3f8 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -16,7 +16,6 @@ function Prepare_Software_Input_File_Hydro() Replace_Keys_In_Configuration_File_If_Needed_For 'Hydro' __static__Create_Symbolic_Link_To_IC_File __static__Create_Symbolic_Link_To_EOS_Folder - Separate_Terminal_Output_For 'Hydro' } function Ensure_All_Needed_Input_Exists_Hydro() @@ -37,6 +36,7 @@ function Ensure_Run_Reproducibility_Hydro() function Run_Software_Hydro() { + Separate_Terminal_Output_For 'Hydro' cd "${HYBRID_software_output_directory[Hydro]}" local -r \ hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ diff --git a/bash/IC_functionality.bash b/bash/IC_functionality.bash index d7c57e4..4c88737 100644 --- a/bash/IC_functionality.bash +++ b/bash/IC_functionality.bash @@ -14,7 +14,6 @@ function Prepare_Software_Input_File_IC() Ensure_Given_Files_Exist "${HYBRID_software_base_config_file[IC]}" Copy_Base_Configuration_To_Output_Folder_For 'IC' Replace_Keys_In_Configuration_File_If_Needed_For 'IC' - Separate_Terminal_Output_For 'IC' } function Ensure_All_Needed_Input_Exists_IC() @@ -32,6 +31,7 @@ function Ensure_Run_Reproducibility_IC() function Run_Software_IC() { + Separate_Terminal_Output_For 'IC' cd "${HYBRID_software_output_directory[IC]}" "${HYBRID_software_executable[IC]}" \ '-i' "${HYBRID_software_configuration_file[IC]}" \ diff --git a/bash/Sampler_functionality.bash b/bash/Sampler_functionality.bash index 233cb8b..91c12f8 100644 --- a/bash/Sampler_functionality.bash +++ b/bash/Sampler_functionality.bash @@ -17,7 +17,6 @@ function Prepare_Software_Input_File_Sampler() __static__Validate_Sampler_Config_File __static__Transform_Relative_Paths_In_Sampler_Config_File __static__Create_Superfluous_Symbolic_Link_To_Freezeout_File - Separate_Terminal_Output_For 'Sampler' } function Ensure_All_Needed_Input_Exists_Sampler() @@ -38,6 +37,7 @@ function Ensure_Run_Reproducibility_Sampler() function Run_Software_Sampler() { + Separate_Terminal_Output_For 'Sampler' local -r sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" cd "${HYBRID_software_output_directory[Sampler]}" "${HYBRID_software_executable[Sampler]}" 'events' '1' \ From f807c737aeae8e6b394e8cbeb01c4120f3ddd60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 16:50:49 +0100 Subject: [PATCH 356/549] Adapt tests --- tests/unit_tests_Afterburner_functionality.bash | 3 ++- tests/unit_tests_Hydro_functionality.bash | 2 +- tests/unit_tests_IC_functionality.bash | 2 +- tests/unit_tests_Sampler_functionality.bash | 5 +++-- tests/unit_tests_software_input_functionality.bash | 7 ++++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 5cdd440..45cefb0 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -184,7 +184,8 @@ function Make_Test_Preliminary_Operations__Afterburner-test-run-software() function Unit_Test__Afterburner-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Afterburner]}" - local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}/Afterburner.log" + local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}""\ +/${HYBRID_terminal_output["Afterburner"]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Afterburner if [[ ! -f "${afterburner_terminal_output}" ]]; then diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 6891bf1..6035a1a 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -156,7 +156,7 @@ function Unit_Test__Hydro-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Hydro]}" local -r \ - hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/Hydro.log" \ + hydro_terminal_output="${HYBRID_software_output_directory[Hydro]}/${HYBRID_terminal_output["Hydro"]}" \ Hydro_config_file_path="${HYBRID_software_configuration_file[Hydro]}" \ IC_output_file_path="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" local terminal_output_result correct_result diff --git a/tests/unit_tests_IC_functionality.bash b/tests/unit_tests_IC_functionality.bash index c1562f0..9685e9f 100644 --- a/tests/unit_tests_IC_functionality.bash +++ b/tests/unit_tests_IC_functionality.bash @@ -93,7 +93,7 @@ function Make_Test_Preliminary_Operations__IC-test-run-software() function Unit_Test__IC-test-run-software() { - local -r ic_terminal_output="${HYBRID_software_output_directory[IC]}/IC.log" + local -r ic_terminal_output="${HYBRID_software_output_directory[IC]}/${HYBRID_terminal_output["IC"]}" mkdir -p "${HYBRID_software_output_directory[IC]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_IC diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index dce0f62..9275b84 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -213,8 +213,9 @@ function Make_Test_Preliminary_Operations__Sampler-test-run-software() function Unit_Test__Sampler-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Sampler]}" - local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}/Sampler.log" \ - sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" + local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}""\ +/${HYBRID_terminal_output["Sampler"]}" + sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Sampler if [[ ! -f "${sampler_terminal_output}" ]]; then diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index b7e0d00..4dfa530 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -262,9 +262,10 @@ function Unit_Test__add-section-terminal-output() return 1 fi local -r \ - string_to_be_found="first line===== NEW RUN OUTPUT =====" \ - string_to_search=$(head -n 5 "IC/${run_id}/IC.log" | tr -d '\n') - + string_to_be_found="first line==========================================" \ + +"======================================" \ + +"================================ NEW RUN OUTPUT ================================" + string_to_search=$(head -n 5 "IC/${run_id}/IC.log" | tr -d '\n') if ! [[ "$string_to_search" == "$string_to_be_found"*"=====" ]]; then Print_Error 'Terminal output file not properly separated.' return 1 From 980db11da27e8a996c5e8de60aeae9249a87e3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 19 Feb 2024 17:31:38 +0100 Subject: [PATCH 357/549] Create separate unit test file for software operations --- ...it_tests_software_input_functionality.bash | 75 ------------------- tests/unit_tests_software_operations.bash | 67 +++++++++++++++++ 2 files changed, 67 insertions(+), 75 deletions(-) create mode 100644 tests/unit_tests_software_operations.bash diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 4dfa530..2623872 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -196,78 +196,3 @@ function Make_Test_Preliminary_Operations__copy-hybrid-handler-config-section() done Define_Further_Global_Variables } - -function Unit_Test__copy-hybrid-handler-config-section() -{ - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml - # Avoid empty lines in the beginning in this test as yq behavior - # might change with different versions (here we compare strings) - printf '%s\n' \ - 'Hybrid_handler:' \ - ' Run_ID: test' \ - 'IC:' \ - ' Executable: ex' \ - ' Config_file: conf' \ - 'Hydro:' \ - ' Executable: exh' \ - ' Config_file: confh' > "${HYBRID_configuration_file}" - local -r git_description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" - local folder description - for folder in "${HYBRIDT_folder_to_run_tests}" ~; do - Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ - "${HYBRIDT_folder_to_run_tests}" "${folder}" #&> /dev/null - if [[ "${folder}" = "${HYBRIDT_folder_to_run_tests}" ]]; then - description="${git_description}" - else - description='Not a Git repository' - fi - printf -v expected_result '%b' \ - "# Git describe of executable folder: ${description}\n\n" \ - "# Git describe of handler folder: ${git_description}\n\n" \ - 'Hybrid_handler:\n' \ - ' Run_ID: test\n' \ - 'IC:\n' \ - ' Executable: ex\n' \ - ' Config_file: conf' # No trailing endline as "$(< ...)" strips them - if [[ "$(< "${HYBRID_handler_config_section_filename[IC]}")" != "${expected_result}" ]]; then - Print_Error \ - "Copying of relevant handler config sections failed!" \ - "---- OBTAINED: ----\n$(< "${HYBRID_handler_config_section_filename[IC]}")" \ - "---- EXPECTED: ----\n${expected_result}" \ - '-------------------' - return 1 - fi - rm "${HYBRID_handler_config_section_filename[IC]}" - done -} - -function Unit_Test__add-section-terminal-output() -{ - local -r \ - config_filename='IC_config.yaml' \ - run_id='IC_only' - printf ' - Hybrid_handler: - Run_ID: %s - IC: - Executable: %s/tests/mocks/smash_IC_black-box.py - ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" - mkdir -p "IC/${run_id}" - touch "IC/${run_id}/IC.log" - printf "first line" >> "IC/${run_id}/IC.log" - Print_Info 'Running Hybrid-handler expecting success' - ("${HYBRIDT_repository_top_level_path}/Hybrid-handler" 'do' '-c' "${config_filename}") - if [[ $? -ne 0 ]]; then - Print_Error 'Hybrid-handler unexpectedly failed.' - return 1 - fi - local -r \ - string_to_be_found="first line==========================================" \ - +"======================================" \ - +"================================ NEW RUN OUTPUT ================================" - string_to_search=$(head -n 5 "IC/${run_id}/IC.log" | tr -d '\n') - if ! [[ "$string_to_search" == "$string_to_be_found"*"=====" ]]; then - Print_Error 'Terminal output file not properly separated.' - return 1 - fi -} diff --git a/tests/unit_tests_software_operations.bash b/tests/unit_tests_software_operations.bash new file mode 100644 index 0000000..15bb9d2 --- /dev/null +++ b/tests/unit_tests_software_operations.bash @@ -0,0 +1,67 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Unit_Test__copy-hybrid-handler-config-section() +{ + HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + # Avoid empty lines in the beginning in this test as yq behavior + # might change with different versions (here we compare strings) + printf '%s\n' \ + 'Hybrid_handler:' \ + ' Run_ID: test' \ + 'IC:' \ + ' Executable: ex' \ + ' Config_file: conf' \ + 'Hydro:' \ + ' Executable: exh' \ + ' Config_file: confh' > "${HYBRID_configuration_file}" + local -r git_description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" + local folder description + for folder in "${HYBRIDT_folder_to_run_tests}" ~; do + Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ + "${HYBRIDT_folder_to_run_tests}" "${folder}" #&> /dev/null + if [[ "${folder}" = "${HYBRIDT_folder_to_run_tests}" ]]; then + description="${git_description}" + else + description='Not a Git repository' + fi + printf -v expected_result '%b' \ + "# Git describe of executable folder: ${description}\n\n" \ + "# Git describe of handler folder: ${git_description}\n\n" \ + 'Hybrid_handler:\n' \ + ' Run_ID: test\n' \ + 'IC:\n' \ + ' Executable: ex\n' \ + ' Config_file: conf' # No trailing endline as "$(< ...)" strips them + if [[ "$(< "${HYBRID_handler_config_section_filename[IC]}")" != "${expected_result}" ]]; then + Print_Error \ + "Copying of relevant handler config sections failed!" \ + "---- OBTAINED: ----\n$(< "${HYBRID_handler_config_section_filename[IC]}")" \ + "---- EXPECTED: ----\n${expected_result}" \ + '-------------------' + return 1 + fi + rm "${HYBRID_handler_config_section_filename[IC]}" + done +} + +function Unit_Test__add-section-terminal-output() +{ + __static__Do_Preliminary_Setup_Operations + HYBRID_software_output_directory[IC]="${HYBRIDT_folder_to_run_tests}/test_dir_IC" + mkdir -p "${HYBRID_software_output_directory[IC]}" + local -r filename="${HYBRID_software_output_directory[IC]}/${HYBRID_terminal_output[IC]}" + touch "${filename}" + Call_Codebase_Function_In_Subshell Separate_Terminal_Output_For 'IC' + if [[ $(grep -o '=' "${filename}" | wc -l) != 282 ]]; then + Print_Error 'Terminal output file not properly separated.' + return 1 + fi + rm -r "${HYBRID_software_output_directory[IC]}" +} From 3ecdb40129db569aab36be6e52b3845425716f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 20 Feb 2024 13:51:53 +0100 Subject: [PATCH 358/549] Rename afterburner file global variable, fix usage of YAML utility functions, rephrase documentation --- bash/Afterburner_functionality.bash | 4 ++-- bash/global_variables.bash | 2 +- bash/sanity_checks.bash | 21 +++++++++++++------ docs/user/prerequisites.md | 2 +- .../unit_tests_Afterburner_functionality.bash | 16 +++++++------- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/bash/Afterburner_functionality.bash b/bash/Afterburner_functionality.bash index 835a981..34093e8 100644 --- a/bash/Afterburner_functionality.bash +++ b/bash/Afterburner_functionality.bash @@ -25,7 +25,7 @@ function Ensure_All_Needed_Input_Exists_Afterburner() "${HYBRID_software_configuration_file[Afterburner]}" \ "${HYBRID_software_input_file[Afterburner]}" Internally_Ensure_Given_Files_Exist \ - "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" + "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_afterburner_list_filename}" } function Ensure_Run_Reproducibility_Afterburner() @@ -50,7 +50,7 @@ function Run_Software_Afterburner() function __static__Create_Sampled_Particles_List_File_Or_Symbolic_Link_With_Or_Without_Spectators() { - local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" + local -r target_link_name="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_afterburner_list_filename}" if [[ "${HYBRID_optional_feature[Add_spectators_from_IC]}" = 'TRUE' ]]; then Ensure_Given_Files_Do_Not_Exist "${target_link_name}" # Here the config.yaml file is expected to be produced by SMASH in the output folder diff --git a/bash/global_variables.bash b/bash/global_variables.bash index c1802c2..42c62af 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -28,7 +28,7 @@ function Define_Further_Global_Variables() ) readonly HYBRID_default_configurations_folder="${HYBRID_top_level_path}/configs" readonly HYBRID_python_folder="${HYBRID_top_level_path}/python" - readonly HYBRID_Afterburner_list_filename="sampled_particles_list.oscar" + readonly HYBRID_afterburner_list_filename="sampled_particles_list.oscar" declare -rgA HYBRID_external_python_scripts=( [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" ) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index a243750..fe0cbe2 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -119,16 +119,25 @@ function __static__Set_Software_Input_Data_File_If_Not_Set_By_User() function Ensure_Consistency_Of_Afterburner_Input() { - if Has_YAML_String_Given_Key 'Afterburner' 'Software_keys.Modi.List.Filename' "${HYBRID_configuration_file}"; then - if [[ $(Read_From_YAML_String_Given_Key 'Afterburner' 'Software_keys.Modi.List.Filename' \ - "${HYBRID_configuration_file}") != "${config_section_input}" ]]; then + local config_section_input='Placeholder in case no input file is given.' + if Has_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Input_file'; then + config_section_input=$(Read_From_YAML_String_Given_Key "${HYBRID_configuration_file}" \ + 'Afterburner' 'Input_file') + fi + if Has_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Software_keys' \ + 'Modi' 'List' 'Filename'; then + local given_filename + given_filename=$(Read_From_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Software_keys' \ + 'Modi' 'List' 'Filename') + if [[ "$given_filename" != "${config_section_input}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_key!' + 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys!' fi fi - if Has_YAML_String_Given_Key 'Afterburner' 'Software_keys.Modi.List.Shift_ID' "${HYBRID_configuration_file}"; then + if Has_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Software_keys' 'Modi' \ + 'List' 'Shift_ID'; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_key!' + 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys!' fi } diff --git a/docs/user/prerequisites.md b/docs/user/prerequisites.md index f892579..92c5044 100644 --- a/docs/user/prerequisites.md +++ b/docs/user/prerequisites.md @@ -10,7 +10,7 @@ | [Hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) | 1.0 or higher | | [Python](https://www.python.org) | 3.0 or higher | -[^1]: 3.1 is only needed for Afterburner functionalities. Otherwise 1.8 is sufficient. +[^1]: Version 3.1 is only needed for the afterburner functionality. Otherwise version 1.8 is sufficient. Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 330bc58..3025e05 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -40,7 +40,7 @@ function Unit_Test__Afterburner-create-input-file() mkdir -p "${HYBRID_software_output_directory[Sampler]}" local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ - plist_Final="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" + plist_Final="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_afterburner_list_filename}" touch "${plist_Sampler}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner if [[ ! -f "${HYBRID_software_configuration_file[Afterburner]}" ]]; then @@ -79,7 +79,7 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" \ - plist_Final="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" + plist_Final="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_afterburner_list_filename}" touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then @@ -145,7 +145,7 @@ function Unit_Test__Afterburner-check-all-input() return 1 fi touch \ - "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" \ + "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_afterburner_list_filename}" \ "${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner if [[ $? -ne 0 ]]; then @@ -154,16 +154,16 @@ function Unit_Test__Afterburner-check-all-input() 'although all files were provided.' return 1 fi - rm "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" - touch "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_Afterburner_list_filename}" - ln -s "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_Afterburner_list_filename}" \ - "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_Afterburner_list_filename}" + rm "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_afterburner_list_filename}" + touch "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_afterburner_list_filename}" + ln -s "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_afterburner_list_filename}" \ + "${HYBRID_software_output_directory[Afterburner]}/${HYBRID_afterburner_list_filename}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Ensuring existence of existing file unexpectedly failed.' return 1 fi - rm "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_Afterburner_list_filename}" + rm "${HYBRID_software_output_directory[Sampler]}/original_${HYBRID_afterburner_list_filename}" Call_Codebase_Function_In_Subshell Ensure_All_Needed_Input_Exists_Afterburner &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Ensuring existence of a link to a non-existing file unexpectedly succeeded.' From 6f43b9df72719b0b045bfcb1121134bf94d7532e Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Tue, 20 Feb 2024 14:43:17 +0100 Subject: [PATCH 359/549] Change default Add_spectators_from_IC option for afterburner to TRUE and modify predefined configs accordingly --- bash/global_variables.bash | 2 +- configs/predef_configs/config_AuAu_130.0.yaml | 1 - configs/predef_configs/config_AuAu_200.0.yaml | 1 - configs/predef_configs/config_AuAu_27.0.yaml | 1 - configs/predef_configs/config_AuAu_39.0.yaml | 1 - configs/predef_configs/config_AuAu_4.3.yaml | 1 - configs/predef_configs/config_AuAu_62.4.yaml | 1 - configs/predef_configs/config_AuAu_7.7.yaml | 1 - configs/predef_configs/config_PbPb_17.3.yaml | 1 - configs/predef_configs/config_PbPb_2760.0.yaml | 1 - configs/predef_configs/config_PbPb_5020.0.yaml | 1 - configs/predef_configs/config_PbPb_6.4.yaml | 1 - configs/predef_configs/config_PbPb_8.8.yaml | 1 - configs/predef_configs/config_TEST.yaml | 1 - python/add_spectators.py | 1 + 15 files changed, 2 insertions(+), 14 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 757841a..7a5457f 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -118,7 +118,7 @@ function Define_Further_Global_Variables() [Afterburner]='' ) declare -gA HYBRID_optional_feature=( - [Add_spectators_from_IC]='FALSE' + [Add_spectators_from_IC]='TRUE' [Spectators_source]='' ) # Variables to be set (and possibly made readonly) after all sanity checks on input succeeded diff --git a/configs/predef_configs/config_AuAu_130.0.yaml b/configs/predef_configs/config_AuAu_130.0.yaml index e524171..5a991d6 100644 --- a/configs/predef_configs/config_AuAu_130.0.yaml +++ b/configs/predef_configs/config_AuAu_130.0.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_200.0.yaml b/configs/predef_configs/config_AuAu_200.0.yaml index 2bfa7b3..964d2eb 100644 --- a/configs/predef_configs/config_AuAu_200.0.yaml +++ b/configs/predef_configs/config_AuAu_200.0.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_27.0.yaml b/configs/predef_configs/config_AuAu_27.0.yaml index 87483e0..fddea69 100644 --- a/configs/predef_configs/config_AuAu_27.0.yaml +++ b/configs/predef_configs/config_AuAu_27.0.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_39.0.yaml b/configs/predef_configs/config_AuAu_39.0.yaml index e4033a6..c116b71 100644 --- a/configs/predef_configs/config_AuAu_39.0.yaml +++ b/configs/predef_configs/config_AuAu_39.0.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_4.3.yaml b/configs/predef_configs/config_AuAu_4.3.yaml index 7772832..534b3a4 100644 --- a/configs/predef_configs/config_AuAu_4.3.yaml +++ b/configs/predef_configs/config_AuAu_4.3.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_62.4.yaml b/configs/predef_configs/config_AuAu_62.4.yaml index 5756820..bafee2a 100644 --- a/configs/predef_configs/config_AuAu_62.4.yaml +++ b/configs/predef_configs/config_AuAu_62.4.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_AuAu_7.7.yaml b/configs/predef_configs/config_AuAu_7.7.yaml index 114bf5f..410ef7e 100644 --- a/configs/predef_configs/config_AuAu_7.7.yaml +++ b/configs/predef_configs/config_AuAu_7.7.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_PbPb_17.3.yaml b/configs/predef_configs/config_PbPb_17.3.yaml index b75cc02..27f9f5a 100644 --- a/configs/predef_configs/config_PbPb_17.3.yaml +++ b/configs/predef_configs/config_PbPb_17.3.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_PbPb_2760.0.yaml b/configs/predef_configs/config_PbPb_2760.0.yaml index 728bf41..70cbf0e 100644 --- a/configs/predef_configs/config_PbPb_2760.0.yaml +++ b/configs/predef_configs/config_PbPb_2760.0.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_PbPb_5020.0.yaml b/configs/predef_configs/config_PbPb_5020.0.yaml index 855b4d9..86b5931 100644 --- a/configs/predef_configs/config_PbPb_5020.0.yaml +++ b/configs/predef_configs/config_PbPb_5020.0.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_PbPb_6.4.yaml b/configs/predef_configs/config_PbPb_6.4.yaml index c0b7681..a9d5e3e 100644 --- a/configs/predef_configs/config_PbPb_6.4.yaml +++ b/configs/predef_configs/config_PbPb_6.4.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_PbPb_8.8.yaml b/configs/predef_configs/config_PbPb_8.8.yaml index 14d0058..e914f01 100644 --- a/configs/predef_configs/config_PbPb_8.8.yaml +++ b/configs/predef_configs/config_PbPb_8.8.yaml @@ -21,4 +21,3 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE diff --git a/configs/predef_configs/config_TEST.yaml b/configs/predef_configs/config_TEST.yaml index 5501e7a..7197307 100644 --- a/configs/predef_configs/config_TEST.yaml +++ b/configs/predef_configs/config_TEST.yaml @@ -26,7 +26,6 @@ Sampler: Afterburner: Executable: /path/to/smash - Add_spectators_from_IC: TRUE Software_keys: General: Nevents: 50 diff --git a/python/add_spectators.py b/python/add_spectators.py index 69a4174..3e1d416 100755 --- a/python/add_spectators.py +++ b/python/add_spectators.py @@ -123,4 +123,5 @@ def write_full_particle_list(spectator_list): get_initial_nucleons_from_config() spectators = extract_spectators() + print("Adding spectators to sampled particle list for afterburner.") write_full_particle_list(spectators) From 792c9ed55edad29905f96589a86a5b9142f1b57e Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Tue, 20 Feb 2024 14:56:29 +0100 Subject: [PATCH 360/549] Remove unused python scripts --- python/assemble_endtimes.py | 88 ----- python/average_endtimes.py | 49 --- python/average_flow.py | 97 ------ python/average_spectra.py | 138 -------- python/check_conservation.py | 254 --------------- python/check_multiplicities.py | 243 -------------- python/create_sampler_config.py | 65 ---- python/create_vhlle_config.py | 74 ----- python/extract_hypersurface_contributions.py | 47 --- python/get_hydro_endtime.py | 31 -- python/hydro_parameters.py | 34 -- python/plot_excitation_functions.py | 143 --------- python/plot_int_vn.py | 81 ----- python/plot_spectra.py | 321 ------------------- 14 files changed, 1665 deletions(-) delete mode 100644 python/assemble_endtimes.py delete mode 100644 python/average_endtimes.py delete mode 100644 python/average_flow.py delete mode 100644 python/average_spectra.py delete mode 100644 python/check_conservation.py delete mode 100644 python/check_multiplicities.py delete mode 100755 python/create_sampler_config.py delete mode 100755 python/create_vhlle_config.py delete mode 100755 python/extract_hypersurface_contributions.py delete mode 100644 python/get_hydro_endtime.py delete mode 100644 python/hydro_parameters.py delete mode 100644 python/plot_excitation_functions.py delete mode 100644 python/plot_int_vn.py delete mode 100644 python/plot_spectra.py diff --git a/python/assemble_endtimes.py b/python/assemble_endtimes.py deleted file mode 100644 index e55b11d..0000000 --- a/python/assemble_endtimes.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/python -import numpy as np -import matplotlib -matplotlib.use('Agg') -from matplotlib import cm -import matplotlib.pyplot as plt -import argparse -import linecache -import seaborn as sns -import collections - - -''' - This scripts combines the hydrodynamical start and end times for all - collision energies. -''' - -matplotlib.rcParams['axes.labelsize'] = 15 -matplotlib.rcParams['legend.fontsize'] = 11 - - -start_times = {'4.3' : 6.18676, - '6.4' : 4.08994, - '7.7' : 3.20539, - '8.8' : 2.91076, - '17.3' : 1.45516, - '27.0' : 0.888732, - '39.0' : 0.6145, - '62.4' : 0.5, - '130.0' : 0.5, - '200.0' : 0.5 - } - -def collect_data(files): - data_collection = {'energies' : [], - 'starttimes' : [], - 'endtimes' : [], - 'lifetimes' : [], - 'errors' : []} - - for file in files: - energy = file.split('/')[-3].split('_')[1] - with open(file) as f: - f.readline() # skip header - data = f.readline().split() - endtime = float(data[0]) - starttime = start_times[energy] - lifetime = endtime - start_times[energy] - data_collection['energies'].append(float(energy)) - data_collection['starttimes'].append(starttime) - data_collection['endtimes'].append(data[0]) - data_collection['lifetimes'].append(lifetime) - data_collection['errors'].append(data[1]) - - sqrts = np.array(data_collection['energies'], dtype = 'float') - inds = sqrts.argsort() - - return {'energies' : np.array(data_collection['energies'])[inds], - 'starttimes' : np.array(data_collection['starttimes'])[inds], - 'endtimes' : np.array(data_collection['endtimes'])[inds], - 'lifetimes' : np.array(data_collection['lifetimes'])[inds], - 'errors' : np.array(data_collection['errors'])[inds] - } - - return data_collection - - -def write_data(data, filename): - with open(args.output_dir + filename, 'w') as f: - f.write('# energy starttime endtime time_difference error \n') - for i in range(0, len(data['energies'])): - f.write(str(data['energies'][i]) + '\t' + - str(data['starttimes'][i]) + '\t' + str(data['endtimes'][i]) + '\t' + - str(data['lifetimes'][i]) + '\t' + str(data['errors'][i]) + '\n') - f.close() - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--endtime_files", nargs = '+', required = False, - help = "Files containing the analyzed integrated vn.") - parser.add_argument("--output_dir", required = True, - help = "Where to store the avareged results.") - args = parser.parse_args() - - data = collect_data(args.endtime_files) - write_data(data, 'hydro_lifetime.txt') diff --git a/python/average_endtimes.py b/python/average_endtimes.py deleted file mode 100644 index 18916f9..0000000 --- a/python/average_endtimes.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/python -import numpy as np -import argparse -import linecache -import matplotlib.pyplot as plt - -''' - This script averages the endtimes of all hydrodynamical evolutions. -''' - -def get_average(): - files = args.endtime_files - - full_data = [] - for file in files: - data = np.loadtxt(file, unpack = True) - full_data.append(data) - - Nevents = float(len(files)) # Number of parallel runs - mean = np.mean(full_data, axis = 0) - sigma = np.std(full_data, axis = 0) - error = sigma / np.sqrt(Nevents - 1.0) if Nevents > 1 else np.zeros(sigma.shape) - - return mean, error - - -def print_to_file(mean, error): - - # create files and write content - file = open(args.output_dir + '/Endtime.txt', 'w') - file.write('# endtime_mean endtime_error \n') - - file.write(str(mean) + '\t' + str(error) + '\n') - file.close() - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("--endtime_files", nargs = '+', required = True, - help = "Path to the endtime files") - parser.add_argument("--output_dir", required = True, - help = "Where to store the avareged results.") - parser.add_argument("--energy", required = True, type=float, - help = "Collision energy.") - args = parser.parse_args() - - - mean_endtime, error_endtime = get_average() - print_to_file(mean_endtime, error_endtime) diff --git a/python/average_flow.py b/python/average_flow.py deleted file mode 100644 index 216a302..0000000 --- a/python/average_flow.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/python -import numpy as np -import argparse -import linecache -import matplotlib.pyplot as plt - -''' - This script combines all event plane flow outputs of the individual - event-by-event runs of a specific setup to yield the mean and an error - estimate. -''' - -def get_average(obs): - if obs == 'v2': files = args.v2_files - elif obs == 'v3': files = args.v3_files - else: print ('Observable not known') - - full_data = [] - for file in files: - data = np.loadtxt(file, unpack = True) - full_data.append(data) - - Nevents = float(len(files)) # Number of parallel runs - mean = np.mean(full_data, axis = 0) - sigma = np.std(full_data, axis = 0) - error = sigma / np.sqrt(Nevents - 1.0) if Nevents > 1 else np.zeros(sigma.shape) - - return mean, error - - -def average_integrated_vn(obs): - if obs == 'v2': files = args.v2_files - elif obs == 'v3': files = args.v3_files - else: print ('Observable not known') - - int_vn_list = [] - for file in files: - with open(file, 'r') as f: - f.readline() # skip, no info about int_vn - f.readline() # skip, no info about int_vn - int_vn = float(f.readline().split()[-1]) - int_vn_list.append(int_vn) - - Nevents = float(len(files)) - mean = np.mean(int_vn_list) - error = np.std(int_vn_list) / np.sqrt(Nevents - 1.0) if Nevents > 1 else 0.0 - - return mean, error - -def print_to_file(mean, error, obs): - if obs == 'v2': files = args.v2_files - elif obs == 'v3': files = args.v3_files - - # create files and write content - file = open(args.output_dir + obs + '.txt', 'w') - file.write('# event plane charged particle ' + obs + '\n') - file.write('# pT \t' + obs + '\t error \n') - - for i in range(0, len(mean[0])): - file.write(str(mean[0][i]) + '\t' + str(mean[1][i]) + '\t' + str(error[1][i]) + '\n') - - file.close() - -def print_int_to_file(mean_v2, error_v2, mean_v3, error_v3): - - # create files and write content - file = open(args.output_dir + 'int_vn.txt', 'w') - file.write('# event plane charged particle integrated v2 and v3 \n') - file.write('# sqrts \t v2 \t v2_error \t v3 \t v3_error \n') - - file.write(str(args.energy) + '\t' + str(mean_v2) + '\t' + str(error_v2) + '\t' + str(mean_v3) + '\t' + str(error_v3)) - file.close() - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("--v2_files", nargs = '+', required = True, - help = "Path to the v2 files") - parser.add_argument("--v3_files", nargs = '+', required = True, - help = "Path to the v3 files") - parser.add_argument("--output_dir", required = True, - help = "Where to store the avareged results.") - parser.add_argument("--energy", required = True, type=float, - help = "Collision energy.") - args = parser.parse_args() - - if (len(args.v2_files) != len(args.v3_files)): - print ('Loaded ' + str(len(args.v2_files)) + ' v2 files, but ' + str(len(args.v3_files)) + ' v3 files.') - print ('Averaging over ' + str(len(args.v2_files)) + ' events.') - - mean_v2, error_v2 = get_average('v2') - mean_v3, error_v3 = get_average('v3') - mean_int_v2, error_int_v2 = average_integrated_vn('v2') - mean_int_v3, error_int_v3 = average_integrated_vn('v3') - - print_int_to_file(mean_int_v2, error_int_v2, mean_int_v3, error_int_v3) - print_to_file(mean_v2, error_v2, 'v2') - print_to_file(mean_v3, error_v3, 'v3') diff --git a/python/average_spectra.py b/python/average_spectra.py deleted file mode 100644 index b80d565..0000000 --- a/python/average_spectra.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/python -import numpy as np -import argparse -import linecache - -''' - This script combines all analysis outputs of the individual event-by-event - runs of a specific setup to yield the mean and an error estimate. - For now, averaging is only implemented for pT, mT, rapidity spectra as well - as mean pT and multiplicity at midrapidity and v2. -''' - -def get_average(obs): - if obs == 'pT': files = args.pT_files - elif obs == 'dNdy': files = args.y_files - elif obs == 'mT': files = args.mT_files - elif obs == 'v2': files = args.v2_files - elif obs == 'midyY': files = args.midyY_files - elif obs == 'meanpT': files = args.meanpT_files - else: print ('Observable not known') - - full_data = [] - for file in files: - data = np.loadtxt(file, unpack = True) - full_data.append(data) - - Nevents = float(len(files)) # Number of parallel runs - mean = np.mean(full_data, axis = 0) - sigma = np.std(full_data, axis = 0) - error = sigma / np.sqrt(Nevents - 1.0) if Nevents > 1 else np.zeros(sigma.shape) - - return mean, error - - -def print_to_file(mean, error, obs): - if obs == 'pT': files = args.pT_files - elif obs == 'mT': files = args.mT_files - elif obs == 'dNdy': files = args.y_files - elif obs == 'v2': files = args.v2_files - elif obs == 'midyY': files = args.midyY_files - elif obs == 'meanpT': files = args.meanpT_files - - # find bin widths from analysis scripts - mtbins = linecache.getline(args.smash_ana_dir + '/test/energy_scan/mult_and_spectra.py', 20) - ybins = linecache.getline(args.smash_ana_dir + '/test/energy_scan/mult_and_spectra.py', 21) - ptbins = linecache.getline(args.smash_ana_dir + '/test/energy_scan/mult_and_spectra.py', 22) - midybins = linecache.getline(args.smash_ana_dir + '/test/energy_scan/mult_and_spectra.py', 23) - if midybins.split()[0] != 'midrapidity_cut' or mtbins.split()[0] != 'mtbins' or ptbins.split()[0] != 'ptbins' or ybins.split()[0] != 'ybins': - print ('Problem in determination of bin width. ' - 'The smash-analysis script \'smash-analysis/test/energy_scan/mult_and_spectra.py\' ' - 'was modified after this file was created. Please check and update accordingly.') - - mtbin_edges = eval(mtbins.split('=')[1][:-2]) - ptbin_edges = eval(ptbins.split('=')[1][:-2]) - ybin_edges = eval(ybins.split('=')[1][:-2]) - midy_bin_width = 2.0 * float(midybins.split()[-1].split(')')[0]) - - # create files and write content - if obs == 'v2': file = open(args.output_dir + obs + 'spectra.txt', 'w') - else: file = open(args.output_dir + obs + '.txt', 'w') - if obs in ['midyY', 'meanpT']: file.write('# ' + obs + ' spectra, already divided by events\n') - else: file.write('# ' + obs + ' spectra, already divided by bin width and events\n') - if obs == 'v2': file.write('# pT_bin_center 211 211_error -211 -211_error 111 111_error 321 321_error -321 -321_error 2212 2212_error -2212 -2212_error 3122 3122_error -3122 -3122_error\n') - else: file.write('# ' + obs + '_bin_center 211 211_error -211 -211_error 111 111_error 321 321_error -321 -321_error 2212 2212_error -2212 -2212_error 3122 3122_error -3122 -3122_error\n') - - if obs not in ['midyY', 'meanpT']: - if obs == 'mT': bin_width = mtbin_edges[1:] - mtbin_edges[:-1] - elif obs in ['pT', 'v2']: bin_width = ptbin_edges[1:] - ptbin_edges[:-1] - elif obs == 'dNdy': bin_width = ybin_edges[1:] - ybin_edges[:-1] - - Nevents_sampler = float(args.Nevents) - for i in range(0,len(mean[0])): - line = '' - line += str(mean[0][i]) - for particle_index in range(1, 10): - if obs != 'v2': line += '\t' + str(mean[particle_index][i]/(bin_width[i] * Nevents_sampler)) + '\t' + str(error[particle_index][i]/(bin_width[i] * Nevents_sampler)) - else: line += '\t' + str(mean[particle_index][i]/(bin_width[i] * midy_bin_width)) + '\t' + str(error[particle_index][i]/(bin_width[i] * midy_bin_width)) - line += '\n' - file.write(line) - elif obs == 'meanpT': - line = '' - for particle_index in range(0, 9): - line += '\t' + str(mean[particle_index]) + '\t' + str(error[particle_index]) - line += '\n' - file.write(line) - elif obs == 'midyY': - line = '' - Nevents_sampler = float(args.Nevents) - line = '' - for particle_index in range(0, 9): - line += '\t' + str(mean[particle_index]/(midy_bin_width * Nevents_sampler)) + '\t' + str(error[particle_index]/(midy_bin_width * Nevents_sampler)) - line += '\n' - file.write(line) - else: - print ('Problem: Observable ' + obs + ' not implemented.') - - file.close() - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("--smash_ana_dir", required = True, - help = "Path to the smash analysis to find mid-y bin width") - parser.add_argument("--pT_files", nargs = '+', required = True, - help = "Path to the pT files") - parser.add_argument("--mT_files", nargs = '+', required = True, - help = "Path to the pT files") - parser.add_argument("--y_files", nargs = '+', required = True, - help = "Path to the dN/dy files") - parser.add_argument("--midyY_files", nargs = '+', required = True, - help = "Path to the midy yield files") - parser.add_argument("--v2_files", nargs = '+', required = True, - help = "Path to the v2 files") - parser.add_argument("--meanpT_files", nargs = '+', required = True, - help = "Path to the mean pT files") - parser.add_argument("--output_dir", required = True, - help = "Where to store the avareged results.") - parser.add_argument("--Nevents", required = True, type=float, - help = "Number of afterburner events in individual runs.") - args = parser.parse_args() - - if (len(args.pT_files) != len(args.mT_files)) or (len(args.pT_files) != len(args.y_files)): - print ('Loaded ' + str(len(args.pT_files)) + ' pT files, but ' + str(len(args.mT_files)) + ' mT files and ' + str(len(args.y_files)) + ' files.') - print ('Averaging over ' + str(len(args.pT_files)) + ' events.') - - mean_pT, error_pT = get_average('pT') - mean_mT, error_mT = get_average('mT') - mean_y, error_y = get_average('dNdy') - mean_v2, error_v2 = get_average('v2') - mean_midyY, error_midyY = get_average('midyY') - mean_meanpT, error_meanpT = get_average('meanpT') - - print_to_file(mean_pT, error_pT, 'pT') - print_to_file(mean_mT, error_mT, 'mT') - print_to_file(mean_y, error_y, 'dNdy') - print_to_file(mean_v2, error_v2, 'v2') - print_to_file(mean_midyY, error_midyY, 'midyY') - print_to_file(mean_meanpT, error_meanpT, 'meanpT') diff --git a/python/check_conservation.py b/python/check_conservation.py deleted file mode 100644 index e51736d..0000000 --- a/python/check_conservation.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/python -import numpy as np -import matplotlib as mpl -import matplotlib.pyplot as plt -import argparse -import linecache -import sys - -''' - This script analyzes how the conserved charges E (energy), B (baryon number) - evolve thoughout the evolution of the hybrid siimulation. - In the sampling process, they are not conserved event-by event, but only - on average, which is why the results of the hadron sampler are depicted - in terms of a bar chart. -''' - -# PDG codes corresponding to baryons in SMASH, necessary to find the baryon -# numbers -PDG_codes = [ 1112, 1114, 1116, 1118, 1212, 1214, - 1216, 1218, 2112, 2114, 2116, 2118, - 2122, 2124, 2126, 2128, 2212, 2214, - 2216, 2218, 2222, 2224, 2226, 2228, - 3112, 3114, 3116, 3118, 3122, 3124, - 3126, 3128, 3212, 3214, 3216, 3218, - 3222, 3224, 3226, 3228, 3312, 3314, - 3322, 3324, 3334, 4112, 4114, 4122, - 4132, 4212, 4214, 4222, 4224, 4232, - 4312, 4314, 4322, 4324, 4332, 4334, - 4412, 4414, 4422, 4424, 4432, 4434, - 4444, 5112, 5114, 5122, 5132, 5142, - 5212, 5214, 5222, 5224, 5232, 5242, - 5312, 5314, 5322, 5324, 5332, 5334, - 5342, 5412, 5414, 5422, 5424, 5432, - 5434, 5442, 5444, 5512, 5514, 5522, - 5524, 5532, 5534, 5542, 5544, 5554, - 11112, 11114, 11116, 11212, 11216, 12112, - 12114, 12116, 12122, 12126, 12212, 12214, - 12216, 12222, 12224, 12226, 13112, 13114, - 13116, 13122, 13124, 13126, 13212, 13214, - 13216, 13222, 13224, 13226, 13314, 13324, - 14122, 21112, 21114, 21212, 21214, 22112, - 22114, 22122, 22124, 22212, 22214, 22222, - 22224, 23112, 23114, 23122, 23124, 23126, - 23212, 23214, 23222, 23224, 31214, 32112, - 32124, 32212, 33122, 42112, 42212, 43122, - 53122, 103316, 103326, 203312, 203316, 203322, - 203326, 203338, 9902114, 9902118, 9902214, 9902218, - 9903118, 19903129, 9903218, 9903228, 9912114, 9912214, - 9922114, 9922116, 19922119, 9922214, 9922216, 19922219, - 9932114, 19932119, 9932214, 19932219, 9952112, 9952212, - 9962112, 9962212, 9972112, 9972212, -1112, -1114, - -1116, -1118, -1212, -1214, -1216, -1218, - -2112, -2114, -2116, -2118, -2122, -2124, - -2126, -2128, -2212, -2214, -2216, -2218, - -2222, -2224, -2226, -2228, -3112, -3114, - -3116, -3118, -3122, -3124, -3126, -3128, - -3212, -3214, -3216, -3218, -3222, -3224, - -3226, -3228, -3312, -3314, -3322, -3324, - -3334, -4112, -4114, -4122, -4132, -4212, - -4214, -4222, -4224, -4232, -4312, -4314, - -4322, -4324, -4332, -4334, -4412, -4414, - -4422, -4424, -4432, -4434, -4444, -5112, - -5114, -5122, -5132, -5142, -5212, -5214, - -5222, -5224, -5232, -5242, -5312, -5314, - -5322, -5324, -5332, -5334, -5342, -5412, - -5414, -5422, -5424, -5432, -5434, -5442, - -5444, -5512, -5514, -5522, -5524, -5532, - -5534, -5542, -5544, -5554, -11112, -11114, - -11116, -11212, -11216, -12112, -12114, -12116, - -12122, -12126, -12212, -12214, -12216, -12222, - -12224, -12226, -13112, -13114, -13116, -13122, - -13124, -13126, -13212, -13214, -13216, -13222, - -13224, -13226, -13314, -13324, -14122, -21112, - -21114, -21212, -21214, -22112, -22114, -22122, - -22124, -22212, -22214, -22222, -22224, -23112, - -23114, -23122, -23124, -23126, -23212, -23214, - -23222, -23224, -31214, -32112, -32124, -32212, - -33122, -42112, -42212, -43122, -53122, -103316, - -103326, -203312, -203316, -203322, -203326, -203338, - -9902114, -9902118, -9902214, -9902218, -9903118, -19903129, - -9903218, -9903228, -9912114, -9912214, -9922114, -9922116, - -19922119, -9922214, -9922216, -19922219, -9932114, -19932119, - -9932214, -19932219, -9952112, -9952212, -9962112, -9962212, - -9972112, -9972212] - -def energy_from_binary(file): - with sbs.BinaryReader(file) as reader: - smash_version = reader.smash_version - - E_tot = 0.0 - num_events = -1 # Counter to dynamically determine event number - net_baryon_number = 0 - #Charge = 0.0 - Baryon_numbers = [0] - for block in reader: - if block["type"] == 'f': - num_events = block["nevent"] - if num_events < (int(args.Nevents) - 1): - Baryon_numbers.append(0) - net_baryon_number = 0 - if block["type"] == 'i': continue - if block["type"] == 'p': - for particle in block['part']: - PDG_id = particle[3] - E_tot += particle[2][0] - #Charge += particle[5] - if abs(PDG_id) in PDG_codes: - NB = 1 if PDG_id > 0 else -1 - net_baryon_number += NB - Baryon_numbers[num_events + 1] = net_baryon_number - - return np.mean(Baryon_numbers), E_tot / (float(args.Nevents)) - -def energy_from_hydro(file): - f = open(file, 'r') - lines_header = 37 #to be skipped - NB_before = 0.0 - E_before = 0.0 - for index, line in enumerate(f.readlines()): - # skip header - if index <= 42: continue - if line.startswith('corona'): - NB_corona = baryon_number - continue - if line.startswith('timestep'): continue - if line.startswith('####'): continue - if line.startswith(' tau'): continue - if line.startswith('mkdir returns:'): continue - # Find values before grid resize - if line.startswith('grid'): - NB_before = baryon_number - E_before = energy - else: - baryon_number, energy = float(line.split()[3]), float(line.split()[5]) - # Find values before grid resize - NB_after = baryon_number - E_after = energy - - NB_tot = NB_corona + NB_before + NB_after - E_tot = E_before + E_after - - return NB_tot, E_tot - -def energy_from_oscar(file, specs): - - is_IC = True if 'SMASH_IC.oscar' in file else False - - Energies = [0.0] - Net_Baryons = [0] - i = 0 - with open(file) as f: - for line in f: - if line.startswith('#!OSCAR2013'): continue - elif line.startswith('# Units:'): continue - elif line.startswith('# SMASH'): continue - elif line.startswith('# event'): - if i != (int(line.split(' ')[2])): - i = int(line.split(' ')[2]) - Energies.append(0.0) - Net_Baryons.append(0) - else: - PDG_id = int(line.split()[9]) - E = float(line.split()[5]) - Q = float(line.split()[11]) - if specs and (int(line.split()[12]) != 0): continue - else: - Energies[i] += float(line.split()[5]) - if abs(PDG_id) in PDG_codes: - NB = 1 if PDG_id > 0 else -1 - Net_Baryons[i] += NB - return Net_Baryons, Energies - -def plotting_E_conservation(IC_energy, Specs_energy, hydro_energy, Sampler_energy, Sampler_no_specs, Final_State_energy): - - Nevents = int(args.Nevents) - x = np.arange(1, len(Sampler_energy) + 1, 1) - - system, energy = args.output_path.split('/')[-3].split('_') - - plt.plot(x, [IC_energy]*Nevents, label = 'SMASH: Initial Energy', color = 'darkred', lw = 2) - plt.bar(x, Sampler_energy, alpha = 0.3, label = 'Sampler: Energy per Event + Energy from Spectators') - plt.plot(x, [hydro_energy + Specs_energy] * Nevents, label = 'Hydro: Energy through Surface + Energy from Spectators', color = 'green', lw = 2) - plt.plot(x, [np.mean(Sampler_energy)]*Nevents, label = 'Sampler: Mean Energy + Energy from Spectators', color = 'midnightblue', lw = 2) - plt.plot(x, [Final_State_energy]*Nevents, label = 'SMASH: Final State Energy', color = 'orange', lw = 2, ls = '--') - plt.legend(title = r'$\Delta$E = ' + str(round(float(100*(Final_State_energy/IC_energy - 1)),2)) + ' %', loc = 'lower center') - plt.title(system + r' @ $\mathbf{\sqrt{s}}$ = ' + energy + ' GeV', fontweight = 'bold') - plt.xlim(0,Nevents + 1) - plt.xlabel('Event') - plt.ylabel(r'E$_\mathsf{tot}$ [GeV]') - plt.tight_layout() - - plt.savefig(args.output_path + '/Energy_Conservation.pdf') - plt.close() - -def plotting_NB_conservation(IC_NB, Specs_NB, hydro_NB, Sampler_NB, Final_State_NB): - - Nevents = int(args.Nevents) - x = np.arange(1, len(Sampler_NB) + 1, 1) - - Final_State_NB = np.mean(Final_State_NB) - system, energy = args.output_path.split('/')[-3].split('_') - - plt.plot(x, [IC_NB]*Nevents, label = r'SMASH: Initial N$_\mathsf{B - \bar{B}}$', color = 'darkred', lw = 2) - plt.bar(x, Sampler_NB, alpha = 0.3, label = r'Sampler: N$_\mathsf{B - \bar{B}}$ per Event') - plt.plot(x, [Specs_NB + hydro_NB]*Nevents, label = r'Hydro: N$_\mathsf{B - \bar{B}}$', color = 'green', lw = 2) - plt.plot(x, [np.mean(Sampler_NB)]*Nevents, label = r'Sampler: Mean N$_\mathsf{B - \bar{B}}$', color = 'midnightblue', lw = 2) - plt.plot(x, [Final_State_NB]*Nevents, label = r'SMASH: Final State N$_\mathsf{B - \bar{B}}$', color = 'orange', lw = 2, ls = '--') - plt.legend(title = r'$\Delta$N$_\mathsf{B}$ = ' + str(round(float(100*(Final_State_NB/IC_NB - 1)),2)) + ' %') - plt.title(system + r' @ $\mathbf{\sqrt{s}}$ = ' + energy + ' GeV', fontweight = 'bold') - plt.xlim(0,Nevents + 1) - plt.xlabel('Event') - plt.ylabel(r'N$_\mathsf{B - \bar{ B}}$') - plt.tight_layout() - plt.savefig(args.output_path + '/Baryon_Number_Conservation.pdf') - plt.close() - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("--SMASH_IC", required = True, - help = "SMASH initial conditions.") - parser.add_argument("--Hydro_Info", required = True, - help = "File with the vhlle output from the terminal.") - parser.add_argument("--Sampler", required = True, - help = "Sampled particle lists.") - parser.add_argument("--SMASH_final_state", required = True, - help = "Final state particle lists.") - parser.add_argument("--SMASH_ana_path", required = True, - help = "Path to smash-analysis.") - parser.add_argument("--output_path", required = True, - help = "Path to store results.") - parser.add_argument("--Nevents", required = True, - help = "Number of events in the afterburner/sampler.") - args = parser.parse_args() - - sys.path.append(args.SMASH_ana_path + '/python_scripts') - import smash_basic_scripts as sbs - - Sampler_without_specs = '/'.join(args.Sampler.split('/')[:-1]) + '/particle_lists.oscar' - NB_SMASH_IC, E_SMASH_IC = energy_from_oscar(args.SMASH_IC, False) - NB_SMASH_IC_specs, E_SMASH_IC_specs = energy_from_oscar(args.SMASH_IC, True) - NB_hydro, E_hydro = energy_from_hydro(args.Hydro_Info) - NB_sampler_per_Event, E_sampler_per_Event = energy_from_oscar(args.Sampler, False) - NB_sampler_per_Event_no_specs, E_sampler_per_Event_no_specs = energy_from_oscar(Sampler_without_specs, False) - NB_SMASH_final_state, E_SMASH_final_state = energy_from_binary(args.SMASH_final_state) - - plotting_E_conservation(E_SMASH_IC, E_SMASH_IC_specs[0], E_hydro, E_sampler_per_Event, E_sampler_per_Event_no_specs, E_SMASH_final_state) - plotting_NB_conservation(NB_SMASH_IC, NB_SMASH_IC_specs[0], NB_hydro, NB_sampler_per_Event, NB_SMASH_final_state) - - print ('Initial SMASH energy: ' + str(E_SMASH_IC[0])) - print ('Spectator energy: ' + str(E_SMASH_IC_specs[0])) - print ('Hydro energy through surface: ' + str(E_hydro)) - print ('Sampler energy: ' + str(np.mean(E_sampler_per_Event))) - print ('Final SMASH energy: ' + str(E_SMASH_final_state)) - print ('Energy gain/loss: ' + str(round(100 * ((np.mean(E_SMASH_final_state) / E_SMASH_IC[0]) -1),2) ) + ' %') diff --git a/python/check_multiplicities.py b/python/check_multiplicities.py deleted file mode 100644 index 2f798c1..0000000 --- a/python/check_multiplicities.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/python -import numpy as np -import matplotlib as mpl -import matplotlib.pyplot as plt -import argparse -import linecache -import sys -from scipy.special import kn #for bessel functions -import matplotlib.gridspec as gridspec - -''' - This script determines the expectation values of the particle multiplicites - from the freezeout hypersurface and compares the results to the - multiplicities sampled by the particle sampler. -''' - -# constant for conversion -hbarc = 0.197327 - -def scalar_product(a, b): - # No need to consider the metric or similar here. u_\mu is contravariant, - # dSigma_\mu is covariant. So the metric tensor does not need to be applied. - # The scalar product is then simply the sum of the multiplied components - a_dot_b = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3] - - return a_dot_b - -def get_multiplicities_boltzmann(g, mass, temp, volume, chem_pot): - if (temp > 0.0): - multiplicity = g * mass * mass * temp * volume / (2.0 * np.pi * np.pi) - multiplicity *= kn(2, mass / temp) - multiplicity *= np.exp(chem_pot / temp) - - else: - multiplicity = 0.0 - return multiplicity / hbarc**3 - -def get_multiplicities_bose(g, mass, temp, volume, chem_pot): - if temp > 0.0: - multiplicity = g * mass * mass * temp * volume / (2.0 * np.pi * np.pi) - - sum = 0.0 - for k in range(1, 21): - sum += 1/float(k) * kn(2, k * mass / temp) - - multiplicity *= sum - multiplicity *= np.exp(chem_pot / temp) - else: - multiplicity = 0.0 - - return multiplicity / hbarc**3 - -def Multiplicities_from_Hypersurface(file): - hyp_data = np.loadtxt(file, unpack = True) - - # get T and muB - temp = hyp_data[12] - muB = hyp_data[13] - muQ = hyp_data[14] - muS = hyp_data[15] - - # get dSigma and u^mu - dSigma_mu = [] - umu = [] - for i in range(0, len(temp)): - dSigma_mu.append([hyp_data[4][i], hyp_data[5][i], - hyp_data[6][i], hyp_data[7][i]]) - umu.append([hyp_data[8][i], hyp_data[9][i], - hyp_data[10][i], hyp_data[11][i]]) - - # Determine expected multiplicities: - Npion_plus = 0.0 - Npion_minus = 0.0 - Npion_zero = 0.0 - Nrho_plus = 0.0 - Nrho_minus = 0.0 - Nrho_zero = 0.0 - Nproton = 0.0 - Nantiproton = 0.0 - for element in range(0, len(temp)): - V = scalar_product(umu[element], dSigma_mu[element]) - T = temp[element] - - # pi+: Q = 1, pi-: Q = -1, pi0: Q = 0 - Npion_plus += get_multiplicities_bose(1.0, 0.138, T, V, muQ[element] * 1.0) - Npion_minus += get_multiplicities_bose(1.0, 0.138, T, V, muQ[element] * (-1.0)) - Npion_zero += get_multiplicities_bose(1.0, 0.138, T, V, 0.0) - # rho+: Q = 1, rho-: Q = -1, rho: Q = 0 - Nrho_plus += get_multiplicities_boltzmann(3.0, 0.776, T, V, muQ[element] * 1.0) - Nrho_minus += get_multiplicities_boltzmann(3.0, 0.776, T, V, muQ[element] * (-1.0)) - Nrho_zero += get_multiplicities_boltzmann(3.0, 0.776, T, V, 0.0) - # proton: B=Q=+1, antiproton: B=Q=-1 - Nproton += get_multiplicities_boltzmann(2.0, 0.938, T, V, muB[element] * 1.0 + muQ[element] * 1.0) - Nantiproton += get_multiplicities_boltzmann(2.0, 0.938, T, V, -muB[element] * 1.0 - muQ[element] * 1.0) - - return [[Npion_plus, Npion_minus, Npion_zero], [Nrho_plus, Nrho_minus, Nrho_zero], [Nproton, Nantiproton]] - -def Multiplicities_from_Sampled_List(file): - - Multiplicities = {'211' : [0], '-211' : [0], '111' : [0], - '213' : [0], '-213' : [0], '113' : [0], - '2112' : [0], '-2112' : [0]} - - i = 0 - with open(file) as f: - for line in f: - # if i > 19: continue - if line.startswith('#!OSCAR2013'): continue - elif line.startswith('# Units:'): continue - elif line.startswith('# SMASH'): continue - elif line.startswith('# event'): - if i != (int(line.split(' ')[2])): - i = int(line.split(' ')[2]) - for value in Multiplicities.values(): - value.append(0) - else: - PDG_id = int(line.split()[9]) - if str(PDG_id) in Multiplicities.keys(): - Multiplicities[str(PDG_id)][i] += 1 - - return Multiplicities - -def plot_Multiplicities(Mult_Hyper, Mult_Sampler): - - Nevents = len(Mult_Sampler['211']) - x = np.arange(1, Nevents + 1, 1).astype(int) - width = 0.2 - - ############## - # Pions - ############## - gs = gridspec.GridSpec(1,20) - plt.figure(figsize=(8,4)) - plt.subplot(gs[:, :18]) - plt.bar(x - width, Mult_Sampler['211'], width, label = r'$\pi^+$', color = 'C0') - plt.bar(x, Mult_Sampler['-211'], width, label = r'$\pi^-$', color = 'C1') - plt.bar(x + width, Mult_Sampler['111'], width, label = r'$\pi^0$', color = 'darkred') - plt.axhline(10 * Mult_Hyper[0][0], color = 'grey', label = 'Theo. Exp.', lw = 4, alpha = 0.7) #dummy for legend entry - plt.legend(ncol = 4) - plt.xlim(0.5, 20.5) - plt.ylim(0, 125) - plt.xticks([5,10,15,20]) - plt.xlabel('Events') - plt.ylabel('Multiplicity') - - plt.subplot(gs[:, 18:]) - plt.bar(1 - width, np.mean(Mult_Sampler['211']), width, label = r'$\pi^+$', color = 'C0') - plt.bar(1, np.mean(Mult_Sampler['-211']), width, label = r'$\pi^-$', color = 'C1') - plt.bar(1 + width, np.mean(Mult_Sampler['111']), width, label = r'$\pi^0$', color = 'darkred') - plt.axhline(Mult_Hyper[0][0], color = 'C0', label = 'Theo. Exp.', lw = 4, alpha = 0.7) - plt.axhline(Mult_Hyper[0][1], color = 'C1', label = 'Theo. Exp.', lw = 4, alpha = 0.7) - plt.axhline(Mult_Hyper[0][2], color = 'darkred', label = 'Theo. Exp.', lw = 4, alpha = 0.7) - plt.xlim(0.5, 1.5) - plt.ylim(0, 125) - plt.figtext(0.921, 0.85, 'Mean:\n' + str(Nevents) + '\nevents', bbox=dict(facecolor='none', edgecolor='gainsboro', boxstyle='round'), fontsize = 8) - plt.xticks([], []) - plt.yticks([], []) - plt.tight_layout() - plt.savefig(args.output_path + '/Pions.pdf') - plt.close() - - - ############## - # Rhos - ############## - - gs = gridspec.GridSpec(1,20) - plt.figure(figsize=(8,4)) - plt.subplot(gs[:, :18]) - plt.bar(x - width, Mult_Sampler['213'], width, label = r'$\rho^+$', color = 'C0') - plt.bar(x, Mult_Sampler['-213'], width, label = r'$\rho^-$', color = 'C1') - plt.bar(x + width, Mult_Sampler['113'], width, label = r'$\rho^0$', color = 'darkred') - plt.axhline(10 * Mult_Hyper[1][1], color = 'grey', label = 'Theo. Exp.', lw = 4, alpha = 0.7) #dummy for legend entry - plt.legend(ncol = 4) - plt.xlim(0.5, 20.5) - plt.ylim(0, 30) - plt.xticks([5,10,15,20]) - plt.xlabel('Events') - plt.ylabel('Multiplicity') - - plt.subplot(gs[:, 18:]) - plt.bar(1 - width, np.mean(Mult_Sampler['213']), width, label = r'$\pi^+$', color = 'C0') - plt.bar(1, np.mean(Mult_Sampler['-213']), width, label = r'$\pi^-$', color = 'C1') - plt.bar(1 + width, np.mean(Mult_Sampler['113']), width, label = r'$\pi^0$', color = 'darkred') - plt.axhline(Mult_Hyper[1][0], color = 'C0', label = 'Theo. Exp.', lw = 4, alpha = 0.7) - plt.axhline(Mult_Hyper[1][1], color = 'C1', label = 'Theo. Exp.', lw = 4, alpha = 0.7) - plt.axhline(Mult_Hyper[1][2], color = 'darkred', label = 'Theo. Exp.', lw = 4, alpha = 0.7) - plt.xlim(0.5, 1.5) - plt.ylim(0, 30) - plt.figtext(0.921, 0.85, 'Mean:\n' + str(Nevents) + '\nevents', bbox=dict(facecolor='none', edgecolor='gainsboro', boxstyle='round'), fontsize = 8) - plt.xticks([], []) - plt.yticks([], []) - plt.tight_layout() - plt.savefig(args.output_path + '/Rhos.pdf') - plt.close() - - ############## - # Protons - ############## - width = 0.5 - gs = gridspec.GridSpec(1,20) - plt.figure(figsize=(8,4)) - plt.subplot(gs[:, :18]) - plt.bar(x, Mult_Sampler['2112'], width, label = r'p', color = 'C0') - plt.bar(x, Mult_Sampler['-2112'], width, label = r'$\bar{\mathrm{p}}$', color = 'C1') - plt.axhline(10 * Mult_Hyper[2][0], color = 'grey', label = 'Theo. Exp.', lw = 4, alpha = 0.7) #dummy for legend entry - plt.legend(ncol = 4) - plt.xlim(0.5, 20.5) - plt.ylim(0, 110) - plt.xticks([5,10,15,20]) - plt.xlabel('Events') - plt.ylabel('Multiplicity') - - plt.subplot(gs[:, 18:]) - plt.bar(1, np.mean(Mult_Sampler['2112']), width, color = 'C0') - plt.bar(1, np.mean(Mult_Sampler['-2112']), width, color = 'C1') - plt.axhline(Mult_Hyper[2][0], color = 'C0', label = 'Theo. Exp.', lw = 4, alpha = 0.7) - plt.axhline(Mult_Hyper[2][1], color = 'C1', label = 'Theo. Exp.', lw = 4, alpha = 0.7) - plt.xlim(0.5, 1.5) - plt.ylim(0, 110) - plt.figtext(0.921, 0.85, 'Mean:\n' + str(Nevents) + '\nevents', bbox=dict(facecolor='none', edgecolor='gainsboro', boxstyle='round'), fontsize = 8) - plt.xticks([], []) - plt.yticks([], []) - plt.tight_layout() - plt.savefig(args.output_path + '/Protons.pdf') - plt.close() - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("--Freezeout_Surface", required = True, - help = "Freezeout hypersurface from hydrodynamics.") - parser.add_argument("--Sampler", required = True, - help = "Sampled particle lists.") - parser.add_argument("--output_path", required = True, - help = "Path to store results.") - args = parser.parse_args() - - - Mult_Hypersurface = Multiplicities_from_Hypersurface(args.Freezeout_Surface) - Mult_Sampler = Multiplicities_from_Sampled_List(args.Sampler) - - plot_Multiplicities(Mult_Hypersurface, Mult_Sampler) diff --git a/python/create_sampler_config.py b/python/create_sampler_config.py deleted file mode 100755 index 4e697d4..0000000 --- a/python/create_sampler_config.py +++ /dev/null @@ -1,65 +0,0 @@ -import numpy as np -import argparse -import os - -''' - This script generates a configuration file for the hadron sampler based on - the 'default' file located in configs/AuAu_8.8GeV. For this, the example - file is copied line by line, where the paths to the hydro output and the - particle lists, that are yet to be produced, are replaced by the correct - paths. -''' - -# pass arguments from the command line to the script -parser = argparse.ArgumentParser() -parser.add_argument("--sampler_config", required = True, - help="Base config file to set up the sampler.") -parser.add_argument("--vhlle_config", required = True, - help="Config for hydro evolution.") -parser.add_argument("--output_file", required = True, - help="Updated vhlle config file") -parser.add_argument("--Nevents", required = True, - help="Number of events to sample") -args = parser.parse_args() - -# Path to the results directory -basepath = '/'.join(args.vhlle_config.split('/')[:-2]) + '/' - -# Extract critical energy density and shear viscosity from hydro config. -with open(args.vhlle_config, 'r') as f: - for line in f: - if line.split()[0] == 'etaS': eta_s = line.split()[1] - elif line.split()[0] == 'e_crit': e_crit = line.split()[1] - else: continue -f.close() - -# Create new vhlle config, that contains the extracted proper time and the -# correct paths to the input and output files. -# The default config file is copied and modified where necessary. -config_updated = open(args.output_file, 'w') - -with open(args.sampler_config, 'r') as f: - for line in f: - if line[0] != '\n': - if line.split()[0] == 'surface': - newline = 'surface ' + basepath + 'Hydro/freezeout.dat' + '\n' - elif line.split()[0] == 'spectra_dir': - newline = 'spectra_dir ' + basepath + 'Sampler' + '\n' - elif line.split()[0] == 'number_of_events': - newline = 'number_of_events ' + str(args.Nevents) + '\n' - elif line.split()[0] == 'shear': - if float(eta_s) == 0.0: - # Bool: Do not take shear corrections into consideration - newline = 'shear ' + '0' + '\n' - else: - # Bool: Take shear corrections into consideration - newline = 'shear ' + '1' + '\n' - - elif line.split()[0] == 'ecrit': - newline = 'ecrit ' + e_crit + '\n' - else: - newline = line - config_updated.write(newline) - else: continue -f.close() -config_updated.close() diff --git a/python/create_vhlle_config.py b/python/create_vhlle_config.py deleted file mode 100755 index 6a178a1..0000000 --- a/python/create_vhlle_config.py +++ /dev/null @@ -1,74 +0,0 @@ -import numpy as np -import argparse -import os -from hydro_parameters import hydro_params - -''' - This script generates a configuration file for vHLLE based on the 'default' - file located in configs/AuAu_8.8GeV. For this, the example file is copied - line by line, where the paths to the SMASH IC input file as well as to the - output directory are replaced by the correct paths. Furthermore, the initial - proper time for the hydro evolution is read from the SMASH output and also - inserted to the vHLLE config file. -''' - -# pass arguments from the command line to the script -parser = argparse.ArgumentParser() -parser.add_argument("--vhlle_config", required = True, - help="Config file to set up vhlle.") -parser.add_argument("--smash_ic", required = True, - help="SMASH_IC output in ASCII format.") -parser.add_argument("--output_file", required = True, - help="Updated vhlle config file") -parser.add_argument("--energy", required = False, - help="Collision energy") -parser.add_argument("--test", required = False, action='store_true', default = False, - help="Collision energy") -args = parser.parse_args() - -# Path to the reults directory -basepath = '/'.join(args.smash_ic.split('/')[:-2]) + '/' - -# Collision energy -if args.energy in hydro_params.keys(): - energy = args.energy -else: - for key in hydro_params: - if float(args.energy) >= float(key): energy = key - -# Extract proper time of hypersurface from SMASH output, to pass it to -# vhlle configuration file -with open(args.smash_ic, 'r') as f: - for i in range(0,4): # Skip header - f.readline() - proper_time = f.readline().split()[0] -f.close() - -# Create new vhlle config, that contains the extracted proper time and the -# correct paths to the input and output files. -# The default config file is copied and modified where necessary. -config_updated = open(args.output_file, 'w') - -with open(args.vhlle_config, 'r') as f: - for line in f: - if line[0] != '\n': - if line.split()[0] == 'etaS': - newline = 'etaS ' + str(hydro_params[energy]['etaS']) + '\n' - elif line.split()[0] == 'Rg': - newline = 'Rg ' + str(hydro_params[energy]['Rg']) + '\n' - elif line.split()[0] == 'Rgz': - newline = 'Rgz ' + str(hydro_params[energy]['Rgz']) + '\n' - elif line.split()[0] == 'tau0': - newline = 'tau0 ' + str(proper_time) + '\n' - elif args.test and line.split()[0] == 'nx': - newline = 'nx 81\n' - elif args.test and line.split()[0] == 'ny': - newline = 'ny 81\n' - elif args.test and line.split()[0] == 'nz': - newline = 'nz 151\n' - else: - newline = line - config_updated.write(newline) - else: continue -f.close() -config_updated.close() diff --git a/python/extract_hypersurface_contributions.py b/python/extract_hypersurface_contributions.py deleted file mode 100755 index 73f9ec9..0000000 --- a/python/extract_hypersurface_contributions.py +++ /dev/null @@ -1,47 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -import re -import argparse -import os - -''' - This script reads in the freezeout hypersurface created by CORNELIUS within - vHLLE and reformats it such that: - - No trailing whitespaces at the beginning of the line - - Different line entries are separated by only whitespace - - Hypersurface patches with vanishing energy density or temperature are - removed. Those patches usually originate from the boundaries of the EoS, - where the baryon density may become unphysically large for certain regions - of the energy density. Since those patches are seemingly unphysical, they - can be ignored. - - The reformatted file can be applied within Sangwook's Cooper-Frye sampler - for particlization. -''' - -# pass arguments from the command line to the script -parser = argparse.ArgumentParser() -parser.add_argument("--vhlle_hypersurface", required = True, - help="Freezeout hypersurface generated by means of vhlle.") -parser.add_argument("--output_file", required = False, - help="Freezeout hypersurface with non-zero contributions only.") -args = parser.parse_args() - -if args.output_file: - hypersurface_contributions = open(args.output_file, 'w') -else: - contributions_file = os.path.dirname(args.vhlle_hypersurface) + '/freezeout_contributions.dat' - hypersurface_contributions = open(contributions_file, 'w') - -with open(args.vhlle_hypersurface, 'r') as f: - for line in f: - line = re.sub(' +', ' ',line) # remove whitespaces between entries - newline = line[0:] - line = line.split() - - if ((float(line[13]) > 0.0) and (float(line[12]) > 0.0)): - # if non-zero energy-density and temperature, print to hypersurface - # file that contains only non-zero contributions - hypersurface_contributions.write(newline) - -hypersurface_contributions.close() diff --git a/python/get_hydro_endtime.py b/python/get_hydro_endtime.py deleted file mode 100644 index e0e3175..0000000 --- a/python/get_hydro_endtime.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/python -import numpy as np -import argparse -import sys - -def Endtime_from_hydro(file): - with open(file) as f: - for index, line in enumerate(f.readlines()): - if index <= 42: continue - if line.endswith('nan\n') and line.split()[-3] == '0': - lastline = line - - endtime = lastline.split()[0] - - return endtime - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("--Hydro_Info", required = True, - help = "File with the vhlle output from the terminal.") - parser.add_argument("--output_path", required = True, - help = "Path to store results.") - args = parser.parse_args() - - - endtime = Endtime_from_hydro(args.Hydro_Info) - - with open(args.output_path + '/Hydro_Endtime.txt', 'w') as f: - f.write('# endtime \n') - f.write(endtime + '\n') - f.close() diff --git a/python/hydro_parameters.py b/python/hydro_parameters.py deleted file mode 100644 index 50657f3..0000000 --- a/python/hydro_parameters.py +++ /dev/null @@ -1,34 +0,0 @@ -''' - Dictionaries to properly set the parameters for the - hydrodynamic evolution depending on the collision setup -''' - -# etaS: shear viscosity / entropy density, taken until 200 from Karpenko et al.: Phys.Rev.C 91 (2015) -# Rg: transversal smearing parameter -# Rgz: longitudinal smearing parameter -hydro_params = {'4.3' : {'etaS' : 0.2, 'Rg' : 1.4, 'Rgz' : 1.3}, - '6.4' : {'etaS' : 0.2, 'Rg' : 1.4, 'Rgz' : 1.2}, - '7.7' : {'etaS' : 0.2, 'Rg' : 1.4, 'Rgz' : 1.2}, - '8.8' : {'etaS' : 0.2, 'Rg' : 1.4, 'Rgz' : 1.0}, - '17.3' : {'etaS' : 0.15, 'Rg' : 1.4, 'Rgz' : 0.7}, - '27.0' : {'etaS' : 0.12, 'Rg' : 1.0, 'Rgz' : 0.4}, - '39.0' : {'etaS' : 0.08, 'Rg' : 1.0, 'Rgz' : 0.3}, - '62.4' : {'etaS' : 0.08, 'Rg' : 1.0, 'Rgz' : 0.6}, - '130.0' : {'etaS' : 0.08, 'Rg' : 1.0, 'Rgz' : 0.8}, - '200.0' : {'etaS' : 0.08, 'Rg' : 1.0, 'Rgz' : 1.1}, - '2760.0' : {'etaS' : 0.08, 'Rg' : 1.2, 'Rgz' : 1.3}, - '5020.0' : {'etaS' : 0.1, 'Rg' : 1.0, 'Rgz' : 1.3}, - } - -# For reference, parameters as used in Karpenko et al.: Phys.Rev.C 91 (2015) -hydro_params_Karpenko = {'7.7' : {'etaS' : 0.2, 'Rg' : 1.4, 'Rgz' : 0.5}, - '8.8' : {'etaS' : 0.2, 'Rg' : 1.4, 'Rgz' : 0.5}, - '11.5' : {'etaS' : 0.2, 'Rg' : 1.4, 'Rgz' : 0.5}, - '17.3' : {'etaS' : 0.15, 'Rg' : 1.4, 'Rgz' : 0.5}, - '19.6' : {'etaS' : 0.15, 'Rg' : 1.4, 'Rgz' : 0.5}, - '27.0' : {'etaS' : 0.12, 'Rg' : 1.2, 'Rgz' : 0.5}, - '39.0' : {'etaS' : 0.08, 'Rg' : 1.0, 'Rgz' : 0.7}, - '62.4' : {'etaS' : 0.08, 'Rg' : 1.0, 'Rgz' : 0.7}, - '200.0' : {'etaS' : 0.08, 'Rg' : 1.0, 'Rgz' : 1.0}, - 'default' : {'etaS' : 0.2, 'Rg' : 1.4, 'Rgz' : 0.5} - } diff --git a/python/plot_excitation_functions.py b/python/plot_excitation_functions.py deleted file mode 100644 index e734d65..0000000 --- a/python/plot_excitation_functions.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/python -import numpy as np -import matplotlib -matplotlib.use('Agg') -from matplotlib import cm -import matplotlib.pyplot as plt -import argparse -import linecache -import seaborn as sns -import collections - -''' - This scripts plots the excitation functions of v2 and midy yield -''' - -matplotlib.rcParams['axes.labelsize'] = 15 -matplotlib.rcParams['legend.fontsize'] = 11 - - -def collect_data(files): - data_collection = {'energies' : [], - 'values' : {'211' : [], '-211' : [],'111' : [],'321' : [],'-321' : [],'2212' : [],'-2212' : [], '3122' : [], '-3122' : []}, - 'errors' : {'211' : [], '-211' : [],'111' : [],'321' : [],'-321' : [],'2212' : [],'-2212' : [], '3122' : [], '-3122' : []}} - - for file in files: - if 'Custom' in file: continue - energy = file.split('/')[-3].split('_')[1] - if 'v2' in file: - data = np.loadtxt(file, unpack = True) - bin_width = data[0][1] - data[0][0] - data_collection['energies'].append(float(energy)) - - data_collection['values']['211'].append(np.sum(data[1]) * bin_width) - data_collection['values']['-211'].append(np.sum(data[3]) * bin_width) - data_collection['values']['111'].append(np.sum(data[5]) * bin_width) - data_collection['values']['321'].append(np.sum(data[7]) * bin_width) - data_collection['values']['-321'].append(np.sum(data[9]) * bin_width) - data_collection['values']['2212'].append(np.sum(data[11]) * bin_width) - data_collection['values']['-2212'].append(np.sum(data[13]) * bin_width) - data_collection['values']['3122'].append(np.sum(data[15]) * bin_width) - data_collection['values']['-3122'].append(np.sum(data[17]) * bin_width) - - data_collection['errors']['211'].append(np.sum(data[2]) * bin_width) - data_collection['errors']['-211'].append(np.sum(data[4]) * bin_width) - data_collection['errors']['111'].append(np.sum(data[6]) * bin_width) - data_collection['errors']['321'].append(np.sum(data[8]) * bin_width) - data_collection['errors']['-321'].append(np.sum(data[10]) * bin_width) - data_collection['errors']['2212'].append(np.sum(data[12]) * bin_width) - data_collection['errors']['-2212'].append(np.sum(data[14]) * bin_width) - data_collection['errors']['3122'].append(np.sum(data[16]) * bin_width) - data_collection['errors']['-3122'].append(np.sum(data[18]) * bin_width) - - else: - data = np.loadtxt(file) - data_collection['energies'].append(float(energy)) - data_collection['values']['211'].append(data[0]) - data_collection['values']['-211'].append(data[2]) - data_collection['values']['111'].append(data[4]) - data_collection['values']['321'].append(data[6]) - data_collection['values']['-321'].append(data[8]) - data_collection['values']['2212'].append(data[10]) - data_collection['values']['-2212'].append(data[12]) - data_collection['values']['3122'].append(data[14]) - data_collection['values']['-3122'].append(data[16]) - - data_collection['errors']['211'].append(data[1]) - data_collection['errors']['-211'].append(data[3]) - data_collection['errors']['111'].append(data[5]) - data_collection['errors']['321'].append(data[7]) - data_collection['errors']['-321'].append(data[9]) - data_collection['errors']['2212'].append(data[11]) - data_collection['errors']['-2212'].append(data[13]) - data_collection['errors']['3122'].append(data[15]) - data_collection['errors']['-3122'].append(data[17]) - - return data_collection - - -def write_data(data, filename): - with open(args.output_dir + filename, 'w') as f: - f.write('# energy 211 211_error -211 -211_error 111 111_error 321 321_error -321 -321_error 2212 2212_error -2212 -2212_error 3122 3122_error -3122 -3122_error\n') - for i in range(0, len(data['energies'])): - f.write(str(data['energies'][i]) + '\t' + - str(data['values']['211'][i]) + '\t' + str(data['errors']['211'][i]) + '\t' + - str(data['values']['-211'][i]) + '\t' + str(data['errors']['-211'][i]) + '\t' + - str(data['values']['111'][i]) + '\t' + str(data['errors']['111'][i]) + '\t' + - str(data['values']['321'][i]) + '\t' + str(data['errors']['321'][i]) + '\t' + - str(data['values']['-321'][i]) + '\t' + str(data['errors']['-321'][i]) + '\t' + - str(data['values']['2212'][i]) + '\t' + str(data['errors']['2212'][i]) + '\t' + - str(data['values']['-2212'][i]) + '\t' + str(data['errors']['-2212'][i]) + '\t' + - str(data['values']['3122'][i]) + '\t' + str(data['errors']['3122'][i]) + '\t' + - str(data['values']['-3122'][i]) + '\t' + str(data['errors']['-3122'][i]) + '\n') - f.close() - - -def plotting(data, obs): - sns.set_palette("mako", 3) - - plt.errorbar(data['energies'], data['values']['-211'], data['errors']['-211'], label = r'$\pi^-$', marker = 'o', ls = ':', lw = 0.5) - plt.errorbar(data['energies'], data['values']['-321'], data['errors']['-321'], label = r'$K^-$', marker = 'o', ls = ':', lw = 0.5) - plt.errorbar(data['energies'], data['values']['2212'], data['errors']['2212'], label = r'$p$', marker = 'o', ls = ':', lw = 0.5) - - plt.legend() - - plt.xlim(1,500) - plt.xscale('log') - plt.xlabel(r'$\sqrt{s_\mathrm{NN}}$') - if obs == 'midyY': plt.ylabel(r'dN/dy$|_{\mathrm{y=0}}$') - elif obs == 'v2': plt.ylabel(r'$v_2^{\mathrm{int}}$') - elif obs == 'meanpT': plt.ylabel(r'$\langle p_\mathrm{T} \rangle |_{\mathrm{y=0}}$') - - plt.tight_layout() - if obs == 'midyY': plt.savefig(args.output_dir + 'midy_yield_exc_func.pdf') - elif obs == 'v2': plt.savefig(args.output_dir + 'v2_exc_func.pdf') - elif obs == 'meanpT': plt.savefig(args.output_dir + 'meanpT_exc_func.pdf') - plt.close() - - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--midyY_files", nargs = '+', required = False, - help = "Files containing the analyzed midy particle spectra.") - parser.add_argument("--v2_files", nargs = '+', required = False, - help = "Files containing the analyzed v2 spectra.") - parser.add_argument("--meanpT_files", nargs = '+', required = False, - help = "Files containing the analyzed mean pT spectra.") - parser.add_argument("--output_dir", required = True, - help = "Where to store the avareged results.") - args = parser.parse_args() - - data_midyY = collect_data(args.midyY_files) - # data_v2 = collect_data(args.v2_files) - data_meanpT = collect_data(args.meanpT_files) - - write_data(data_midyY, 'Excitation_Func_midy_Yield.txt') - # write_data(data_v2, 'Excitation_Func_int_v2.txt') - write_data(data_meanpT, 'Excitation_Func_meanpT.txt') - - plotting(data_midyY, 'midyY') - # plotting(data_v2, 'v2') - plotting(data_meanpT, 'meanpT') diff --git a/python/plot_int_vn.py b/python/plot_int_vn.py deleted file mode 100644 index 3adb52a..0000000 --- a/python/plot_int_vn.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/python -import numpy as np -import matplotlib -matplotlib.use('Agg') -from matplotlib import cm -import matplotlib.pyplot as plt -import argparse -import linecache -import seaborn as sns -import collections - -''' - This scripts plots the excitation functions of v2 and midy yield -''' - -matplotlib.rcParams['axes.labelsize'] = 15 -matplotlib.rcParams['legend.fontsize'] = 11 - - -def collect_data(files): - data_collection = {'energies' : [], - 'values' : {'v2' : [], 'v3' : []}, - 'errors' : {'v2' : [], 'v3' : []}} - - for file in files: - energy = file.split('/')[-3].split('_')[1] - with open(file) as f: - f.readline() # skip header - f.readline() # skip header - data = f.readline().split() - print (energy) - data_collection['energies'].append(float(data[0])) - data_collection['values']['v2'].append(float(data[1])) - data_collection['values']['v3'].append(float(data[3])) - data_collection['errors']['v2'].append(float(data[2])) - data_collection['errors']['v3'].append(float(data[4])) - - return collections.OrderedDict(sorted(data_collection.items(), key=lambda kv: kv[0])) - - -def write_data(data, filename): - with open(args.output_dir + filename, 'w') as f: - f.write('# energy v2 v2_error v3 v3_error \n') - for i in range(0, len(data['energies'])): - f.write(str(data['energies'][i]) + '\t' + - str(data['values']['v2'][i]) + '\t' + str(data['errors']['v2'][i]) + '\t' + - str(data['values']['v3'][i]) + '\t' + str(data['errors']['v3'][i]) + '\n') - f.close() - - -def plotting(data): - sns.set_palette("mako", 3) - - plt.errorbar(data['energies'], data['values']['v2'], data['errors']['v2'], label = r'charged particle v$_2$', marker = 'o', ls = 'none') - plt.errorbar(data['energies'], data['values']['v3'], data['errors']['v3'], label = r'charged particle v$_3$', marker = '^', ls = 'none') - plt.legend() - - plt.xlim(1,500) - plt.xscale('log') - plt.xlabel(r'$\sqrt{s_\mathrm{NN}}$') - plt.ylabel(r'$v_2$ or $v_3$') - - plt.tight_layout() - plt.savefig(args.output_dir + 'Integrated_vn_exc_func.pdf') - plt.close() - - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--vn_files", nargs = '+', required = False, - help = "Files containing the analyzed integrated vn.") - parser.add_argument("--output_dir", required = True, - help = "Where to store the avareged results.") - args = parser.parse_args() - - data = collect_data(args.vn_files) - write_data(data, 'Excitation_Func_int_vn.txt') - - plotting(data) diff --git a/python/plot_spectra.py b/python/plot_spectra.py deleted file mode 100644 index 9ad9974..0000000 --- a/python/plot_spectra.py +++ /dev/null @@ -1,321 +0,0 @@ -#!/usr/bin/python -import numpy as np -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import argparse -import linecache - -''' - This scripts creates plots of the analyzed particle spectra for - mT, pT and rapidity. Either for the individual event-by-event runs or - for the averaged results of multiple event-by-event runs. -''' - -matplotlib.rcParams['lines.linewidth'] = 2.0 - -def plot_y_spectra(file, output_path, Nevents): - ydata = np.loadtxt(file, unpack = True) - - plt.figure(figsize=(10,5)) - plt.subplot(121) - if args.energy and args.system: plt.title(str(args.system) + r' @ $\sqrt{s}$ = ' + str(args.energy) + ' GeV') - - if not args.average: - # rapidity bins are uniform - bin_width = ydata[0][1] - ydata[0][0] - - plt.plot(ydata[0], ydata[1]/(Nevents * bin_width), label = r'$\pi^+$') - plt.plot(ydata[0], ydata[2]/(Nevents * bin_width), label = r'$\pi^-$') - plt.plot(ydata[0], ydata[3]/(Nevents * bin_width), label = r'$\pi^0$', color = 'darkred') - plt.plot(ydata[0], ydata[4]/(Nevents * bin_width), label = r'$K^+$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[5]/(Nevents * bin_width), label = r'$K^-$', ls = '--', color = 'C1') - - plt.fill_between(ydata[0], ydata[1]/(Nevents * bin_width) - np.sqrt(ydata[1])/(Nevents * bin_width), ydata[1]/(Nevents * bin_width) + np.sqrt(ydata[1])/(Nevents * bin_width), color = 'C0', alpha = 0.4, lw = 0.0) - plt.fill_between(ydata[0], ydata[2]/(Nevents * bin_width) - np.sqrt(ydata[2])/(Nevents * bin_width), ydata[2]/(Nevents * bin_width) + np.sqrt(ydata[2])/(Nevents * bin_width), color = 'C1', alpha = 0.4, lw = 0.0) - plt.fill_between(ydata[0], ydata[3]/(Nevents * bin_width) - np.sqrt(ydata[3])/(Nevents * bin_width), ydata[3]/(Nevents * bin_width) + np.sqrt(ydata[3])/(Nevents * bin_width), color = 'darkred', alpha = 0.4, lw = 0.0) - plt.fill_between(ydata[0], ydata[4]/(Nevents * bin_width) - np.sqrt(ydata[4])/(Nevents * bin_width), ydata[4]/(Nevents * bin_width) + np.sqrt(ydata[4])/(Nevents * bin_width), color = 'C0', alpha = 0.4, lw = 0.0) - plt.fill_between(ydata[0], ydata[5]/(Nevents * bin_width) - np.sqrt(ydata[5])/(Nevents * bin_width), ydata[5]/(Nevents * bin_width) + np.sqrt(ydata[5])/(Nevents * bin_width), color = 'C1', alpha = 0.4, lw = 0.0) - - else: - plt.plot(ydata[0], ydata[1], label = r'$\pi^+$') - plt.plot(ydata[0], ydata[3], label = r'$\pi^-$') - plt.plot(ydata[0], ydata[5], label = r'$\pi^0$', color = 'darkred') - plt.plot(ydata[0], ydata[7], label = r'K$^+$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[9], label = r'K$^-$', ls = '--', color = 'C1') - - plt.fill_between(ydata[0], ydata[1] - ydata[2], ydata[1] + ydata[2], alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], ydata[3] - ydata[4], ydata[3] + ydata[4], alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], ydata[5] - ydata[6], ydata[5] + ydata[6], alpha = 0.5, color = 'darkred', lw = 0.0) - plt.fill_between(ydata[0], ydata[7] - ydata[8], ydata[7] + ydata[8], alpha = 0.5, color = 'C0', lw = 0.0) - plt.fill_between(ydata[0], ydata[9] - ydata[10], ydata[9] + ydata[10], alpha = 0.5, color = 'C1', lw = 0.0) - - plt.legend(ncol = 2, loc = 'upper right') - plt.ylabel('dN/dy') - plt.xlabel('y') - plt.xlim(-4,4) - - plt.subplot(122) - if args.energy and args.system: plt.title(str(args.system) + r' @ $\sqrt{s}$ = ' + str(args.energy) + ' GeV') - - if not args.average: - plt.plot(ydata[0], ydata[6]/(Nevents * bin_width), label = r'p', ls = '-', color = 'C0') - plt.plot(ydata[0], ydata[7]/(Nevents * bin_width), label = r'$\bar{p}$', ls = '-', color = 'C1') - plt.plot(ydata[0], ydata[8]/(Nevents * bin_width), label = r'$\Lambda$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[9]/(Nevents * bin_width), label = r'$\bar{\Lambda}$', ls = '--', color = 'C1') - - plt.fill_between(ydata[0], ydata[6]/(Nevents * bin_width) - np.sqrt(ydata[6])/(Nevents * bin_width), ydata[6]/(Nevents * bin_width) + np.sqrt(ydata[6])/(Nevents * bin_width), color = 'C0', alpha = 0.4, lw = 0.0) - plt.fill_between(ydata[0], ydata[7]/(Nevents * bin_width) - np.sqrt(ydata[7])/(Nevents * bin_width), ydata[7]/(Nevents * bin_width) + np.sqrt(ydata[7])/(Nevents * bin_width), color = 'C1', alpha = 0.4, lw = 0.0) - plt.fill_between(ydata[0], ydata[8]/(Nevents * bin_width) - np.sqrt(ydata[8])/(Nevents * bin_width), ydata[8]/(Nevents * bin_width) + np.sqrt(ydata[8])/(Nevents * bin_width), color = 'C0', alpha = 0.4, lw = 0.0) - plt.fill_between(ydata[0], ydata[9]/(Nevents * bin_width) - np.sqrt(ydata[9])/(Nevents * bin_width), ydata[9]/(Nevents * bin_width) + np.sqrt(ydata[9])/(Nevents * bin_width), color = 'C1', alpha = 0.4, lw = 0.0) - - else: - plt.plot(ydata[0], ydata[11], label = r'p') - plt.plot(ydata[0], ydata[13], label = r'$\bar{p}$') - plt.plot(ydata[0], ydata[15], label = r'$\Lambda$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[17], label = r'$\bar{\Lambda}$', ls = '--', color = 'C1') - - plt.fill_between(ydata[0], ydata[11] - ydata[12], ydata[11] + ydata[12], alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], ydata[13] - ydata[14], ydata[13] + ydata[14], alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], ydata[15] - ydata[16], ydata[15] + ydata[16], alpha = 0.5, color = 'C0', lw = 0.0) - plt.fill_between(ydata[0], ydata[17] - ydata[18], ydata[17] + ydata[18], alpha = 0.5, color = 'C1', lw = 0.0) - - - plt.legend(ncol = 2, loc = 'upper right') - plt.ylabel('dN/dy') - plt.xlabel('y') - plt.xlim(-4,4) - - plt.savefig(output_path) - plt.close() - - -def plot_mT_spectra(file, output_path, Nevents): - ydata = np.loadtxt(file, unpack = True) - - m_pi = 0.138 - m_kaon = 0.495 - m_proton = 0.938 - m_lambda = 1.321 - - plt.figure(figsize=(10,5)) - plt.subplot(121) - if args.energy and args.system: plt.title(str(args.system) + r' @ $\sqrt{s}$ = ' + str(args.energy) + ' GeV') - - if not args.average: - # Get uneven bin sizes - bin_edges = linecache.getline(file, 4)[17:-2].split(' ') - # Remove white spaces - for num, element in enumerate(bin_edges): - if element == '': bin_edges.pop(num) - bin_edges = np.array(bin_edges).astype('float') - bin_width = bin_edges[1:] - bin_edges[:-1] - - plt.plot(ydata[0], ydata[1]/(Nevents * bin_width * (ydata[0] + m_pi)), label = r'$\pi^+$') - plt.plot(ydata[0], ydata[2]/(Nevents * bin_width * (ydata[0] + m_pi)), label = r'$\pi^-$') - plt.plot(ydata[0], ydata[3]/(Nevents * bin_width * (ydata[0] + m_pi)), label = r'$\pi^0$', color = 'darkred') - plt.plot(ydata[0], ydata[4]/(Nevents * bin_width * (ydata[0] + m_kaon)), label = r'$K^+$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[5]/(Nevents * bin_width * (ydata[0] + m_kaon)), label = r'$K^-$', ls = '--', color = 'C1') - - else: - plt.plot(ydata[0], ydata[1] / (ydata[0] + m_pi), label = r'$\pi^+$') - plt.plot(ydata[0], ydata[3] / (ydata[0] + m_pi), label = r'$\pi^-$') - plt.plot(ydata[0], ydata[5] / (ydata[0] + m_pi), label = r'$\pi^0$', color = 'darkred') - plt.plot(ydata[0], ydata[7] / (ydata[0] + m_kaon), label = r'$K^+$', color = 'C0', ls = '--') - plt.plot(ydata[0], ydata[9] / (ydata[0] + m_kaon), label = r'$K^-$', color = 'C1', ls = '--') - - plt.fill_between(ydata[0], (ydata[1] - ydata[2]) / (ydata[0] + m_pi), (ydata[1] + ydata[2]) / (ydata[0] + m_pi), alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], (ydata[3] - ydata[4]) / (ydata[0] + m_pi), (ydata[3] + ydata[4]) / (ydata[0] + m_pi), alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], (ydata[5] - ydata[6]) / (ydata[0] + m_pi), (ydata[5] + ydata[6]) / (ydata[0] + m_pi), alpha = 0.5, lw = 0.0, color = 'darkred') - plt.fill_between(ydata[0], (ydata[7] - ydata[8]) / (ydata[0] + m_kaon), (ydata[7] + ydata[8]) / (ydata[0] + m_kaon), alpha = 0.5, lw = 0.0, color = 'C0') - plt.fill_between(ydata[0], (ydata[9] - ydata[10]) / (ydata[0] + m_kaon), (ydata[9] + ydata[10]) / (ydata[0] + m_kaon), alpha = 0.5, lw = 0.0, color = 'C1') - - - plt.legend(ncol = 2, loc = 'upper right') - plt.ylabel(r'1/m$_\mathrm{T}$ d$^2$N/dm$_\mathrm{T}$dy|$_{y=0}$ [Gev$^{-2}$]') - plt.yscale('log') - plt.xlabel(r'm$_\mathrm{T}$ - m$_0$ [GeV]') - plt.xlim(0,2.2) - - plt.subplot(122) - if args.energy and args.system: plt.title(str(args.system) + r' @ $\sqrt{s}$ = ' + str(args.energy) + ' GeV') - - if not args.average: - plt.plot(ydata[0], ydata[6]/(Nevents * bin_width * (ydata[0] + m_proton)), label = r'p', ls = '-', color = 'C0') - plt.plot(ydata[0], ydata[7]/(Nevents * bin_width * (ydata[0] + m_proton)), label = r'$\bar{p}$', ls = '-', color = 'C1') - plt.plot(ydata[0], ydata[8]/(Nevents * bin_width * (ydata[0] + m_lambda)), label = r'$\Lambda$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[9]/(Nevents * bin_width * (ydata[0] + m_lambda)), label = r'$\bar{\Lambda}$', ls = '--', color = 'C1') - - else: - plt.plot(ydata[0], ydata[11] / (ydata[0] + m_proton), label = r'$p$') - plt.plot(ydata[0], ydata[13] / (ydata[0] + m_proton), label = r'$\bar{p}$') - plt.plot(ydata[0], ydata[15] / (ydata[0] + m_lambda), label = r'$\Lambda$', color = 'C0', ls = '--') - plt.plot(ydata[0], ydata[17] / (ydata[0] + m_lambda), label = r'$\bar{\Lambda}$', color = 'C1', ls = '--') - - plt.fill_between(ydata[0], (ydata[11] - ydata[12]) / (ydata[0] + m_proton), (ydata[11] + ydata[12]) / (ydata[0] + m_proton), alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], (ydata[13] - ydata[14]) / (ydata[0] + m_proton), (ydata[13] + ydata[14]) / (ydata[0] + m_proton), alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], (ydata[15] - ydata[16]) / (ydata[0] + m_lambda), (ydata[15] + ydata[16]) / (ydata[0] + m_lambda), alpha = 0.5, lw = 0.0, color = 'C0') - plt.fill_between(ydata[0], (ydata[17] - ydata[18]) / (ydata[0] + m_lambda), (ydata[17] + ydata[18]) / (ydata[0] + m_lambda), alpha = 0.5, lw = 0.0, color = 'C1') - - plt.legend(ncol = 2, loc = 'upper right') - plt.ylabel(r'1/m$_\mathrm{T}$ d$^2$N/dm$_\mathrm{T}$dy|$_{y=0}$ [Gev$^{-2}$]') - plt.yscale('log') - plt.xlabel(r'm$_\mathrm{T}$ - m$_0$ [GeV]') - plt.xlim(0,2.2) - - plt.tight_layout() - plt.savefig(output_path) - plt.close() - - -def plot_pT_spectra(file, output_path, Nevents): - ydata = np.loadtxt(file, unpack = True) - - plt.figure(figsize=(10,5)) - plt.subplot(121) - if args.energy and args.system: plt.title(str(args.system) + r' @ $\sqrt{s}$ = ' + str(args.energy) + ' GeV') - - if not args.average: - # Get uneven bin sizes - bin_edges = linecache.getline(file, 4)[17:-2].split(' ') - # Remove white spaces - for num, element in enumerate(bin_edges): - if element == '': bin_edges.pop(num) - bin_edges = np.array(bin_edges).astype('float') - bin_width = bin_edges[1:] - bin_edges[:-1] - - plt.plot(ydata[0], ydata[1]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'$\pi^+$') - plt.plot(ydata[0], ydata[2]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'$\pi^-$') - plt.plot(ydata[0], ydata[3]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'$\pi^0$', color = 'darkred') - plt.plot(ydata[0], ydata[4]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'$K^+$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[5]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'$K^-$', ls = '--', color = 'C1') - - else: - plt.plot(ydata[0], ydata[1]/(2 * np.pi * ydata[0]), label = r'$\pi^+$') - plt.plot(ydata[0], ydata[3]/(2 * np.pi * ydata[0]), label = r'$\pi^-$') - plt.plot(ydata[0], ydata[5]/(2 * np.pi * ydata[0]), label = r'$\pi^0$', color = 'darkred') - plt.plot(ydata[0], ydata[7]/(2 * np.pi * ydata[0]), label = r'$K^+$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[9]/(2 * np.pi * ydata[0]), label = r'$K^-$', ls = '--', color = 'C1') - - plt.fill_between(ydata[0], (ydata[1] - ydata[2]) / (2 * np.pi * ydata[0]), (ydata[1] + ydata[2]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], (ydata[3] - ydata[4]) / (2 * np.pi * ydata[0]), (ydata[3] + ydata[4]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], (ydata[5] - ydata[6]) / (2 * np.pi * ydata[0]), (ydata[5] + ydata[6]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0, color = 'darkred') - plt.fill_between(ydata[0], (ydata[7] - ydata[8]) / (2 * np.pi * ydata[0]), (ydata[7] + ydata[8]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0, color = 'C0') - plt.fill_between(ydata[0], (ydata[9] - ydata[10]) / (2 * np.pi * ydata[0]), (ydata[9] + ydata[10]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0, color = 'C1') - - - - plt.legend(ncol = 2, loc = 'upper right') - plt.yscale('log') - plt.ylabel(r'1/(2$\pi$ p$_\mathrm{T}$) d$^2$N/dp$_\mathrm{T}$dy|$_{y=0}$ [Gev$^{-2}$]') - plt.xlabel(r'p$_\mathrm{T}$ [GeV]') - plt.xlim(0,2.0) - plt.ylim(1e-1, 1e3) - - plt.subplot(122) - if args.energy and args.system: plt.title(str(args.system) + r' @ $\sqrt{s}$ = ' + str(args.energy) + ' GeV') - - if not args.average: - plt.plot(ydata[0], ydata[6]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'p', ls = '-', color = 'C0') - plt.plot(ydata[0], ydata[7]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'$\bar{p}$', ls = '-', color = 'C1') - plt.plot(ydata[0], ydata[8]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'$\Lambda$', ls = '--', color = 'C0') - plt.plot(ydata[0], ydata[9]/(Nevents * bin_width * 2 * np.pi * ydata[0]), label = r'$\bar{\Lambda}$', ls = '--', color = 'C1') - - else: - plt.plot(ydata[0], ydata[11] / (2 * np.pi * ydata[0]), label = r'$p$') - plt.plot(ydata[0], ydata[13] / (2 * np.pi * ydata[0]), label = r'$\bar{p}$') - plt.plot(ydata[0], ydata[15] / (2 * np.pi * ydata[0]), label = r'$\Lambda$', color = 'C0', ls = '--') - plt.plot(ydata[0], ydata[17] / (2 * np.pi * ydata[0]), label = r'$\bar{\Lambda}$', color = 'C1', ls = '--') - - plt.fill_between(ydata[0], (ydata[11] - ydata[12]) / (2 * np.pi * ydata[0]), (ydata[11] + ydata[12]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], (ydata[13] - ydata[14]) / (2 * np.pi * ydata[0]), (ydata[13] + ydata[14]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0) - plt.fill_between(ydata[0], (ydata[15] - ydata[16]) / (2 * np.pi * ydata[0]), (ydata[15] + ydata[16]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0, color = 'C0') - plt.fill_between(ydata[0], (ydata[17] - ydata[18]) / (2 * np.pi * ydata[0]), (ydata[17] + ydata[18]) / (2 * np.pi * ydata[0]), alpha = 0.5, lw = 0.0, color = 'C1') - - plt.legend(ncol = 2, loc = 'upper right') - plt.ylabel(r'1/(2$\pi$ p$_\mathrm{T}$) d$^2$N/dp$_\mathrm{T}$dy|$_{y=0}$ [Gev$^{-2}$]') - plt.yscale('log') - plt.xlabel(r'p$_\mathrm{T}$ [GeV]') - plt.xlim(0,2.0) - - plt.tight_layout() - plt.savefig(output_path) - plt.close() - - -def plot_v2(file, output_path): - data = np.loadtxt(file, unpack = True) - - plt.figure(figsize=(10,5)) - plt.subplot(121) - if args.energy and args.system: plt.title(str(args.system) + r' @ $\sqrt{s}$ = ' + str(args.energy) + ' GeV') - - plt.plot(data[0], data[1], label = r'$\pi^+$') - plt.plot(data[0], data[3], label = r'$\pi^-$') - plt.plot(data[0], data[5], label = r'$\pi^0$', color = 'darkred') - plt.plot(data[0], data[7], label = r'$K^+$', ls = '--', color = 'C0') - plt.plot(data[0], data[9], label = r'$K^-$', ls = '--', color = 'C1') - - plt.fill_between(data[0], data[1] - data[2], data[1] + data[2], alpha = 0.5, lw = 0.0) - plt.fill_between(data[0], data[3] - data[4], data[3] + data[4], alpha = 0.5, lw = 0.0) - plt.fill_between(data[0], data[5] - data[6], data[5] + data[6], alpha = 0.5, lw = 0.0, color = 'darkred') - plt.fill_between(data[0], data[7] - data[8], data[7] + data[8], alpha = 0.5, lw = 0.0, color = 'C0') - plt.fill_between(data[0], data[9] - data[10], data[9] + data[10], alpha = 0.5, lw = 0.0, color = 'C1') - plt.ylim(-1,1) - - plt.legend(ncol = 2, loc = 'upper right') - plt.ylabel(r'v$_2$') - plt.xlabel(r'p$_\mathrm{T}$ [GeV]') - plt.xlim(0,2.0) - - plt.subplot(122) - if args.energy and args.system: plt.title(str(args.system) + r' @ $\sqrt{s}$ = ' + str(args.energy) + ' GeV') - plt.plot(data[0], data[11], label = r'p') - plt.plot(data[0], data[13], label = r'$\bar{p}$') - plt.plot(data[0], data[15], label = r'$\Lambda$', ls = '--', color = 'C0') - plt.plot(data[0], data[17], label = r'$\bar{\Lambda}$', ls = '--', color = 'C1') - - plt.fill_between(data[0], data[11] - data[12], data[11] + data[12], alpha = 0.5, lw = 0.0) - plt.fill_between(data[0], data[13] - data[14], data[13] + data[14], alpha = 0.5, lw = 0.0) - plt.fill_between(data[0], data[15] - data[16], data[15] + data[16], alpha = 0.5, lw = 0.0, color = 'C0') - plt.fill_between(data[0], data[17] - data[18], data[17] + data[18], alpha = 0.5, lw = 0.0, color = 'C1') - - plt.legend(ncol = 2, loc = 'upper right') - plt.ylabel(r'v$_2$') - plt.xlabel(r'p$_\mathrm{T}$ [GeV]') - plt.xlim(0,2.0) - plt.ylim(-1,1) - - plt.tight_layout() - plt.savefig(output_path) - plt.close() - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--input_files", nargs = '+', required = False, - help = "Files containing the analyzed particle spectra.") - parser.add_argument("--energy", required = False, - help = "Collision energy (sqrt(s)).") - parser.add_argument("--system", required = False, - help = "Collision system.") - parser.add_argument("--Nevents", required = True, - help = "Number of events.") - parser.add_argument("--average", required = False, default = False, - help = "Whether to plot averaged quantities.") - args = parser.parse_args() - - for file in args.input_files: - observable = file.split('/')[-1].split('.')[0] - plot_path_and_name = '/'.join(file.split('/')[:-1]) + '/' + observable + '.pdf' - - - if observable in ['yspectra', 'dNdy']: - plot_y_spectra(file, plot_path_and_name, float(args.Nevents)) - elif observable in ['mtspectra', 'mT']: - plot_mT_spectra(file, plot_path_and_name, float(args.Nevents)) - elif observable in ['ptspectra', 'pT']: - plot_pT_spectra(file, plot_path_and_name, float(args.Nevents)) - elif observable in ['v2']: - if args.average: - plot_v2(file, plot_path_and_name) From 57e5519b8a467f03f2793babd794d59e36a86aea Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Tue, 20 Feb 2024 17:20:00 +0100 Subject: [PATCH 361/549] Add python3 requirement --- bash/system_requirements.bash | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index 23806bc..c259d4e 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -25,6 +25,7 @@ function __static__Declare_System_Requirements() [sed]='4.2.1' [tput]='5.7' [yq]='4.18.1' + [python3]='3.0.0' ) declare -rga HYBRID_programs_just_required=( cat @@ -311,7 +312,7 @@ function __static__Try_Find_Version() fi local found_version case "$1" in - awk | git | sed) + awk | git | sed | python3) found_version=$($1 --version) ;;& # Continue matching other cases awk | sed) @@ -323,7 +324,7 @@ function __static__Try_Find_Version() found_version="${BASH_VERSINFO[@]:0:3}" found_version="${found_version// /.}" ;; - git) + git | python3) found_version=$(grep -oE "${HYBRID_version_regex}" <<< "${found_version}") ;; tput) @@ -442,7 +443,7 @@ function __static__Print_Requirement_Version_Report_Line() found=${tmp_array[0]} version_found=${tmp_array[1]} version_ok=${tmp_array[2]} - printf -v line " ${text_color}Command ${emph_color}%6s${text_color}: ${default}" "${program}" + printf -v line " ${text_color}Command ${emph_color}%8s${text_color}: ${default}" "${program}" if [[ ${found} = '---' ]]; then line+="${red}NOT " else From 7936b7946bd65a35d2a5c8c3e561c2693d6c7d0a Mon Sep 17 00:00:00 2001 From: Niklas Goetz Date: Wed, 21 Feb 2024 11:42:34 +0100 Subject: [PATCH 362/549] Test for both list input config entries --- bash/sanity_checks.bash | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index fe0cbe2..9b5f6b7 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -139,6 +139,11 @@ function Ensure_Consistency_Of_Afterburner_Input() exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys!' fi + if Has_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Software_keys' 'Modi' \ + 'List' 'File_Prefix'; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys!' + fi } Make_Functions_Defined_In_This_File_Readonly From c52f519124356736008fba712e0fa582e0cb6b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Wed, 21 Feb 2024 11:55:46 +0100 Subject: [PATCH 363/549] Beautify tests --- tests/unit_tests_Afterburner_functionality.bash | 7 +++---- tests/unit_tests_Sampler_functionality.bash | 8 +++----- tests/unit_tests_software_operations.bash | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 45cefb0..41088aa 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -184,15 +184,14 @@ function Make_Test_Preliminary_Operations__Afterburner-test-run-software() function Unit_Test__Afterburner-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Afterburner]}" - local -r afterburner_terminal_output="${HYBRID_software_output_directory[Afterburner]}""\ -/${HYBRID_terminal_output["Afterburner"]}" + local -r terminal_output="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_terminal_output["Afterburner"]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Afterburner - if [[ ! -f "${afterburner_terminal_output}" ]]; then + if [[ ! -f "${terminal_output}" ]]; then Print_Error 'The terminal output was not created.' return 1 fi - terminal_output_result=$(< "${afterburner_terminal_output}") + terminal_output_result=$(< "${terminal_output}") printf -v correct_result '%s' \ "-i ${HYBRID_software_configuration_file[Afterburner]} " \ "-o ${HYBRID_software_output_directory[Afterburner]} -n" diff --git a/tests/unit_tests_Sampler_functionality.bash b/tests/unit_tests_Sampler_functionality.bash index 9275b84..b3c9b63 100644 --- a/tests/unit_tests_Sampler_functionality.bash +++ b/tests/unit_tests_Sampler_functionality.bash @@ -213,16 +213,14 @@ function Make_Test_Preliminary_Operations__Sampler-test-run-software() function Unit_Test__Sampler-test-run-software() { mkdir -p "${HYBRID_software_output_directory[Sampler]}" - local -r sampler_terminal_output="${HYBRID_software_output_directory[Sampler]}""\ -/${HYBRID_terminal_output["Sampler"]}" - sampler_config_file_path="${HYBRID_software_configuration_file[Sampler]}" + local -r terminal_output="${HYBRID_software_output_directory[Sampler]}/${HYBRID_terminal_output["Sampler"]}" local terminal_output_result correct_result Call_Codebase_Function_In_Subshell Run_Software_Sampler - if [[ ! -f "${sampler_terminal_output}" ]]; then + if [[ ! -f "${terminal_output}" ]]; then Print_Error 'The terminal output was not created.' return 1 fi - terminal_output_result=$(< "${sampler_terminal_output}") + terminal_output_result=$(< "${terminal_output}") correct_result="events 1 ${HYBRID_software_configuration_file[Sampler]}" if [[ "${terminal_output_result}" != "${correct_result}" ]]; then Print_Error 'The terminal output has not the expected content.' diff --git a/tests/unit_tests_software_operations.bash b/tests/unit_tests_software_operations.bash index 15bb9d2..38aa64f 100644 --- a/tests/unit_tests_software_operations.bash +++ b/tests/unit_tests_software_operations.bash @@ -22,7 +22,7 @@ function Unit_Test__copy-hybrid-handler-config-section() ' Executable: exh' \ ' Config_file: confh' > "${HYBRID_configuration_file}" local -r git_description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" - local folder description + local folder description expected_result for folder in "${HYBRIDT_folder_to_run_tests}" ~; do Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ "${HYBRIDT_folder_to_run_tests}" "${folder}" #&> /dev/null From e3d2a0a1534f91453139e69a2fc75e613b30e7b1 Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Wed, 21 Feb 2024 12:51:03 +0100 Subject: [PATCH 364/549] Adjust add_spectators flags in Afterburner functional test --- tests/functional_tests_Afterburner_only.bash | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 3529fad..99e20cf 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -32,6 +32,7 @@ function Functional_Test__do-Afterburner-only() Run_ID: %s Afterburner: Executable: %s/tests/mocks/smash_afterburner_black-box.py + Add_spectators_from_IC: false Software_keys: Modi: List: @@ -82,6 +83,7 @@ function Functional_Test__do-Afterburner-only() Afterburner: Executable: %s/mocks/smash_afterburner_black-box.py Input_file: %s/test/particle_lists_2.oscar + Add_spectators_from_IC: false Software_keys: Modi: List: From 76dd776ca50994ce1c3c53f11bafcbd89859ac6c Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Wed, 21 Feb 2024 15:12:39 +0100 Subject: [PATCH 365/549] Change default output directory and adjust functional tests accordingly --- bash/command_line_parsers/helper.bash | 2 +- bash/global_variables.bash | 2 +- tests/functional_tests_Afterburner_only.bash | 16 ++++++++-------- tests/functional_tests_Hydro_only.bash | 12 ++++++------ tests/functional_tests_IC_only.bash | 6 +++--- tests/functional_tests_Sampler_only.bash | 6 +++--- tests/functional_tests_full_workflow.bash | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index e1550e5..bff4f75 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -51,7 +51,7 @@ function __static__Print_Do_Help_Message() printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n' \ 'You can specify the following command line options to the' 'do' 'execution mode:' __static__Print_Command_Line_Option_Help \ - '-o | --output-directory' '.' \ + '-o | --output-directory' './data' \ "Directory where the run folder(s) will be created." __static__Print_Command_Line_Option_Help \ '-c | --configuration-file' "${HYBRID_configuration_file}" \ diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 7a5457f..1e01dc7 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -88,7 +88,7 @@ function Define_Further_Global_Variables() # Variables to be set (and possibly made readonly) from command line HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' - HYBRID_output_directory="$(realpath .)" + HYBRID_output_directory="$(realpath './data')" # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_run_id="Run_$(date +'%Y-%m-%d_%H%M%S')" HYBRID_given_software_sections=() diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 99e20cf..cc22f57 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -40,14 +40,14 @@ function Functional_Test__do-Afterburner-only() ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' __static__Check_Successful_Handler_Run $? mv 'Afterburner' 'Afterburner-success' # Expect failure and test "SMASH" message Print_Info 'Running Hybrid-handler expecting invalid Afterburner input file failure' terminal_output_file="Afterburner/${run_id}/Terminal_Output.txt" BLACK_BOX_FAIL='invalid_config' \ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid Afterburner input.' return 1 @@ -64,7 +64,7 @@ function Functional_Test__do-Afterburner-only() # Expect failure and test "SMASH" unfinished/lock files Print_Info 'Running Hybrid-handler expecting crash in Afterburner software' BLACK_BOX_FAIL='smash_crashes' \ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with Afterburner software crashing.' return 1 @@ -91,7 +91,7 @@ function Functional_Test__do-Afterburner-only() ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-custom-input' # Expect failure when using custom input while also running the sampler @@ -109,7 +109,7 @@ function Functional_Test__do-Afterburner-only() File_Directory: "." ' "${run_id}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 @@ -130,7 +130,7 @@ function Functional_Test__do-Afterburner-only() List: File_Directory: "." ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' __static__Check_Successful_Handler_Run $? mv 'Afterburner' 'Afterburner-success-with-spectators' # Expect success and test the add_spectator functionality with custom spectator input @@ -151,7 +151,7 @@ function Functional_Test__do-Afterburner-only() List: File_Directory: "." ' "${run_id}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' __static__Check_Successful_Handler_Run $? || return 1 mv 'Afterburner' 'Afterburner-success-with-spectators' # Expect failure when combining custom spectator lists and running IC @@ -172,7 +172,7 @@ function Functional_Test__do-Afterburner-only() List: File_Directory: "." ' "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 diff --git a/tests/functional_tests_Hydro_only.bash b/tests/functional_tests_Hydro_only.bash index d940607..e03afbc 100644 --- a/tests/functional_tests_Hydro_only.bash +++ b/tests/functional_tests_Hydro_only.bash @@ -27,7 +27,7 @@ function Functional_Test__do-Hydro-only() mkdir -p "IC/${run_id}" touch "IC/${run_id}/SMASH_IC.dat" Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -ne 0 ]]; then Print_Error 'Hybrid-handler unexpectedly failed.' return 1 @@ -38,7 +38,7 @@ function Functional_Test__do-Hydro-only() Print_Info 'Running Hybrid-handler expecting invalid IC argument' terminal_output_file="Hydro/${run_id}/Terminal_Output.txt" BLACK_BOX_FAIL='invalid_input' \ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input for Hydro.' return 1 @@ -65,7 +65,7 @@ function Functional_Test__do-Hydro-only() mkdir -p test touch 'test/input' Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -ne 0 ]]; then Print_Error 'Hybrid-handler unexpectedly failed.' return 1 @@ -76,7 +76,7 @@ function Functional_Test__do-Hydro-only() Print_Info 'Running Hybrid-handler expecting invalid config argument' terminal_output_file="Hydro/${run_id}/Terminal_Output.txt" BLACK_BOX_FAIL='invalid_config' \ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Hydro.' return 1 @@ -93,7 +93,7 @@ function Functional_Test__do-Hydro-only() # Expect failure and test terminal output in the case of a crash of vHLLE Print_Info 'Running Hybrid-handler expecting crash in Hydro' BLACK_BOX_FAIL='crash' \ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with Hydro crashing.' return 1 @@ -115,7 +115,7 @@ function Functional_Test__do-Hydro-only() Input_file: %s/test/input ' "${run_id}" "$(pwd)" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 diff --git a/tests/functional_tests_IC_only.bash b/tests/functional_tests_IC_only.bash index d5781af..415fea9 100644 --- a/tests/functional_tests_IC_only.bash +++ b/tests/functional_tests_IC_only.bash @@ -22,7 +22,7 @@ function Functional_Test__do-IC-only() ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${config_filename}" # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -ne 0 ]]; then Print_Error 'Hybrid-handler unexpectedly failed.' return 1 @@ -33,7 +33,7 @@ function Functional_Test__do-IC-only() Print_Info 'Running Hybrid-handler expecting invalid IC input file failure' terminal_output_file="IC/${run_id}/Terminal_Output.txt" BLACK_BOX_FAIL='invalid_config' \ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid IC input.' return 1 @@ -50,7 +50,7 @@ function Functional_Test__do-IC-only() # Expect failure and test "SMASH" unfinished/lock files Print_Info 'Running Hybrid-handler expecting crash in IC software' BLACK_BOX_FAIL='smash_crashes' \ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with IC software crashing.' return 1 diff --git a/tests/functional_tests_Sampler_only.bash b/tests/functional_tests_Sampler_only.bash index c0c9823..db96c32 100644 --- a/tests/functional_tests_Sampler_only.bash +++ b/tests/functional_tests_Sampler_only.bash @@ -24,7 +24,7 @@ function Functional_Test__do-Sampler-only() ' "${run_id}" "${HYBRIDT_repository_top_level_path}" > "${hybrid_handler_config}" # Expect success and test presence of output files Print_Info 'Running Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" '-o' '.' if [[ $? -ne 0 ]]; then Print_Error 'Hybrid-handler unexpectedly failed.' return 1 @@ -36,7 +36,7 @@ function Functional_Test__do-Sampler-only() terminal_output_file="Sampler/${run_id}/Terminal_Output.txt" Print_Info 'Running Hybrid-handler expecting crash in Sampler' BLACK_BOX_FAIL='true' \ - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" '-o' '.' if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with Sampler crashing.' return 1 @@ -62,7 +62,7 @@ function Functional_Test__do-Sampler-only() Config_file: %s ' "${HYBRIDT_repository_top_level_path}" \ "${invalid_sampler_config}" > "${hybrid_handler_config}" - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" &> /dev/null + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${hybrid_handler_config}" '-o' '.' &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Hybrid-handler unexpectedly succeeded with invalid config for Sampler.' return 1 diff --git a/tests/functional_tests_full_workflow.bash b/tests/functional_tests_full_workflow.bash index c06c103..a57ffbf 100644 --- a/tests/functional_tests_full_workflow.bash +++ b/tests/functional_tests_full_workflow.bash @@ -27,7 +27,7 @@ function __static__Test_Full_Workflow() __static__Create_Auxiliaries_For_Hydro # Expect success and test absence of "SMASH" unfinished file Print_Info 'Running full workflow with Hybrid-handler expecting success' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' __static__Check_Outcome_Of_Full_Run $? } From d0e47e155876181b43a5fd7a37acdd39c373cad1 Mon Sep 17 00:00:00 2001 From: Zuzana Paulinyova Date: Wed, 21 Feb 2024 15:34:03 +0100 Subject: [PATCH 366/549] Adjust documentation --- docs/user/configuration-file.md | 4 ++-- docs/user/overview.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user/configuration-file.md b/docs/user/configuration-file.md index c533bda..6445eae 100644 --- a/docs/user/configuration-file.md +++ b/docs/user/configuration-file.md @@ -116,13 +116,13 @@ Sampler: ???+ config-key "`Add_spectators_from_IC`" Whether spectators from the initial conditions stage should be included or not in the afterburner run can be decided via this boolean key. - The default value is `false`. + The default value is `true`. ???+ config-key "`Spectators_source`" If spectators from the initial conditions stage should be included in the afterburner run, a :material-file: *SMASH_IC.oscar* file is expected to exist in the :file_folder: ***IC*** output sub-folder with the same `Run_ID`. However, using this key any file path can be specified. - This key is ignored, unless `Add_spectators_from_IC` is not set to `true`. + This key is ignored if `Add_spectators_from_IC` is set to `false`. ```yaml title="Example" Afterburner: diff --git a/docs/user/overview.md b/docs/user/overview.md index 4bb572fb1..05bb1c2 100644 --- a/docs/user/overview.md +++ b/docs/user/overview.md @@ -14,7 +14,7 @@ Few further customizations are possible using command line options, which are do ## The general behavior -The main `do` execution mode of the handler runs stages of the model and it will create a given output tree at the specified output directory (by default this is the folder from where the handler is run, but it can customized using the `-o` or `--output-directory` command line option). +The main `do` execution mode of the handler runs stages of the model and it will create a given output tree at the specified output directory (by default this is a subdirectory of the folder from where the handler is run named :file_folder: ***data***, but it can customized using the `-o` or `--output-directory` command line option). Assuming all stages are run, this is what the user will obtain. ``` { .bash .no-copy } 📂 Output-directory From 52cd9cd2a9e8f2a9a9d118dc27b3aaeaf44c8de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 22 Feb 2024 13:24:59 +0100 Subject: [PATCH 367/549] Correct usage of YAML utility function --- bash/sanity_checks.bash | 24 +++++++++----------- tests/functional_tests_Afterburner_only.bash | 7 +++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 9b5f6b7..10a548c 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -120,29 +120,27 @@ function __static__Set_Software_Input_Data_File_If_Not_Set_By_User() function Ensure_Consistency_Of_Afterburner_Input() { local config_section_input='Placeholder in case no input file is given.' - if Has_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Input_file'; then + if Has_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")" 'Afterburner' 'Input_file'; then config_section_input=$(Read_From_YAML_String_Given_Key "${HYBRID_configuration_file}" \ 'Afterburner' 'Input_file') fi - if Has_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Software_keys' \ + if Has_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' \ 'Modi' 'List' 'Filename'; then local given_filename - given_filename=$(Read_From_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Software_keys' \ - 'Modi' 'List' 'Filename') + given_filename=$(Read_From_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")"'Afterburner' \ + 'Software_keys' 'Modi' 'List' 'Filename') if [[ "$given_filename" != "${config_section_input}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys!' + 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys" \ + " specifying the input list filename!' fi fi - if Has_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Software_keys' 'Modi' \ - 'List' 'Shift_ID'; then + if Has_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' \ + 'List' 'Shift_ID' || Has_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")" 'Afterburner' \ + 'Software_keys' 'Modi' 'List' 'File_Prefix'; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys!' - fi - if Has_YAML_String_Given_Key "${HYBRID_configuration_file}" 'Afterburner' 'Software_keys' 'Modi' \ - 'List' 'File_Prefix'; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys!' + 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys" \ + " specifying the input list prefix and ID!' fi } diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 311510c..abf402d 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -106,7 +106,7 @@ function Functional_Test__do-Afterburner-only() List: File_Directory: "." ' "${run_id}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" - Print_Info 'Running Hybrid-handler expecting failure' + Print_Info 'Running Hybrid-handler expecting failure when using custom input while also running the sampler' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' @@ -125,7 +125,7 @@ function Functional_Test__do-Afterburner-only() File_Directory: "." Filename: "particle_lists_2.oscar" ' "${run_id}" "${HYBRIDT_tests_folder}" > "${config_filename}" - Print_Info 'Running Hybrid-handler expecting failure' + Print_Info 'Running Hybrid-handler expecting failure when specifying custom input via Software_keys Filename' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' @@ -145,7 +145,8 @@ function Functional_Test__do-Afterburner-only() File_Prefix: "sampling" Shift_Id: 0 ' "${run_id}" "${HYBRIDT_tests_folder}" > "${config_filename}" - Print_Info 'Running Hybrid-handler expecting failure' + Print_Info 'Running Hybrid-handler expecting failure when specifying custom input via" \ + " Software_keys Shift_Id/File_Prefix' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' From 42eb3168ebc8806793ffb1947e9547e016ef4218 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 22 Feb 2024 14:35:26 +0100 Subject: [PATCH 368/549] Fix function to ensure afterburner input consistency Due to some missing $-signs a command expansion had become a simple string and all checks to be performed were actually skipped. Once this was fixed some unit tests failed and have been fixed, too (the configuration file is now needed when testing the input preparation for the afterburner). Furthermore some output message was split using double quotes inside single quotes and this was not doing what is desired. --- bash/sanity_checks.bash | 31 +++++++++---------- tests/functional_tests_Afterburner_only.bash | 11 +++---- .../unit_tests_Afterburner_functionality.bash | 10 ++++-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 10a548c..ef41eb0 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -119,28 +119,27 @@ function __static__Set_Software_Input_Data_File_If_Not_Set_By_User() function Ensure_Consistency_Of_Afterburner_Input() { - local config_section_input='Placeholder in case no input file is given.' - if Has_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")" 'Afterburner' 'Input_file'; then - config_section_input=$(Read_From_YAML_String_Given_Key "${HYBRID_configuration_file}" \ - 'Afterburner' 'Input_file') - fi - if Has_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' \ - 'Modi' 'List' 'Filename'; then + Ensure_That_Given_Variables_Are_Set_And_Not_Empty 'HYBRID_software_input_file[Afterburner]' + if Has_YAML_String_Given_Key \ + "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'Filename'; then local given_filename - given_filename=$(Read_From_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")"'Afterburner' \ + given_filename=$(Read_From_YAML_String_Given_Key "$(< "${HYBRID_configuration_file}")" 'Afterburner' \ 'Software_keys' 'Modi' 'List' 'Filename') - if [[ "$given_filename" != "${config_section_input}" ]]; then + if [[ "${given_filename}" != "${HYBRID_software_input_file[Afterburner]}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys" \ - " specifying the input list filename!' + 'The Afterburner input particle list has to be modified via the ' \ + --emph 'Input_file' ' key,' 'not the ' --emph 'Software_keys' \ + ' specifying the input list filename!' fi fi - if Has_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' \ - 'List' 'Shift_ID' || Has_YAML_String_Given_Key "(< "${HYBRID_configuration_file}")" 'Afterburner' \ - 'Software_keys' 'Modi' 'List' 'File_Prefix'; then + if Has_YAML_String_Given_Key \ + "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'Shift_ID' \ + || Has_YAML_String_Given_Key \ + "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'File_Prefix'; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the Input_file key, not the Software_keys" \ - " specifying the input list prefix and ID!' + 'The Afterburner input particle list has to be modified via the ' \ + --emph 'Input_file' ' key,' 'not the ' --emph 'Software_keys' \ + ' specifying the input list prefix and ID!' fi } diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index abf402d..2b69b40 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -112,8 +112,7 @@ function Functional_Test__do-Afterburner-only() Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi - mv 'Afterburner' 'Afterburner-failure-custom-input' - # Expect failure when wrongly specifying custom input + # Expect failure when wrongly specifying custom input (I) printf ' Hybrid_handler: Run_ID: %s @@ -131,8 +130,7 @@ function Functional_Test__do-Afterburner-only() Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi - mv 'Afterburner' 'Afterburner-failure-wrong-specified-custom-input-shift-id' - # Expect failure when wrongly specifying custom input + # Expect failure when wrongly specifying custom input (II) printf ' Hybrid_handler: Run_ID: %s @@ -145,14 +143,13 @@ function Functional_Test__do-Afterburner-only() File_Prefix: "sampling" Shift_Id: 0 ' "${run_id}" "${HYBRIDT_tests_folder}" > "${config_filename}" - Print_Info 'Running Hybrid-handler expecting failure when specifying custom input via" \ - " Software_keys Shift_Id/File_Prefix' + Print_Info \ + 'Running Hybrid-handler expecting failure when specifying custom input via Software_keys Shift_Id/File_Prefix' Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 fi - mv 'Afterburner' 'Afterburner-failure-wrong-specified-custom-input-shift-id' # Expect success and test the add_spectator functionality Print_Info 'Running Hybrid-handler expecting success with the add_spectator option' mkdir -p "IC/${run_id}" diff --git a/tests/unit_tests_Afterburner_functionality.bash b/tests/unit_tests_Afterburner_functionality.bash index 3025e05..f8d7ef2 100644 --- a/tests/unit_tests_Afterburner_functionality.bash +++ b/tests/unit_tests_Afterburner_functionality.bash @@ -36,7 +36,7 @@ function Make_Test_Preliminary_Operations__Afterburner-create-input-file() function Unit_Test__Afterburner-create-input-file() { - touch "${HYBRID_software_base_config_file[Afterburner]}" + touch "${HYBRID_software_base_config_file[Afterburner]}" "${HYBRID_configuration_file}" mkdir -p "${HYBRID_software_output_directory[Sampler]}" local -r \ plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ @@ -59,7 +59,7 @@ function Unit_Test__Afterburner-create-input-file() function Clean_Tests_Environment_For_Following_Test__Afterburner-create-input-file() { - rm "${HYBRID_software_base_config_file[Afterburner]}" + rm "${HYBRID_software_base_config_file[Afterburner]}" "${HYBRID_configuration_file}" rm -r "${HYBRID_output_directory}" } @@ -80,7 +80,11 @@ function Unit_Test__Afterburner-create-input-file-with-spectators() plist_Sampler="${HYBRID_software_output_directory[Sampler]}/particle_lists.oscar" \ plist_IC="${HYBRID_software_output_directory[IC]}/SMASH_IC.oscar" \ plist_Final="${HYBRID_software_output_directory[Afterburner]}/${HYBRID_afterburner_list_filename}" - touch "${HYBRID_software_base_config_file[Afterburner]}" "${plist_Sampler}" "${plist_Final}" + touch \ + "${HYBRID_software_base_config_file[Afterburner]}" \ + "${plist_Sampler}" \ + "${plist_Final}" \ + "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Afterburner &> /dev/null if [[ $? -ne 110 ]]; then Print_Error \ From fad515a1cf4d11cabaf0932fdd266ac21ce82190 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 22 Feb 2024 16:05:05 +0100 Subject: [PATCH 369/549] Invoke handler in the same way in all functional tests --- tests/functional_tests_Afterburner_only.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional_tests_Afterburner_only.bash b/tests/functional_tests_Afterburner_only.bash index 58bd86e..804555f 100644 --- a/tests/functional_tests_Afterburner_only.bash +++ b/tests/functional_tests_Afterburner_only.bash @@ -109,7 +109,7 @@ function Functional_Test__do-Afterburner-only() File_Directory: "." ' "${run_id}" "${HYBRIDT_tests_folder}" "$(pwd)" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure when using custom input while also running the sampler' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 @@ -127,7 +127,7 @@ function Functional_Test__do-Afterburner-only() Filename: "particle_lists_2.oscar" ' "${run_id}" "${HYBRIDT_tests_folder}" > "${config_filename}" Print_Info 'Running Hybrid-handler expecting failure when specifying custom input via Software_keys Filename' - Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" &> /dev/null + Run_Hybrid_Handler_With_Given_Options_In_Subshell 'do' '-c' "${config_filename}" '-o' '.' &> /dev/null if [[ $? -ne 110 ]]; then Print_Error 'Hybrid-handler did not fail as expected with exit code 110.' return 1 From 2aaf2ba15482290c9a4272ab363fbf6e95bd5126 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 7 Feb 2024 14:03:58 +0100 Subject: [PATCH 370/549] Add new key to software sections to specify scan parameters --- bash/global_variables.bash | 10 ++++++++++ tests/unit_tests_configuration.bash | 26 ++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 2c03198..96200d6 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -65,23 +65,27 @@ function Define_Further_Global_Variables() declare -rgA HYBRID_ic_valid_keys=( [Executable]='HYBRID_software_executable[IC]' [Config_file]='HYBRID_software_base_config_file[IC]' + [Scan_parameters]='HYBRID_scan_parameters[IC]' [Software_keys]='HYBRID_software_new_input_keys[IC]' ) declare -rgA HYBRID_hydro_valid_keys=( [Executable]='HYBRID_software_executable[Hydro]' [Config_file]='HYBRID_software_base_config_file[Hydro]' [Input_file]='HYBRID_software_user_custom_input_file[Hydro]' + [Scan_parameters]='HYBRID_scan_parameters[Hydro]' [Software_keys]='HYBRID_software_new_input_keys[Hydro]' ) declare -rgA HYBRID_sampler_valid_keys=( [Executable]='HYBRID_software_executable[Sampler]' [Config_file]='HYBRID_software_base_config_file[Sampler]' + [Scan_parameters]='HYBRID_scan_parameters[Sampler]' [Software_keys]='HYBRID_software_new_input_keys[Sampler]' ) declare -rgA HYBRID_afterburner_valid_keys=( [Executable]='HYBRID_software_executable[Afterburner]' [Config_file]='HYBRID_software_base_config_file[Afterburner]' [Input_file]='HYBRID_software_user_custom_input_file[Afterburner]' + [Scan_parameters]='HYBRID_scan_parameters[Afterburner]' [Software_keys]='HYBRID_software_new_input_keys[Afterburner]' [Add_spectators_from_IC]='HYBRID_optional_feature[Add_spectators_from_IC]' [Spectators_source]='HYBRID_optional_feature[Spectators_source]' @@ -118,6 +122,12 @@ function Define_Further_Global_Variables() [Sampler]="${HYBRID_default_configurations_folder}/hadron_sampler" [Afterburner]="${HYBRID_default_configurations_folder}/smash_afterburner.yaml" ) + declare -gA HYBRID_scan_parameters=( + [IC]='' + [Hydro]='' + [Sampler]='' + [Afterburner]='' + ) declare -gA HYBRID_software_new_input_keys=( [IC]='' [Hydro]='' diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index cead138..de9e8d3 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -216,12 +216,13 @@ function Unit_Test__configuration-parse-general-section() function __static__Test_Section_Parsing_In_Subshell() ( - local section executable input_file new_keys + local section executable input_file scan_params new_keys section=$1 executable=$2 config_file=$3 input_file=$4 - new_keys=$5 + scan_params=$5 + new_keys=$6 Call_Codebase_Function Validate_And_Parse_Configuration_File if [[ "${#HYBRID_given_software_sections[@]}" -ne 1 ]] \ || [[ "${HYBRID_given_software_sections[0]}" != "${section}" ]]; then @@ -236,6 +237,9 @@ function __static__Test_Section_Parsing_In_Subshell() if [[ ${HYBRID_software_user_custom_input_file[${section}]} != "${input_file}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (input file).' fi + if [[ ${HYBRID_scan_parameters[${section}]} != "${scan_params}" ]]; then + Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (scan parameters).' + fi if [[ ${HYBRID_software_new_input_keys[${section}]} != "${new_keys}" ]]; then Print_Fatal_And_Exit 'Parsing of ' --emph "${section}" ' section failed (software keys).' fi @@ -253,12 +257,14 @@ function Unit_Test__configuration-parse-IC-section() IC: Executable: foo Config_file: bar + Scan_parameters: + - General.Randomseed Software_keys: General: Randomseed: 12345 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell \ - 'IC' 'foo' 'bar' '' $'General:\n Randomseed: 12345' + 'IC' 'foo' 'bar' '' '- General.Randomseed' $'General:\n Randomseed: 12345' if [[ $? -ne 0 ]]; then return 1 fi @@ -280,11 +286,12 @@ function Unit_Test__configuration-parse-Hydro-section() Executable: foo Config_file: bar Input_file: ket + Scan_parameters: [etaS] Software_keys: etaS: 0.12345 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell \ - 'Hydro' 'foo' 'bar' 'ket' 'etaS: 0.12345' + 'Hydro' 'foo' 'bar' 'ket' '[etaS]' 'etaS: 0.12345' if [[ $? -ne 0 ]]; then return 1 fi @@ -305,11 +312,12 @@ function Unit_Test__configuration-parse-Sampler-section() Sampler: Executable: foo Config_file: bar + Scan_parameters: [shear] Software_keys: shear: 1.2345 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell \ - 'Sampler' 'foo' 'bar' '' 'shear: 1.2345' + 'Sampler' 'foo' 'bar' '' '[shear]' 'shear: 1.2345' if [[ $? -ne 0 ]]; then return 1 fi @@ -331,12 +339,18 @@ function Unit_Test__configuration-parse-Afterburner-section() Executable: foo Config_file: bar Input_file: ket + Scan_parameters: + - General.End_Time + - General.Randomseed Software_keys: General: End_Time: 42000 + Randomseed: 42 ' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell __static__Test_Section_Parsing_In_Subshell \ - 'Afterburner' 'foo' 'bar' 'ket' $'General:\n End_Time: 42000' + 'Afterburner' 'foo' 'bar' 'ket' \ + $'- General.End_Time\n- General.Randomseed' \ + $'General:\n End_Time: 42000\n Randomseed: 42' if [[ $? -ne 0 ]]; then return 1 fi From ad025b44dcf4b228412187dd5dd897a558b27f23 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 7 Feb 2024 14:54:14 +0100 Subject: [PATCH 371/549] Add functionality to enforce format of scan parameters lists --- bash/parameters_scan.bash | 19 +++++++++++ bash/source_codebase_files.bash | 1 + tests/unit_tests_parameters_scan.bash | 46 +++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 bash/parameters_scan.bash create mode 100644 tests/unit_tests_parameters_scan.bash diff --git a/bash/parameters_scan.bash b/bash/parameters_scan.bash new file mode 100644 index 0000000..1bb4130 --- /dev/null +++ b/bash/parameters_scan.bash @@ -0,0 +1,19 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# Since it is not know here how the user has specified the list of scan parameters +# in the YAML configuration, we enforce here the "flow" yq-style, i.e. '[...]' +# -> https://mikefarah.gitbook.io/yq/operators/style#set-flow-quote-style +function Format_Scan_Parameters_Lists() +{ + local key + for key in "${!HYBRID_scan_parameters[@]}"; do + HYBRID_scan_parameters["${key}"]=$(yq '.. style="flow"' <<< "${HYBRID_scan_parameters["${key}"]}") + done +} diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 043ff66..13d59bf 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -31,6 +31,7 @@ function __static__Source_Codebase_Files() 'global_variables.bash' 'Hydro_functionality.bash' 'IC_functionality.bash' + 'parameters_scan.bash' 'Sampler_functionality.bash' 'sanity_checks.bash' 'software_input_functionality.bash' diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash new file mode 100644 index 0000000..2083d96 --- /dev/null +++ b/tests/unit_tests_parameters_scan.bash @@ -0,0 +1,46 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Test_Preliminary_Operations__parameters-scan-format-lists() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'global_variables.bash' + 'parameters_scan.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables +} + +function Unit_Test__parameters-scan-format-lists() +{ + HYBRID_scan_parameters=( + [IC]=$'- General.Randomseed\n- General.End_Time' + [Hydro]='[etaS,e_crit]' + [Sampler]='[shear, bulk]' + [Afterburner]='' + ) + Call_Codebase_Function Format_Scan_Parameters_Lists + declare -Ar reference_values=( + [IC]='[General.Randomseed, General.End_Time]' + [Hydro]='[etaS, e_crit]' + [Sampler]='[shear, bulk]' + [Afterburner]='' + ) + local key counter=0 + for key in "${!reference_values[@]}"; do + if [[ "${HYBRID_scan_parameters["${key}"]}" != "${reference_values["${key}"]}" ]]; then + Print_Error 'Formatting of ' --emph "${key}" ' scan parameters failed.' + ((counter++)) + fi + done + return ${counter} +} From 11be429cdfbf57c393457c9aae53f594ffde2bcb Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 8 Feb 2024 13:18:45 +0100 Subject: [PATCH 372/549] Implement validation mechanism of yaml single parameter scan Although at the moment only a scan with a list of values is accepted, a simple way to extend this adding new scans has been in place. A new scan should be declared as valid and its validation should be added. --- bash/global_variables.bash | 6 +++ bash/parameters_scan.bash | 53 +++++++++++++++++++++++++++ tests/unit_tests_parameters_scan.bash | 32 ++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 96200d6..2e5ccf7 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -57,6 +57,12 @@ function Define_Further_Global_Variables() [Sampler]='Hybrid_handler_Sampler_config.yaml' [Afterburner]='Hybrid_handler_Afterburner_config.yaml' ) + # This array specifies a set of YAML lists of keys that define a valid parameter scan. + # ATTENTION: The keys inside each YAML list must be alphabetically sorted to allow + # the validation mechanism to work! + readonly HYBRID_valid_scan_specification_keys=( + '[Values]' + ) # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=( diff --git a/bash/parameters_scan.bash b/bash/parameters_scan.bash index 1bb4130..3a30083 100644 --- a/bash/parameters_scan.bash +++ b/bash/parameters_scan.bash @@ -17,3 +17,56 @@ function Format_Scan_Parameters_Lists() HYBRID_scan_parameters["${key}"]=$(yq '.. style="flow"' <<< "${HYBRID_scan_parameters["${key}"]}") done } + +function __static__Is_Given_Key_Value_A_Valid_Scan() +{ + local -r value="$1" + if [[ $(yq '. | type' <<< "${value}") != '!!map' ]]; then + Print_Error 'The given scan\n' --emph "${value}" '\nis not a YAML map.' + return 1 + elif [[ $(yq '. | keys | .. style="flow"' <<< "${value}") != '[Scan]' ]]; then + Print_Error \ + 'The given scan\n' --emph "${value}" '\nis not a YAML map containing only the ' \ + --emph 'Scan' ' key at top level.' + return 1 + fi + local -r scan_keys=$(yq '.Scan | keys | .. style="flow"' <<< "${value}") + if ! __static__Are_Given_Scan_Keys_Allowed; then + Print_Error \ + 'The value\n' --emph "${value}" '\ndoes not define a valid scan.' \ + 'Refer to the documentation to see which are valid scan specifications.' + return 1 + elif ! __static__Has_Valid_Scan_Correct_Values; then + Print_Error -l --\ + 'The given scan\n' --emph "${value}" '\nis allowed but its specification is invalid.' + return 1 + fi +} + +function __static__Are_Given_Scan_Keys_Allowed() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty scan_keys + # Here we need to take into account that the Scan keys might be given by the + # user in an arbitrary order and therefore we need to sort them before comparison + local given_keys=$(yq '. | sort | .. style="flow"' <<< "${scan_keys}") + Element_In_Array_Equals_To "${given_keys}" "${HYBRID_valid_scan_specification_keys[@]}" +} + +function __static__Has_Valid_Scan_Correct_Values() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty value scan_keys + case "${scan_keys}" in + "[Values]" ) + if [[ $(yq '.Scan.Values | type' <<< "${value}") != '!!seq' ]]; then + Print_Error \ + 'The value ' --emph "$(yq '.Scan.Values' <<< "${value}")" \ + ' of the ' --emph 'Values' ' key is not a list of parameter values.' + return 1 + fi + ;; + *) + Print_Internal_And_Exit \ + 'Unknown scan passed to values validation function' --emph "${FUNCNAME}" '.' + ;; + esac +} diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index 2083d96..5a60f96 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -44,3 +44,35 @@ function Unit_Test__parameters-scan-format-lists() done return ${counter} } + +function Make_Test_Preliminary_Operations__parameters-scan-YAML-scan-syntax() +{ + Make_Test_Preliminary_Operations__parameters-scan-format-lists +} + +function Unit_Test__parameters-scan-YAML-scan-syntax() +{ + local value values + values=( + 'scalar' '[a,b,c]' 'True' '42' # Not a map + '{Scan: XX, Wrong: YY}' # Not a map with Scan only + # Wrong scans + '{Scan: {Wrong: XX}}' + '{Scan: {Values: String}}' + '{Scan: {Values: True}}' + '{Scan: {Values: 42}}' + ) + for value in "${values[@]}" ; do + Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Scan syntax validation for\n' --emph "${value}" '\nunexpectedly succeeded.' + return 1 + fi + done + value='{Scan: {Values: [a,b,c]}}' + Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" + if [[ $? -ne 0 ]]; then + Print_Error 'Scan syntax validation unexpectedly failed (' --emph "${value}" ').' + return 1 + fi +} From fb52a54be98e22a0f623477292d888a1bb3d5cdc Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 8 Feb 2024 14:51:22 +0100 Subject: [PATCH 373/549] Refactor scan YAML validation using IOSP principle --- bash/parameters_scan.bash | 69 +++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/bash/parameters_scan.bash b/bash/parameters_scan.bash index 3a30083..424f7b8 100644 --- a/bash/parameters_scan.bash +++ b/bash/parameters_scan.bash @@ -20,46 +20,73 @@ function Format_Scan_Parameters_Lists() function __static__Is_Given_Key_Value_A_Valid_Scan() { - local -r value="$1" - if [[ $(yq '. | type' <<< "${value}") != '!!map' ]]; then - Print_Error 'The given scan\n' --emph "${value}" '\nis not a YAML map.' + local -r given_scan="$1" + __static__Check_If_Given_Scan_Is_A_YAML_Map || return 1 + __static__Check_If_Given_Scan_Is_A_YAML_Map_With_Scan_Key_Only || return 1 + # Now the scan keys can be safely extracted + local -r scan_keys=$(__static__Get_Scan_Keys) + __static__Check_If_Given_Scan_Keys_Are_Allowed || return 1 + __static__Check_If_Keys_Of_Given_Scan_Have_Correct_Values || return 1 +} + +function __static__Check_If_Given_Scan_Is_A_YAML_Map() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan + if [[ $(yq '. | type' <<< "${given_scan}") != '!!map' ]]; then + Print_Error 'The given scan\n' --emph "${given_scan}" '\nis not a YAML map.' return 1 - elif [[ $(yq '. | keys | .. style="flow"' <<< "${value}") != '[Scan]' ]]; then + fi +} + +function __static__Check_If_Given_Scan_Is_A_YAML_Map_With_Scan_Key_Only() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan + if [[ $(yq '. | keys | .. style="flow"' <<< "${given_scan}") != '[Scan]' ]]; then Print_Error \ - 'The given scan\n' --emph "${value}" '\nis not a YAML map containing only the ' \ + 'The given scan\n' --emph "${given_scan}" '\nis not a YAML map containing only the ' \ --emph 'Scan' ' key at top level.' return 1 fi - local -r scan_keys=$(yq '.Scan | keys | .. style="flow"' <<< "${value}") - if ! __static__Are_Given_Scan_Keys_Allowed; then +} + +function __static__Get_Scan_Keys() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan + yq '.Scan | keys | .. style="flow"' <<< "${given_scan}" +} + +function __static__Check_If_Given_Scan_Keys_Are_Allowed() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan scan_keys + # Here we need to take into account that the Scan keys might be given by the + # user in an arbitrary order and therefore we need to sort them before comparison + local given_keys=$(yq '. | sort | .. style="flow"' <<< "${scan_keys}") + if ! Element_In_Array_Equals_To "${given_keys}" "${HYBRID_valid_scan_specification_keys[@]}"; then Print_Error \ - 'The value\n' --emph "${value}" '\ndoes not define a valid scan.' \ + 'The value\n' --emph "${given_scan}" '\ndoes not define a valid scan.' \ 'Refer to the documentation to see which are valid scan specifications.' return 1 - elif ! __static__Has_Valid_Scan_Correct_Values; then - Print_Error -l --\ - 'The given scan\n' --emph "${value}" '\nis allowed but its specification is invalid.' - return 1 fi } -function __static__Are_Given_Scan_Keys_Allowed() +function __static__Check_If_Keys_Of_Given_Scan_Have_Correct_Values() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty scan_keys - # Here we need to take into account that the Scan keys might be given by the - # user in an arbitrary order and therefore we need to sort them before comparison - local given_keys=$(yq '. | sort | .. style="flow"' <<< "${scan_keys}") - Element_In_Array_Equals_To "${given_keys}" "${HYBRID_valid_scan_specification_keys[@]}" + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan scan_keys + if ! __static__Has_Valid_Scan_Correct_Values; then + Print_Error -l --\ + 'The given scan\n' --emph "${given_scan}" '\nis allowed but its specification is invalid.' + return 1 + fi } function __static__Has_Valid_Scan_Correct_Values() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty value scan_keys + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan scan_keys case "${scan_keys}" in "[Values]" ) - if [[ $(yq '.Scan.Values | type' <<< "${value}") != '!!seq' ]]; then + if [[ $(yq '.Scan.Values | type' <<< "${given_scan}") != '!!seq' ]]; then Print_Error \ - 'The value ' --emph "$(yq '.Scan.Values' <<< "${value}")" \ + 'The value ' --emph "$(yq '.Scan.Values' <<< "${given_scan}")" \ ' of the ' --emph 'Values' ' key is not a list of parameter values.' return 1 fi From 57c27614f9dc2832a81a49e58fe09fabcb6be8bd Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 8 Feb 2024 15:03:24 +0100 Subject: [PATCH 374/549] Add logical validation of single parameter scan --- bash/parameters_scan.bash | 22 ++++++++++++++ tests/unit_tests_parameters_scan.bash | 43 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/bash/parameters_scan.bash b/bash/parameters_scan.bash index 424f7b8..05873bd 100644 --- a/bash/parameters_scan.bash +++ b/bash/parameters_scan.bash @@ -18,6 +18,28 @@ function Format_Scan_Parameters_Lists() done } +function __static__Is_Parameter_To_Be_Scanned() +{ + local -r key="$1" yaml_section="$2" + # Here the key is assumed to be a period-separated list of keys to navigate the YAML + # tree. However the utility functions needs separate arguments and we let word-splitting + # help us, by that also assuming that there are no spaces in keys (a kind of design decision). + if ! Has_YAML_String_Given_Key "${yaml_section}" ${key//./ }; then + Print_Error \ + 'The ' --emph "${key}" ' parameter is asked to be scanned but its value' \ + 'was not specified in the corresponding ' --emph 'Software_keys' '.' + return 1 + fi + local parameter_value + parameter_value=$(Read_From_YAML_String_Given_Key "${yaml_section}" ${key//./ }) + if ! __static__Is_Given_Key_Value_A_Valid_Scan "${parameter_value}"; then + Print_Error -l --\ + 'The ' --emph "${key}" \ + ' parameter is asked to be scanned but its value was not properly specified as a scan.' + return 1 + fi +} + function __static__Is_Given_Key_Value_A_Valid_Scan() { local -r given_scan="$1" diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index 5a60f96..10a06e5 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -76,3 +76,46 @@ function Unit_Test__parameters-scan-YAML-scan-syntax() return 1 fi } + +function Make_Test_Preliminary_Operations__parameters-scan-single-validation() +{ + Make_Test_Preliminary_Operations__parameters-scan-format-lists +} + +function __static__Test_Validation_Of_Parameter() +{ + local -r section=$1 key=$2 new_keys=$3 expect=$4 + if [[ "${expect}" = 'EXPECT_SUCCESS' ]]; then + Call_Codebase_Function __static__Is_Parameter_To_Be_Scanned "${key}" "${new_keys}" + if [[ $? -ne 0 ]]; then + Print_Error 'Validation of ' --emph "${section}" ' parameter unexpectedly failed.' + return 1 + fi + else + Call_Codebase_Function __static__Is_Parameter_To_Be_Scanned "${key}" "${new_keys}" &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of ' --emph "${section}" ' parameter unexpectedly succeeded.' + return 1 + fi + fi +} + +function Unit_Test__parameters-scan-single-validation() +{ + HYBRID_software_new_input_keys=( + [IC]=$'Modi:\n Collider:\n Sqrtsnn: {Scan: {Values: [4.3, 7.7]}}' + [Hydro]='etaS: {Scan: {Values: [0.13, 0.15, 0.17]}}' + [Sampler]='shear: 1.' + [Afterburner]='' + ) + __static__Test_Validation_Of_Parameter \ + 'IC' 'Modi.Collider.Sqrtsnn' "${HYBRID_software_new_input_keys[IC]}" 'EXPECT_SUCCESS' || return 1 + __static__Test_Validation_Of_Parameter \ + 'Hydro' 'etaS' "${HYBRID_software_new_input_keys[Hydro]}" 'EXPECT_SUCCESS' || return 1 + __static__Test_Validation_Of_Parameter \ + 'Sampler' 'shear' "${HYBRID_software_new_input_keys[Sampler]}" 'EXPECT_FAILURE' || return 1 + __static__Test_Validation_Of_Parameter \ + 'Afterburner ' 'General.Randomseed' \ + "${HYBRID_software_new_input_keys[Afterburner]}" 'EXPECT_FAILURE' || return 1 +} + From 3c90c1ae853a7adc975c0a6ff523fe24017302fe Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 8 Feb 2024 15:39:13 +0100 Subject: [PATCH 375/549] Implement general parameters scan validation --- bash/parameters_scan.bash | 23 ++++++++++++++++++ tests/unit_tests_parameters_scan.bash | 34 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/bash/parameters_scan.bash b/bash/parameters_scan.bash index 05873bd..68b09ec 100644 --- a/bash/parameters_scan.bash +++ b/bash/parameters_scan.bash @@ -18,6 +18,29 @@ function Format_Scan_Parameters_Lists() done } +function Validate_Scan_Parameters() +{ + local key parameters parameter counter=0 + for key in "${!HYBRID_scan_parameters[@]}"; do + if [[ "${HYBRID_scan_parameters["${key}"]}" = '' ]]; then + continue + else + readarray -t parameters < <(yq -r '.[]' <<< "${HYBRID_scan_parameters["${key}"]}") + for parameter in "${parameters[@]}"; do + if ! __static__Is_Parameter_To_Be_Scanned \ + "${parameter}" "${HYBRID_software_new_input_keys["${key}"]}"; then + (( counter++ )) || true + fi + done + fi + done + if [[ ${counter} -ne 0 ]]; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ + '\nThe hybrid handler configuration file contains '\ + --emph "${counter}" ' invalid scan specifications.' + fi +} + function __static__Is_Parameter_To_Be_Scanned() { local -r key="$1" yaml_section="$2" diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index 10a06e5..21b349a 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -119,3 +119,37 @@ function Unit_Test__parameters-scan-single-validation() "${HYBRID_software_new_input_keys[Afterburner]}" 'EXPECT_FAILURE' || return 1 } +function Make_Test_Preliminary_Operations__parameters-scan-global-validation() +{ + Make_Test_Preliminary_Operations__parameters-scan-format-lists +} + +function Unit_Test__parameters-scan-global-validation() +{ + HYBRID_scan_parameters[IC]='[Modi.Collider.Sqrtsnn]' + HYBRID_scan_parameters[Hydro]='[etaS]' + HYBRID_software_new_input_keys[IC]=$'Modi:\n Collider:\n Sqrtsnn: {Scan: {Values: [4.3, 7.7]}}' + HYBRID_software_new_input_keys[Hydro]='etaS: {Scan: {Values: [0.13, 0.15, 0.17]}}' + Call_Codebase_Function_In_Subshell Validate_Scan_Parameters + if [[ $? -ne 0 ]]; then + Print_Error 'Validation of scan parameters unexpectedly failed.' + return 1 + fi + HYBRID_scan_parameters=( + [IC]='' + [Hydro]='' + [Sampler]='[shear]' + [Afterburner]='[General.Randomseed]' + ) + HYBRID_software_new_input_keys=( + [IC]='' + [Hydro]='' + [Sampler]='shear: 1.' + [Afterburner]='' + ) + Call_Codebase_Function_In_Subshell Validate_Scan_Parameters #&> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of scan parameters unexpectedly succeeded.' + return 1 + fi +} From b25fda116fafe14e74a90d125de158aae79b7b2b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 8 Feb 2024 15:40:58 +0100 Subject: [PATCH 376/549] Rename file with more specific name --- bash/{parameters_scan.bash => parameters_scan_validation.bash} | 0 bash/source_codebase_files.bash | 2 +- tests/unit_tests_parameters_scan.bash | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename bash/{parameters_scan.bash => parameters_scan_validation.bash} (100%) diff --git a/bash/parameters_scan.bash b/bash/parameters_scan_validation.bash similarity index 100% rename from bash/parameters_scan.bash rename to bash/parameters_scan_validation.bash diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 13d59bf..522fed3 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -31,7 +31,7 @@ function __static__Source_Codebase_Files() 'global_variables.bash' 'Hydro_functionality.bash' 'IC_functionality.bash' - 'parameters_scan.bash' + 'parameters_scan_validation.bash' 'Sampler_functionality.bash' 'sanity_checks.bash' 'software_input_functionality.bash' diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index 21b349a..bd58056 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -12,7 +12,7 @@ function Make_Test_Preliminary_Operations__parameters-scan-format-lists() local file_to_be_sourced list_of_files list_of_files=( 'global_variables.bash' - 'parameters_scan.bash' + 'parameters_scan_validation.bash' ) for file_to_be_sourced in "${list_of_files[@]}"; do source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} From ed6c28f3b0a3ef18c558875a2cda96879c8d7a3f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 8 Feb 2024 16:39:04 +0100 Subject: [PATCH 377/549] Restrict scans of parameters to bool, int, float type only Furthermore, it is enforced that the values must have the same type, which is surely a reasonable assumption. --- bash/parameters_scan_validation.bash | 12 ++++++++++++ tests/unit_tests_parameters_scan.bash | 7 +++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/bash/parameters_scan_validation.bash b/bash/parameters_scan_validation.bash index 68b09ec..f2b77b8 100644 --- a/bash/parameters_scan_validation.bash +++ b/bash/parameters_scan_validation.bash @@ -135,6 +135,18 @@ function __static__Has_Valid_Scan_Correct_Values() ' of the ' --emph 'Values' ' key is not a list of parameter values.' return 1 fi + local list_of_value_types + list_of_value_types=( $(yq '.Scan.Values[] | type' <<< "${given_scan}" | sort -u) ) + if [[ ${#list_of_value_types[@]} -ne 1 ]]; then + Print_Error \ + 'The parameter values have different YAML types: ' --emph "${list_of_value_types[*]//!!/}" '.' + return 1 + elif [[ ! ${list_of_value_types[0]} =~ ^!!(bool|int|float)$ ]]; then + Print_Error \ + 'Parameter scans with values of ' --emph "${list_of_value_types[0]//!!/}" \ + ' type are not allowed.' 'Valid parameter types are ' --emph 'bool int float' ', only.' + return 1 + fi ;; *) Print_Internal_And_Exit \ diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index bd58056..d2e6b53 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -61,15 +61,18 @@ function Unit_Test__parameters-scan-YAML-scan-syntax() '{Scan: {Values: String}}' '{Scan: {Values: True}}' '{Scan: {Values: 42}}' + '{Scan: {Values: [42, False, 3.14]}}' + '{Scan: {Values: [42, 3.14]}}' + '{Scan: {Values: ["Hi", "Bye"]}}' ) for value in "${values[@]}" ; do - Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" &> /dev/null + Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" #&> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Scan syntax validation for\n' --emph "${value}" '\nunexpectedly succeeded.' return 1 fi done - value='{Scan: {Values: [a,b,c]}}' + value='{Scan: {Values: [1,2,3]}}' Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" if [[ $? -ne 0 ]]; then Print_Error 'Scan syntax validation unexpectedly failed (' --emph "${value}" ').' From c438c5180de0a3eb90cc0692e3c3aa10bcaf3270 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 8 Feb 2024 17:22:19 +0100 Subject: [PATCH 378/549] Slightly improve implementation of scan validation --- bash/parameters_scan_validation.bash | 25 ++++++++++++------------- tests/unit_tests_parameters_scan.bash | 4 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/bash/parameters_scan_validation.bash b/bash/parameters_scan_validation.bash index f2b77b8..4382ce9 100644 --- a/bash/parameters_scan_validation.bash +++ b/bash/parameters_scan_validation.bash @@ -68,8 +68,10 @@ function __static__Is_Given_Key_Value_A_Valid_Scan() local -r given_scan="$1" __static__Check_If_Given_Scan_Is_A_YAML_Map || return 1 __static__Check_If_Given_Scan_Is_A_YAML_Map_With_Scan_Key_Only || return 1 - # Now the scan keys can be safely extracted - local -r scan_keys=$(__static__Get_Scan_Keys) + # Now the scan keys can be safely extracted. Note that here we need to take into + # account that the Scan keys might be given by the user in an arbitrary order + # and therefore we need to sort them before comparison + local -r sorted_scan_keys=$(__static__Get_Sorted_Scan_Keys) __static__Check_If_Given_Scan_Keys_Are_Allowed || return 1 __static__Check_If_Keys_Of_Given_Scan_Have_Correct_Values || return 1 } @@ -94,21 +96,18 @@ function __static__Check_If_Given_Scan_Is_A_YAML_Map_With_Scan_Key_Only() fi } -function __static__Get_Scan_Keys() +function __static__Get_Sorted_Scan_Keys() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan - yq '.Scan | keys | .. style="flow"' <<< "${given_scan}" + yq '.Scan | keys | sort | .. style="flow"' <<< "${given_scan}" } function __static__Check_If_Given_Scan_Keys_Are_Allowed() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan scan_keys - # Here we need to take into account that the Scan keys might be given by the - # user in an arbitrary order and therefore we need to sort them before comparison - local given_keys=$(yq '. | sort | .. style="flow"' <<< "${scan_keys}") - if ! Element_In_Array_Equals_To "${given_keys}" "${HYBRID_valid_scan_specification_keys[@]}"; then + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys + if ! Element_In_Array_Equals_To "${sorted_scan_keys}" "${HYBRID_valid_scan_specification_keys[@]}"; then Print_Error \ - 'The value\n' --emph "${given_scan}" '\ndoes not define a valid scan.' \ + 'The value\n' --emph "${sorted_scan_keys}" '\ndoes not define a valid scan.' \ 'Refer to the documentation to see which are valid scan specifications.' return 1 fi @@ -116,7 +115,7 @@ function __static__Check_If_Given_Scan_Keys_Are_Allowed() function __static__Check_If_Keys_Of_Given_Scan_Have_Correct_Values() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan scan_keys + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys if ! __static__Has_Valid_Scan_Correct_Values; then Print_Error -l --\ 'The given scan\n' --emph "${given_scan}" '\nis allowed but its specification is invalid.' @@ -126,8 +125,8 @@ function __static__Check_If_Keys_Of_Given_Scan_Have_Correct_Values() function __static__Has_Valid_Scan_Correct_Values() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan scan_keys - case "${scan_keys}" in + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys + case "${sorted_scan_keys}" in "[Values]" ) if [[ $(yq '.Scan.Values | type' <<< "${given_scan}") != '!!seq' ]]; then Print_Error \ diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index d2e6b53..576855c 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -66,7 +66,7 @@ function Unit_Test__parameters-scan-YAML-scan-syntax() '{Scan: {Values: ["Hi", "Bye"]}}' ) for value in "${values[@]}" ; do - Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" #&> /dev/null + Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Scan syntax validation for\n' --emph "${value}" '\nunexpectedly succeeded.' return 1 @@ -150,7 +150,7 @@ function Unit_Test__parameters-scan-global-validation() [Sampler]='shear: 1.' [Afterburner]='' ) - Call_Codebase_Function_In_Subshell Validate_Scan_Parameters #&> /dev/null + Call_Codebase_Function_In_Subshell Validate_Scan_Parameters &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of scan parameters unexpectedly succeeded.' return 1 From 2aee9e953b6604bf81df65170f4fa7213687b752 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 9 Feb 2024 11:49:09 +0100 Subject: [PATCH 379/549] Import new changes from BashLogger --- bash/logger.bash | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bash/logger.bash b/bash/logger.bash index 7196aba..e6f4cb4 100644 --- a/bash/logger.bash +++ b/bash/logger.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -13,7 +13,7 @@ # #---------------------------------------------------------------------------------------- # -# Copyright (c) 2019,2023 +# Copyright (c) 2019,2023-2024 # Alessandro Sciarra # # This file is part of BashLogger. @@ -161,6 +161,8 @@ function __static__Logger() { if [[ $# -lt 1 ]]; then __static__Logger 'INTERNAL' "${FUNCNAME} called without label!" + elif [[ $# -lt 2 ]]; then + set -- "$1" '' # Set empty message if none provided fi local label labelLength labelToBePrinted color emphColor finalEndline restoreDefault finalEndline='\n' From e901cf868a816ad6fba325cbd704dcb668b1308d Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 9 Feb 2024 11:50:02 +0100 Subject: [PATCH 380/549] Make utility function fail in a better way For associative arrays set but empty the function to ensure they are not empty was failing with an unbound variable accessed. Now the descriptive internal error is printed instead. --- bash/utility_functions.bash | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index ab00951..3919073 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -332,11 +332,14 @@ function Ensure_That_Given_Variables_Are_Set_And_Not_Empty() # would return the variable declared attributes (e.g. 'a' for arrays). if [[ $(declare -p "${variable_name}" 2> /dev/null) =~ ^declare\ -[aA] ]]; then declare -n ref=${variable_name} + set +u # Here 'ref' might be unset for empty associative arrays! Do not exit if so if [[ ${#ref[@]} -ne 0 ]]; then + set -u continue fi + set -u else - set +u # Here variable_name might be unset! Do not exit if so + set +u # Here 'variable_name' might be unset! Do not exit if so if [[ "${!variable_name}" != '' ]]; then set -u continue From e661bd47b3dc6acd3423a23076d4d129ad4d1652 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 9 Feb 2024 11:53:40 +0100 Subject: [PATCH 381/549] Store scan objects while validating them It makes sense to also store the scan object while validating them. The validation requires quite some operations to go through the YAML sections and most of this would be needed again to extract the scanning information before producing all the possible combinations. It is therefore handy to immediatelty store the validated information. This is temporarily done into the associative array which will eventually contain the list of values per parameter. This has the advantage that that single array will be an in-out parameter for the functionality that has to work out the values of each parameter. --- bash/parameters_scan_validation.bash | 21 ++++++++++++++++----- tests/unit_tests_parameters_scan.bash | 11 ++++++++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/bash/parameters_scan_validation.bash b/bash/parameters_scan_validation.bash index 4382ce9..8f0defb 100644 --- a/bash/parameters_scan_validation.bash +++ b/bash/parameters_scan_validation.bash @@ -18,8 +18,13 @@ function Format_Scan_Parameters_Lists() done } -function Validate_Scan_Parameters() +# NOTE: In the following functions the parameter(s) to be scanned are stored as a period-separated +# list of keys to navigate the YAML tree. However, the utility functions are general and needs +# keys as separate arguments. Hence we let word-splitting help us when calling the function. +# By that it is also assumed that there are no spaces in keys (a kind of design decision). +function Validate_And_Store_Scan_Parameters() { + Ensure_That_Given_Variables_Are_Set list_of_parameters_values local key parameters parameter counter=0 for key in "${!HYBRID_scan_parameters[@]}"; do if [[ "${HYBRID_scan_parameters["${key}"]}" = '' ]]; then @@ -30,6 +35,15 @@ function Validate_Scan_Parameters() if ! __static__Is_Parameter_To_Be_Scanned \ "${parameter}" "${HYBRID_software_new_input_keys["${key}"]}"; then (( counter++ )) || true + else + parameter_value=$( + Read_From_YAML_String_Given_Key \ + "${HYBRID_software_new_input_keys["${key}"]}" ${parameter//./ } + ) + # Get rid of YAML top-level 'Scan' key + list_of_parameters_values["${key}.Software_keys.${parameter}"]=$( + yq '.Scan' <<< "${parameter_value}" + ) fi done fi @@ -44,9 +58,6 @@ function Validate_Scan_Parameters() function __static__Is_Parameter_To_Be_Scanned() { local -r key="$1" yaml_section="$2" - # Here the key is assumed to be a period-separated list of keys to navigate the YAML - # tree. However the utility functions needs separate arguments and we let word-splitting - # help us, by that also assuming that there are no spaces in keys (a kind of design decision). if ! Has_YAML_String_Given_Key "${yaml_section}" ${key//./ }; then Print_Error \ 'The ' --emph "${key}" ' parameter is asked to be scanned but its value' \ @@ -149,7 +160,7 @@ function __static__Has_Valid_Scan_Correct_Values() ;; *) Print_Internal_And_Exit \ - 'Unknown scan passed to values validation function' --emph "${FUNCNAME}" '.' + 'Unknown scan passed to ' --emph "${FUNCNAME}" ' function.' ;; esac } diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index 576855c..75a7d55 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -129,14 +129,23 @@ function Make_Test_Preliminary_Operations__parameters-scan-global-validation() function Unit_Test__parameters-scan-global-validation() { + declare -A list_of_parameters_values HYBRID_scan_parameters[IC]='[Modi.Collider.Sqrtsnn]' HYBRID_scan_parameters[Hydro]='[etaS]' HYBRID_software_new_input_keys[IC]=$'Modi:\n Collider:\n Sqrtsnn: {Scan: {Values: [4.3, 7.7]}}' HYBRID_software_new_input_keys[Hydro]='etaS: {Scan: {Values: [0.13, 0.15, 0.17]}}' - Call_Codebase_Function_In_Subshell Validate_Scan_Parameters + Call_Codebase_Function Validate_And_Store_Scan_Parameters if [[ $? -ne 0 ]]; then Print_Error 'Validation of scan parameters unexpectedly failed.' return 1 + elif ! Element_In_Array_Equals_To 'IC.Software_keys.Modi.Collider.Sqrtsnn' "${!list_of_parameters_values[@]}" ||\ + ! Element_In_Array_Equals_To 'Hydro.Software_keys.etaS' "${!list_of_parameters_values[@]}"; then + Print_Error 'Storing of scanning information unexpectedly failed (missing/wrong keys).' + return 1 + elif [[ "${list_of_parameters_values[IC.Software_keys.Modi.Collider.Sqrtsnn]}" != '{Values: [4.3, 7.7]}' ]] ||\ + [[ "${list_of_parameters_values[Hydro.Software_keys.etaS]}" != '{Values: [0.13, 0.15, 0.17]}' ]]; then + Print_Error 'Storing of scanning information unexpectedly failed (wrong values).' + return 1 fi HYBRID_scan_parameters=( [IC]='' From b00e013a399106f2b6c005a795113d2b2b45a87f Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 9 Feb 2024 12:06:20 +0100 Subject: [PATCH 382/549] Rename file with better name --- bash/{parameters_scan_validation.bash => scan_validation.bash} | 0 bash/source_codebase_files.bash | 2 +- tests/unit_tests_parameters_scan.bash | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename bash/{parameters_scan_validation.bash => scan_validation.bash} (100%) diff --git a/bash/parameters_scan_validation.bash b/bash/scan_validation.bash similarity index 100% rename from bash/parameters_scan_validation.bash rename to bash/scan_validation.bash diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 522fed3..6a562c6 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -31,9 +31,9 @@ function __static__Source_Codebase_Files() 'global_variables.bash' 'Hydro_functionality.bash' 'IC_functionality.bash' - 'parameters_scan_validation.bash' 'Sampler_functionality.bash' 'sanity_checks.bash' + 'scan_validation.bash' 'software_input_functionality.bash' 'system_requirements.bash' 'version.bash' diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index 75a7d55..a0cdee0 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -12,7 +12,7 @@ function Make_Test_Preliminary_Operations__parameters-scan-format-lists() local file_to_be_sourced list_of_files list_of_files=( 'global_variables.bash' - 'parameters_scan_validation.bash' + 'scan_validation.bash' ) for file_to_be_sourced in "${list_of_files[@]}"; do source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} From 3c1c3892d39fd50418d7e35127770fa5fb6219e2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 9 Feb 2024 13:33:09 +0100 Subject: [PATCH 383/549] Add functionality to extract list of scan parameters values --- bash/scan_values_operations.bash | 48 +++++++++++++++++++++++++++ bash/source_codebase_files.bash | 1 + tests/unit_tests_parameters_scan.bash | 27 +++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 bash/scan_values_operations.bash diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash new file mode 100644 index 0000000..8e47ef7 --- /dev/null +++ b/bash/scan_values_operations.bash @@ -0,0 +1,48 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# NOTE: Here it is assumed that the 'list_of_parameters_values' is containing the +# full period-separated list of YAML keys as array keys and the "scan object" +# as values. This function will replace the array values with plain lists of +# parameter values. +function Create_List_Of_Parameters_Values() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values + if false; then + # This is where the Latin Hypercube Sampling algorithm has to be called + # in order to call the Python script and use its output to fill the + # 'list_of_parameters_values' array, e.g. in a 'while read' loop. + # The if-clause above should be on a new boolean key to be given in the + # generic Hybrid_handler section. + : + else + local parameter + for parameter in "${!list_of_parameters_values[@]}"; do + __static__Generate_And_Store_Parameter_List_Of_Values "${parameter}" + done + fi +} + +function __static__Generate_And_Store_Parameter_List_Of_Values() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values + local -r parameter=$1 scan_map="${list_of_parameters_values["$1"]}" + local -r sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" + case "${sorted_scan_keys}" in + "[Values]" ) + list_of_parameters_values["${parameter}"]=$( + yq '.Values | .. style="flow"' <<< "${scan_map}" + ) + ;; + *) + Print_Internal_And_Exit \ + 'Unknown scan in ' --emph "${FUNCNAME}" ' function.' + ;; + esac +} diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 6a562c6..ba34f3f 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -34,6 +34,7 @@ function __static__Source_Codebase_Files() 'Sampler_functionality.bash' 'sanity_checks.bash' 'scan_validation.bash' + 'scan_values_operations.bash' 'software_input_functionality.bash' 'system_requirements.bash' 'version.bash' diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_parameters_scan.bash index a0cdee0..9d6543b 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_parameters_scan.bash @@ -165,3 +165,30 @@ function Unit_Test__parameters-scan-global-validation() return 1 fi } + +function Make_Test_Preliminary_Operations__parameters-scan-create-list() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'global_variables.bash' + 'scan_values_operations.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables +} + +function Unit_Test__parameters-scan-create-list() +{ + declare -A list_of_parameters_values=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Values: [4.3, 7.7]}' + ['Hydro.Software_keys.etaS']='{Values: [0.13, 0.15, 0.17]}' + ) + Call_Codebase_Function Create_List_Of_Parameters_Values + if [[ "${list_of_parameters_values[IC.Software_keys.Modi.Collider.Sqrtsnn]}" != '[4.3, 7.7]' ]] ||\ + [[ "${list_of_parameters_values[Hydro.Software_keys.etaS]}" != '[0.13, 0.15, 0.17]' ]]; then + Print_Error 'Parameters values list was not correctly created.' + return 1 + fi +} From 7398f4335acb9c91e6cef27fc59697ccdf880f91 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 9 Feb 2024 13:51:39 +0100 Subject: [PATCH 384/549] Split unit tests of parameters scan in multiple files --- ...n.bash => unit_tests_scan_validation.bash} | 27 -------------- tests/unit_tests_scan_values_operations.bash | 35 +++++++++++++++++++ 2 files changed, 35 insertions(+), 27 deletions(-) rename tests/{unit_tests_parameters_scan.bash => unit_tests_scan_validation.bash} (85%) create mode 100644 tests/unit_tests_scan_values_operations.bash diff --git a/tests/unit_tests_parameters_scan.bash b/tests/unit_tests_scan_validation.bash similarity index 85% rename from tests/unit_tests_parameters_scan.bash rename to tests/unit_tests_scan_validation.bash index 9d6543b..a0cdee0 100644 --- a/tests/unit_tests_parameters_scan.bash +++ b/tests/unit_tests_scan_validation.bash @@ -165,30 +165,3 @@ function Unit_Test__parameters-scan-global-validation() return 1 fi } - -function Make_Test_Preliminary_Operations__parameters-scan-create-list() -{ - local file_to_be_sourced list_of_files - list_of_files=( - 'global_variables.bash' - 'scan_values_operations.bash' - ) - for file_to_be_sourced in "${list_of_files[@]}"; do - source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} - done - Define_Further_Global_Variables -} - -function Unit_Test__parameters-scan-create-list() -{ - declare -A list_of_parameters_values=( - ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Values: [4.3, 7.7]}' - ['Hydro.Software_keys.etaS']='{Values: [0.13, 0.15, 0.17]}' - ) - Call_Codebase_Function Create_List_Of_Parameters_Values - if [[ "${list_of_parameters_values[IC.Software_keys.Modi.Collider.Sqrtsnn]}" != '[4.3, 7.7]' ]] ||\ - [[ "${list_of_parameters_values[Hydro.Software_keys.etaS]}" != '[0.13, 0.15, 0.17]' ]]; then - Print_Error 'Parameters values list was not correctly created.' - return 1 - fi -} diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash new file mode 100644 index 0000000..f22550b --- /dev/null +++ b/tests/unit_tests_scan_values_operations.bash @@ -0,0 +1,35 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Test_Preliminary_Operations__parameters-scan-create-list() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'global_variables.bash' + 'scan_values_operations.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables +} + +function Unit_Test__parameters-scan-create-list() +{ + declare -A list_of_parameters_values=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Values: [4.3, 7.7]}' + ['Hydro.Software_keys.etaS']='{Values: [0.13, 0.15, 0.17]}' + ) + Call_Codebase_Function Create_List_Of_Parameters_Values + if [[ "${list_of_parameters_values[IC.Software_keys.Modi.Collider.Sqrtsnn]}" != '[4.3, 7.7]' ]] ||\ + [[ "${list_of_parameters_values[Hydro.Software_keys.etaS]}" != '[0.13, 0.15, 0.17]' ]]; then + Print_Error 'Parameters values list was not correctly created.' + return 1 + fi +} From 8ab77c433a00a203bcaf9f9e70d47fd5fea81a26 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 9 Feb 2024 13:54:13 +0100 Subject: [PATCH 385/549] Fix code formatting --- bash/scan_validation.bash | 14 ++++++-------- bash/scan_values_operations.bash | 2 +- tests/unit_tests_scan_validation.bash | 10 +++++----- tests/unit_tests_scan_values_operations.bash | 4 ++-- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index 8f0defb..e3083e6 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -34,7 +34,7 @@ function Validate_And_Store_Scan_Parameters() for parameter in "${parameters[@]}"; do if ! __static__Is_Parameter_To_Be_Scanned \ "${parameter}" "${HYBRID_software_new_input_keys["${key}"]}"; then - (( counter++ )) || true + ((counter++)) || true else parameter_value=$( Read_From_YAML_String_Given_Key \ @@ -50,7 +50,7 @@ function Validate_And_Store_Scan_Parameters() done if [[ ${counter} -ne 0 ]]; then exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ - '\nThe hybrid handler configuration file contains '\ + '\nThe hybrid handler configuration file contains ' \ --emph "${counter}" ' invalid scan specifications.' fi } @@ -67,8 +67,7 @@ function __static__Is_Parameter_To_Be_Scanned() local parameter_value parameter_value=$(Read_From_YAML_String_Given_Key "${yaml_section}" ${key//./ }) if ! __static__Is_Given_Key_Value_A_Valid_Scan "${parameter_value}"; then - Print_Error -l --\ - 'The ' --emph "${key}" \ + Print_Error -l -- 'The ' --emph "${key}" \ ' parameter is asked to be scanned but its value was not properly specified as a scan.' return 1 fi @@ -128,8 +127,7 @@ function __static__Check_If_Keys_Of_Given_Scan_Have_Correct_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys if ! __static__Has_Valid_Scan_Correct_Values; then - Print_Error -l --\ - 'The given scan\n' --emph "${given_scan}" '\nis allowed but its specification is invalid.' + Print_Error -l -- 'The given scan\n' --emph "${given_scan}" '\nis allowed but its specification is invalid.' return 1 fi } @@ -138,7 +136,7 @@ function __static__Has_Valid_Scan_Correct_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys case "${sorted_scan_keys}" in - "[Values]" ) + "[Values]") if [[ $(yq '.Scan.Values | type' <<< "${given_scan}") != '!!seq' ]]; then Print_Error \ 'The value ' --emph "$(yq '.Scan.Values' <<< "${given_scan}")" \ @@ -146,7 +144,7 @@ function __static__Has_Valid_Scan_Correct_Values() return 1 fi local list_of_value_types - list_of_value_types=( $(yq '.Scan.Values[] | type' <<< "${given_scan}" | sort -u) ) + list_of_value_types=($(yq '.Scan.Values[] | type' <<< "${given_scan}" | sort -u)) if [[ ${#list_of_value_types[@]} -ne 1 ]]; then Print_Error \ 'The parameter values have different YAML types: ' --emph "${list_of_value_types[*]//!!/}" '.' diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index 8e47ef7..c5d8854 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -35,7 +35,7 @@ function __static__Generate_And_Store_Parameter_List_Of_Values() local -r parameter=$1 scan_map="${list_of_parameters_values["$1"]}" local -r sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" case "${sorted_scan_keys}" in - "[Values]" ) + "[Values]") list_of_parameters_values["${parameter}"]=$( yq '.Values | .. style="flow"' <<< "${scan_map}" ) diff --git a/tests/unit_tests_scan_validation.bash b/tests/unit_tests_scan_validation.bash index a0cdee0..9202bc0 100644 --- a/tests/unit_tests_scan_validation.bash +++ b/tests/unit_tests_scan_validation.bash @@ -65,7 +65,7 @@ function Unit_Test__parameters-scan-YAML-scan-syntax() '{Scan: {Values: [42, 3.14]}}' '{Scan: {Values: ["Hi", "Bye"]}}' ) - for value in "${values[@]}" ; do + for value in "${values[@]}"; do Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Scan syntax validation for\n' --emph "${value}" '\nunexpectedly succeeded.' @@ -138,12 +138,12 @@ function Unit_Test__parameters-scan-global-validation() if [[ $? -ne 0 ]]; then Print_Error 'Validation of scan parameters unexpectedly failed.' return 1 - elif ! Element_In_Array_Equals_To 'IC.Software_keys.Modi.Collider.Sqrtsnn' "${!list_of_parameters_values[@]}" ||\ - ! Element_In_Array_Equals_To 'Hydro.Software_keys.etaS' "${!list_of_parameters_values[@]}"; then + elif ! Element_In_Array_Equals_To 'IC.Software_keys.Modi.Collider.Sqrtsnn' "${!list_of_parameters_values[@]}" \ + || ! Element_In_Array_Equals_To 'Hydro.Software_keys.etaS' "${!list_of_parameters_values[@]}"; then Print_Error 'Storing of scanning information unexpectedly failed (missing/wrong keys).' return 1 - elif [[ "${list_of_parameters_values[IC.Software_keys.Modi.Collider.Sqrtsnn]}" != '{Values: [4.3, 7.7]}' ]] ||\ - [[ "${list_of_parameters_values[Hydro.Software_keys.etaS]}" != '{Values: [0.13, 0.15, 0.17]}' ]]; then + elif [[ "${list_of_parameters_values[IC.Software_keys.Modi.Collider.Sqrtsnn]}" != '{Values: [4.3, 7.7]}' ]] \ + || [[ "${list_of_parameters_values[Hydro.Software_keys.etaS]}" != '{Values: [0.13, 0.15, 0.17]}' ]]; then Print_Error 'Storing of scanning information unexpectedly failed (wrong values).' return 1 fi diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash index f22550b..d4ce896 100644 --- a/tests/unit_tests_scan_values_operations.bash +++ b/tests/unit_tests_scan_values_operations.bash @@ -27,8 +27,8 @@ function Unit_Test__parameters-scan-create-list() ['Hydro.Software_keys.etaS']='{Values: [0.13, 0.15, 0.17]}' ) Call_Codebase_Function Create_List_Of_Parameters_Values - if [[ "${list_of_parameters_values[IC.Software_keys.Modi.Collider.Sqrtsnn]}" != '[4.3, 7.7]' ]] ||\ - [[ "${list_of_parameters_values[Hydro.Software_keys.etaS]}" != '[0.13, 0.15, 0.17]' ]]; then + if [[ "${list_of_parameters_values[IC.Software_keys.Modi.Collider.Sqrtsnn]}" != '[4.3, 7.7]' ]] \ + || [[ "${list_of_parameters_values[Hydro.Software_keys.etaS]}" != '[0.13, 0.15, 0.17]' ]]; then Print_Error 'Parameters values list was not correctly created.' return 1 fi From e83e9fd4d7f3923f18b68fe130667f08d9eeed59 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 9 Feb 2024 15:28:51 +0100 Subject: [PATCH 386/549] Shorten some unit tests names --- tests/unit_tests_scan_validation.bash | 22 ++++++++++---------- tests/unit_tests_scan_values_operations.bash | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/unit_tests_scan_validation.bash b/tests/unit_tests_scan_validation.bash index 9202bc0..206aed8 100644 --- a/tests/unit_tests_scan_validation.bash +++ b/tests/unit_tests_scan_validation.bash @@ -7,7 +7,7 @@ # #=================================================== -function Make_Test_Preliminary_Operations__parameters-scan-format-lists() +function Make_Test_Preliminary_Operations__scan-format-lists() { local file_to_be_sourced list_of_files list_of_files=( @@ -20,7 +20,7 @@ function Make_Test_Preliminary_Operations__parameters-scan-format-lists() Define_Further_Global_Variables } -function Unit_Test__parameters-scan-format-lists() +function Unit_Test__scan-format-lists() { HYBRID_scan_parameters=( [IC]=$'- General.Randomseed\n- General.End_Time' @@ -45,12 +45,12 @@ function Unit_Test__parameters-scan-format-lists() return ${counter} } -function Make_Test_Preliminary_Operations__parameters-scan-YAML-scan-syntax() +function Make_Test_Preliminary_Operations__scan-YAML-scan-syntax() { - Make_Test_Preliminary_Operations__parameters-scan-format-lists + Make_Test_Preliminary_Operations__scan-format-lists } -function Unit_Test__parameters-scan-YAML-scan-syntax() +function Unit_Test__scan-YAML-scan-syntax() { local value values values=( @@ -80,9 +80,9 @@ function Unit_Test__parameters-scan-YAML-scan-syntax() fi } -function Make_Test_Preliminary_Operations__parameters-scan-single-validation() +function Make_Test_Preliminary_Operations__scan-single-validation() { - Make_Test_Preliminary_Operations__parameters-scan-format-lists + Make_Test_Preliminary_Operations__scan-format-lists } function __static__Test_Validation_Of_Parameter() @@ -103,7 +103,7 @@ function __static__Test_Validation_Of_Parameter() fi } -function Unit_Test__parameters-scan-single-validation() +function Unit_Test__scan-single-validation() { HYBRID_software_new_input_keys=( [IC]=$'Modi:\n Collider:\n Sqrtsnn: {Scan: {Values: [4.3, 7.7]}}' @@ -122,12 +122,12 @@ function Unit_Test__parameters-scan-single-validation() "${HYBRID_software_new_input_keys[Afterburner]}" 'EXPECT_FAILURE' || return 1 } -function Make_Test_Preliminary_Operations__parameters-scan-global-validation() +function Make_Test_Preliminary_Operations__scan-global-validation() { - Make_Test_Preliminary_Operations__parameters-scan-format-lists + Make_Test_Preliminary_Operations__scan-format-lists } -function Unit_Test__parameters-scan-global-validation() +function Unit_Test__scan-global-validation() { declare -A list_of_parameters_values HYBRID_scan_parameters[IC]='[Modi.Collider.Sqrtsnn]' diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash index d4ce896..bcdd4c1 100644 --- a/tests/unit_tests_scan_values_operations.bash +++ b/tests/unit_tests_scan_values_operations.bash @@ -7,7 +7,7 @@ # #=================================================== -function Make_Test_Preliminary_Operations__parameters-scan-create-list() +function Make_Test_Preliminary_Operations__scan-create-list() { local file_to_be_sourced list_of_files list_of_files=( @@ -20,7 +20,7 @@ function Make_Test_Preliminary_Operations__parameters-scan-create-list() Define_Further_Global_Variables } -function Unit_Test__parameters-scan-create-list() +function Unit_Test__scan-create-list() { declare -A list_of_parameters_values=( ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Values: [4.3, 7.7]}' From ac254e23b26097ddfa18099d03bb808a14beec79 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 12:10:20 +0100 Subject: [PATCH 387/549] Add utility function to ensure not existent folders --- bash/utility_functions.bash | 9 +++++++++ tests/unit_tests_utility_functions.bash | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 3919073..8da677e 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -178,6 +178,11 @@ function Ensure_Given_Files_Exist() __static__Check_Given_Files_With '! -f' 'FATAL' "$@" } +function Ensure_Given_Folders_Do_Not_Exist() +{ + __static__Check_Given_Files_With '-d' 'FATAL' "$@" +} + function Ensure_Given_Folders_Exist() { __static__Check_Given_Files_With '! -d' 'FATAL' "$@" @@ -224,6 +229,10 @@ function __static__Check_Given_Files_With() negations=('NOT ' '') string+=' file' ;; + -d) + negations=('' 'NOT ') + string+=' folder' + ;; "! -d") negations=('NOT ' '') string+=' folder' diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 3d5ba6f..5342b90 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -319,5 +319,11 @@ function Unit_Test__utility-files-existence() Print_Error 'Function to ensure existent folders unexpectedly succeeded on a file.' return 1 fi + Call_Codebase_Function Ensure_Given_Folders_Do_Not_Exist 'not-existing' 'dfadgdsfs' + Call_Codebase_Function_In_Subshell Ensure_Given_Folders_Do_Not_Exist "${HOME}" &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Function to ensure not existent folders unexpectedly succeeded.' + return 1 + fi rm 'link_test' } From ad3ef4181af142ff5461e0bcdf3f28db8fbfbba5 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 13:32:01 +0100 Subject: [PATCH 388/549] Let each unit test create and work in a dedicated folder --- tests/unit_tests.bash | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests.bash b/tests/unit_tests.bash index ae86510..97feb08 100644 --- a/tests/unit_tests.bash +++ b/tests/unit_tests.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -16,11 +16,13 @@ function Make_Test_Preliminary_Operations() { local test_name=$1 { - # The following global variable is needed whe defining the software global variables + # The following global variable is needed when defining the software global variables # and since it is likely that most unit tests need it, let's always define it readonly HYBRID_top_level_path="${HYBRIDT_repository_top_level_path}" # Write header to the log file to give some structure to it printf "\n[$(date)]\nRunning unit test \"%s\"\n\n" "${test_name}" + mkdir "$1" || exit ${HYBRID_fatal_builtin} + cd "$1" || exit ${HYBRID_fatal_builtin} Call_Function_If_Existing_Or_No_Op ${FUNCNAME}__$1 } &>> "${HYBRIDT_log_file}" 9>&1 # The fd 9 is used by the logger } From 8614d581cf8a6b86bfa648b7d1aae71b8c5247a2 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 14:00:40 +0100 Subject: [PATCH 389/549] Implement functionality to create scan output config files --- bash/global_variables.bash | 1 + bash/scan_files_operations.bash | 87 +++++++++++++++++++++ tests/unit_tests_scan_files_operations.bash | 68 ++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 bash/scan_files_operations.bash create mode 100644 tests/unit_tests_scan_files_operations.bash diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 2e5ccf7..22ef451 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -106,6 +106,7 @@ function Define_Further_Global_Variables() HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' HYBRID_output_directory="$(realpath './data')" + HYBRID_scan_directory="$(realpath ./scan)" # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_run_id="Run_$(date +'%Y-%m-%d_%H%M%S')" HYBRID_given_software_sections=() diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash new file mode 100644 index 0000000..61c00b5 --- /dev/null +++ b/bash/scan_files_operations.bash @@ -0,0 +1,87 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Create_And_Populate_Scan_Folder() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values + local -r parameters=( "${!list_of_parameters_values[@]}" ) + local parameters_combinations + readarray -t parameters_combinations < \ + <(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${list_of_parameters_values[@]}") + readonly parameters_combinations + __static__Validate_And_Create_Scan_Folder + __static__Create_Output_Files_In_Scan_Folder +} + +function __static__Get_Parameters_Combinations_For_New_Configuration_Files() +{ + # NOTE: This is were multiple ways of doing combinations will be implemented: + # For example, cartesian product VS all first values, all second ones, etc. + # At the moment only the cartesian product approach is implemented, i.e. + # all possible combinations of parameters values are considered. + __static__Get_All_Parameters_Combinations "$@" +} + +function __static__Get_All_Parameters_Combinations() +{ + local values string_to_be_expanded + for values in "$@"; do + values=$(sed -e 's/ //g' -e 's/[[]/\{/' -e 's/[]]/\}/' <<< "${values}") + string_to_be_expanded+="${values}_" + done + # NOTE: The following use of 'eval' is fine since the string that is expanded + # is guaranteed to be validated to contain only YAML int, bool or float. + eval printf '%s\\\n' "${string_to_be_expanded%?}" | sed 's/_/ /g' +} + +function __static__Validate_And_Create_Scan_Folder() +{ + Ensure_Given_Folders_Do_Not_Exist \ + 'The scan output folder is meant to be freshly created by the handler.' '--' \ + "${HYBRID_scan_directory}" + mkdir -p "${HYBRID_scan_directory}" +} + +function __static__Create_Output_Files_In_Scan_Folder() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty \ + list_of_parameters_values parameters parameters_combinations + local id filename + for id in "${!parameters_combinations[@]}"; do + filename="$(__static__Get_Output_Filename "${id}")" + Print_Info "${filename}" "${parameters_combinations[id]}" + # Let word splitting split values in each parameters combination + __static__Create_Single_Output_File_In_Scan_Folder ${parameters_combinations[id]} + done +} + +function __static__Create_Single_Output_File_In_Scan_Folder() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters filename + local -r set_of_values=( "$@" ) + local index yq_replacements + for ((index=0; index<${#parameters[@]}; index++)); do + yq_replacements+="( .${parameters[index]} ) = ${set_of_values[index]} |" + done + yq "${yq_replacements%?}" "${HYBRID_configuration_file}" > "${filename}" +} + +function __static__Get_Output_Filename() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters_combinations + # Here the filename has to contain the run number prefixed with the correct + # amount of leading zeroes in order to make sorting easier for the user. + local -r \ + number_of_combinations=${#parameters_combinations[@]} \ + run_number=$(($1+1)) + printf '%s_%0*d.yaml' \ + "${HYBRID_scan_directory}/${HYBRID_scan_directory}" \ + "${#number_of_combinations}" \ + "${run_number}" +} diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash new file mode 100644 index 0000000..c5c49b7 --- /dev/null +++ b/tests/unit_tests_scan_files_operations.bash @@ -0,0 +1,68 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Make_Test_Preliminary_Operations__scan-create-single-file() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'global_variables.bash' + 'scan_files_operations.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables +} + +function Unit_Test__scan-create-single-file() +{ + declare -A list_of_parameters_values=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='[4.3, 7.7]' + ['Hydro.Software_keys.etaS']='[0.13, 0.15, 0.17]' + ) + printf ' + IC: + Software_keys: + Modi: + Collider: + Sqrtsnn: {Scan: {Values: [4.3, 7.7]}} + Hydro: + Software_keys: + etaS: {Scan: {Values: [0.13, 0.15, 0.17]}} + ' > 'config.yaml' + HYBRID_scan_directory='scan_test' + Call_Codebase_Function Create_And_Populate_Scan_Folder + cd "${HYBRID_scan_directory}" + shopt -s nullglob + local -r list_of_files=(*) + if [[ ${#list_of_files[@]} -ne 6 ]]; then + Print_Error 'Expected ' --emph '6' ' files to be created, but ' --emph "${#list_of_files[@]}" ' found.' + return 1 + fi + local file values sqrt_snn eta_s + set -- "4.3 0.13" "4.3 0.15" "4.3 0.17" "7.7 0.13" "7.7 0.15" "7.7 0.17" + for file in "${list_of_files[@]}"; do + if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_[1-6]\.yaml$ ]]; then + Print_Error 'Filename ' --emph "${file}" ' not matching expected name.' + return 1 + fi + values=( $1 ) # Use word splitting to split values + shift + sqrt_snn=$(Read_From_YAML_String_Given_Key "$(< "${file}")" 'IC' 'Software_keys' 'Modi' 'Collider' 'Sqrtsnn') + if [[ "${sqrt_snn}" != "${values[0]}" ]]; then + Print_Error 'Value of ' --emph 'Sqrtsnn' ' wrongly set in output file.' + return 1 + fi + eta_s=$(Read_From_YAML_String_Given_Key "$(< "${file}")" 'Hydro' 'Software_keys' 'etaS') + if [[ "${eta_s}" != "${values[1]}" ]]; then + Print_Error 'Value of ' --emph 'etaS' ' wrongly set in output file.' + return 1 + fi + done +} From 37c8e86163d3d46e150f38c61feaf6e7030216b4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 15:29:53 +0100 Subject: [PATCH 390/549] Implement scan combinations file functionality For each run, the scan parameters are also added to the configuration file for reproducibility reasons. --- bash/global_variables.bash | 1 + bash/scan_files_operations.bash | 84 +++++++++++++++++---- bash/utility_functions.bash | 5 ++ tests/unit_tests_scan_files_operations.bash | 23 +++++- 4 files changed, 95 insertions(+), 18 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 22ef451..497d42f 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -63,6 +63,7 @@ function Define_Further_Global_Variables() readonly HYBRID_valid_scan_specification_keys=( '[Values]' ) + readonly HYBRID_scan_combinations_filename='scan_combinations.dat' # The following associative arrays declare maps between valid keys in the handler config # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=( diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 61c00b5..5fccaa2 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -10,13 +10,16 @@ function Create_And_Populate_Scan_Folder() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values - local -r parameters=( "${!list_of_parameters_values[@]}" ) + local -r \ + parameters=( "${!list_of_parameters_values[@]}" ) \ + scan_combinations_file="${HYBRID_scan_directory}/${HYBRID_scan_combinations_filename}" local parameters_combinations readarray -t parameters_combinations < \ <(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${list_of_parameters_values[@]}") readonly parameters_combinations __static__Validate_And_Create_Scan_Folder - __static__Create_Output_Files_In_Scan_Folder + __static__Create_Combinations_File_With_Metadata_Header_Block + __static__Create_Output_Files_In_Scan_Folder_And_Complete_Combinations_File } function __static__Get_Parameters_Combinations_For_New_Configuration_Files() @@ -48,28 +51,34 @@ function __static__Validate_And_Create_Scan_Folder() mkdir -p "${HYBRID_scan_directory}" } -function __static__Create_Output_Files_In_Scan_Folder() +function __static__Create_Combinations_File_With_Metadata_Header_Block() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters scan_combinations_file + Internally_Ensure_Given_Files_Do_Not_Exist "${scan_combinations_file}" + local index + { + for index in "${!parameters[@]}"; do + printf '# Parameter_%d: %s\n' $((index+1)) "${parameters[index]}" + done + printf '#\n#___Run' + for index in "${!parameters[@]}"; do + printf ' Parameter_%d' $((index+1)) + done + printf '\n' + } > "${scan_combinations_file}" +} + +function __static__Create_Output_Files_In_Scan_Folder_And_Complete_Combinations_File() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty \ list_of_parameters_values parameters parameters_combinations local id filename for id in "${!parameters_combinations[@]}"; do filename="$(__static__Get_Output_Filename "${id}")" - Print_Info "${filename}" "${parameters_combinations[id]}" # Let word splitting split values in each parameters combination - __static__Create_Single_Output_File_In_Scan_Folder ${parameters_combinations[id]} - done -} - -function __static__Create_Single_Output_File_In_Scan_Folder() -{ - Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters filename - local -r set_of_values=( "$@" ) - local index yq_replacements - for ((index=0; index<${#parameters[@]}; index++)); do - yq_replacements+="( .${parameters[index]} ) = ${set_of_values[index]} |" + __static__Add_Line_To_Combinations_File "${id}" ${parameters_combinations[id]} + __static__Create_Single_Output_File_In_Scan_Folder "${id}" ${parameters_combinations[id]} done - yq "${yq_replacements%?}" "${HYBRID_configuration_file}" > "${filename}" } function __static__Get_Output_Filename() @@ -85,3 +94,46 @@ function __static__Get_Output_Filename() "${#number_of_combinations}" \ "${run_number}" } + +function __static__Add_Line_To_Combinations_File() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty scan_combinations_file + # These fields lengths are hard-coded for the moment and are meant to + # properly align the column content to the header line description + { + printf '%7d' $(($1+1)) + shift + printf ' %11s' "$@" + printf '\n' + } >> "${scan_combinations_file}" +} + +function __static__Create_Single_Output_File_In_Scan_Folder() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters filename + local -r run_number=$(($1+1)) + shift + local -r set_of_values=( "$@" ) + Internally_Ensure_Given_Files_Do_Not_Exist "${filename}" + __static__Add_Parameters_Comment_Line_To_New_Configuration_File + local index yq_replacements + for ((index=0; index<${#parameters[@]}; index++)); do + yq_replacements+="( .${parameters[index]} ) = ${set_of_values[index]} |" + done + yq "${yq_replacements%?}" "${HYBRID_configuration_file}" >> "${filename}" +} + +function __static__Add_Parameters_Comment_Line_To_New_Configuration_File() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty \ + parameters filename run_number set_of_values + local -r longest_parameter_length=$(wc -L < <(printf '%s\n' "${parameters[@]}")) + { + printf '# Run %d of parameter scan "%s"\n#\n' "${run_number}" "${HYBRID_scan_directory}" + local index + for index in "${!parameters[@]}"; do + printf '# %*s: %s\n' "${longest_parameter_length}" "${parameters[index]}" "${set_of_values[index]}" + done + printf '\n' + } > "${filename}" +} diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 8da677e..adc39f6 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -188,6 +188,11 @@ function Ensure_Given_Folders_Exist() __static__Check_Given_Files_With '! -d' 'FATAL' "$@" } +function Internally_Ensure_Given_Files_Do_Not_Exist() +{ + __static__Check_Given_Files_With '-f' 'INTERNAL' "$@" +} + function Internally_Ensure_Given_Files_Exist() { __static__Check_Given_Files_With '! -f' 'INTERNAL' "$@" diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index c5c49b7..c381be3 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -36,18 +36,37 @@ function Unit_Test__scan-create-single-file() Software_keys: etaS: {Scan: {Values: [0.13, 0.15, 0.17]}} ' > 'config.yaml' + cat > ref_scan_combinations.dat < /dev/null; then + Print_Error 'Scan combinations file expected different.' + return 1 + fi + continue + fi if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_[1-6]\.yaml$ ]]; then Print_Error 'Filename ' --emph "${file}" ' not matching expected name.' return 1 From 1213f52eab46f5ef5c278867a3f55132d7d9cdc0 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 18:17:40 +0100 Subject: [PATCH 391/549] Make scan cofiguration names more specific --- bash/scan_files_operations.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 5fccaa2..0f94916 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -89,8 +89,8 @@ function __static__Get_Output_Filename() local -r \ number_of_combinations=${#parameters_combinations[@]} \ run_number=$(($1+1)) - printf '%s_%0*d.yaml' \ - "${HYBRID_scan_directory}/${HYBRID_scan_directory}" \ + printf '%s_run_%0*d.yaml' \ + "${HYBRID_scan_directory}/$(basename "${HYBRID_scan_directory}")" \ "${#number_of_combinations}" \ "${run_number}" } From 604b38e9527efc25d94b1d8b2cb9cd6e847283fd Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 18:22:44 +0100 Subject: [PATCH 392/549] Test missed corner case in validating scan configuration --- bash/scan_validation.bash | 7 +++++++ tests/unit_tests_scan_validation.bash | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index e3083e6..b6c494a 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -25,6 +25,9 @@ function Format_Scan_Parameters_Lists() function Validate_And_Store_Scan_Parameters() { Ensure_That_Given_Variables_Are_Set list_of_parameters_values + # Manually set the array in order to check at the end if it stayed empty. + # NOTE: In 'set -u' mode this is needed, otherwise accessing the size would fail for empty arrays. + list_of_parameters_values=() local key parameters parameter counter=0 for key in "${!HYBRID_scan_parameters[@]}"; do if [[ "${HYBRID_scan_parameters["${key}"]}" = '' ]]; then @@ -52,6 +55,10 @@ function Validate_And_Store_Scan_Parameters() exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ '\nThe hybrid handler configuration file contains ' \ --emph "${counter}" ' invalid scan specifications.' + elif [[ ${#list_of_parameters_values[@]} -eq 0 ]]; then + exit_code=${HYBRID_fatal_wrong_config_file} Print_Fatal_And_Exit \ + 'The hybrid handler configuration file does not properly specify scan parameters.' \ + 'Make sure to specify the ' --emph 'Scan_parameters' ' key in the appropriate sections.' fi } diff --git a/tests/unit_tests_scan_validation.bash b/tests/unit_tests_scan_validation.bash index 206aed8..1f57569 100644 --- a/tests/unit_tests_scan_validation.bash +++ b/tests/unit_tests_scan_validation.bash @@ -159,7 +159,13 @@ function Unit_Test__scan-global-validation() [Sampler]='shear: 1.' [Afterburner]='' ) - Call_Codebase_Function_In_Subshell Validate_Scan_Parameters &> /dev/null + Call_Codebase_Function_In_Subshell Validate_And_Store_Scan_Parameters &> /dev/null + if [[ $? -eq 0 ]]; then + Print_Error 'Validation of scan parameters unexpectedly succeeded.' + return 1 + fi + HYBRID_scan_parameters=() + Call_Codebase_Function_In_Subshell Validate_And_Store_Scan_Parameters &> /dev/null if [[ $? -eq 0 ]]; then Print_Error 'Validation of scan parameters unexpectedly succeeded.' return 1 From 16139dbc14c62be314b582118abb60f909578a3a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 18:26:22 +0100 Subject: [PATCH 393/549] Implement in-terminal help for new prepare-scan mode This has been taken as opportunity to extract in a separate file the declaration of allowed command line options, which will enable a simple reuse for future autocompletion implementation. --- .../command_line_parsers/allowed_options.bash | 31 ++++++++++ bash/command_line_parsers/helper.bash | 59 +++++++++++++++---- bash/source_codebase_files.bash | 1 + 3 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 bash/command_line_parsers/allowed_options.bash diff --git a/bash/command_line_parsers/allowed_options.bash b/bash/command_line_parsers/allowed_options.bash new file mode 100644 index 0000000..a580614 --- /dev/null +++ b/bash/command_line_parsers/allowed_options.bash @@ -0,0 +1,31 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +# These variable declarations are gathered in a function in order to allow for +# reuse when implementing in-terminal manuals and/or autocompletion. +# +# NOTE: Since this file is sourced by the autocompletion code in the user +# environment, it is important to keep to the minimum any pollution there. +# Therefore this function has a prefix and it starts by '_'. Furthermore +# this function is not marked as readonly to avoid errors in case the user +# sources this file multiple times e.g. through their ~/.bashrc file. +function _HYBRID_Declare_Allowed_Command_Line_Options() +{ + # This associative array is meant to map execution modes to allowed options + # in such a mode. A single associative array instead of many different arrays + # makes it cleaner for the user environment. However, it is then needed to + # assume that no option contains spaces and use word splitting from the caller. + # This is a very reasonable assumption, though. + declare -rgA HYBRID_allowed_command_line_options=( + ['help']='' + ['version']='' + ['do']='--output-directory --configuration-file' + ['prepare-scan']='--output-directory --configuration-file --scan-name' + ) +} diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index bff4f75..15d03b2 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -13,8 +13,9 @@ function Give_Required_Help() help) __static__Print_Main_Help_Message ;; - do-help) - __static__Print_Do_Help_Message + do-help | prepare-scan-help) + _HYBRID_Declare_Allowed_Command_Line_Options + __static__Print_Help_Message_For_Given_Mode "${HYBRID_execution_mode%%-help}" ;; *) Print_Internal_And_Exit \ @@ -40,22 +41,23 @@ function __static__Print_Main_Help_Message() ) execution_modes_description=( [do]='Do everything is necessary to run the workflow given in the configuration file' + [prepare-scan]='Prepare configurations files for the handler scanning in the given parameters' ) __static__Print_Handler_Header_And_Usage_Synopsis __static__Print_Modes_Description Check_System_Requirements_And_Make_Report } -function __static__Print_Do_Help_Message() +function __static__Print_Help_Message_For_Given_Mode() { - printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n' \ - 'You can specify the following command line options to the' 'do' 'execution mode:' - __static__Print_Command_Line_Option_Help \ - '-o | --output-directory' './data' \ - "Directory where the run folder(s) will be created." - __static__Print_Command_Line_Option_Help \ - '-c | --configuration-file' "${HYBRID_configuration_file}" \ - "YAML configuration file to be used by the handler." + Ensure_That_Given_Variables_Are_Set_And_Not_Empty HYBRID_allowed_command_line_options + local -r mode="$1" + __static__Print_Help_Header_For_Given_Mode_Help "${mode}" + local option + # Let word splitting split allowed command line options + for option in ${HYBRID_allowed_command_line_options["${mode}"]}; do + __static__Print_Given_Command_Line_Option_Help "${option}" + done } function __static__Print_Handler_Header_And_Usage_Synopsis() @@ -108,6 +110,41 @@ function __static__Print_Modes_Description() 'Use' '--help' 'after each non auxiliary mode to get further information about it.' } +function __static__Print_Help_Header_For_Given_Mode_Help() +{ + printf '\e[38;5;38m %s \e[38;5;85m%s \e[38;5;38m%s\e[0m\n' \ + 'You can specify the following command line options to the' "$1" 'execution mode:' +} + +function __static__Print_Given_Command_Line_Option_Help() +{ + case "$1" in + --output-directory) + __static__Print_Command_Line_Option_Help \ + '-o | --output-directory' "${HYBRID_output_directory/${PWD}/.}" \ + "Directory where the output folder(s) will be created." + ;; + --configuration-file) + __static__Print_Command_Line_Option_Help \ + '-c | --configuration-file' "${HYBRID_configuration_file}" \ + "YAML configuration file to be used by the handler." + ;; + --scan-name) + __static__Print_Command_Line_Option_Help \ + '-s | --scan-name' "$(realpath --relative-to=. "${HYBRID_scan_directory}")" \ + "Label of the scan used by the handler to produce output." \ + "The new configuration files will be put in a sub-folder" \ + "of the output directory named using the specified name" \ + "and the configuration files themselves will contain the"\ + "scan name as part of their name." + ;; + *) + Print_Internal_And_Exit \ + 'Unknown option ' --emph "$1" ' passed to ' --emph "${FUNCNAME}" ' function.' + ;; + esac +} + function __static__Print_Command_Line_Option_Help() { local -r \ diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index ba34f3f..2846f84 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -20,6 +20,7 @@ function __static__Source_Codebase_Files() source "${HYBRID_top_level_path}/bash/utility_functions.bash" || exit ${HYBRID_fatal_builtin} list_of_files=( 'Afterburner_functionality.bash' + 'command_line_parsers/allowed_options.bash' 'command_line_parsers/helper.bash' 'command_line_parsers/main_parser.bash' 'command_line_parsers/sub_parser.bash' From 779daf790a7aa575a0cf4503bd72986e172ee7f4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 18:28:17 +0100 Subject: [PATCH 394/549] Implement scan-prepare command line options parsing The new execution mode has options in common with the already existing do execution mode. The common options are parsed together in a common parser function, but a validation of options per mode has been added and performed before, such that it should not be possible to accept wrong options. A mechanism to internally replace short options with long ones and only deal with the latter has been introduced, too. --- bash/command_line_parsers/main_parser.bash | 58 +++++++++++++++++++--- bash/command_line_parsers/sub_parser.bash | 25 +++++++++- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 173f18f..31f1083 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -12,7 +12,8 @@ function Parse_Execution_Mode() if [[ "${#HYBRID_command_line_options_to_parse[@]}" -eq 0 ]]; then return 0 fi - #Locally set function arguments to take advantage of shift + __static__Replace_Short_Options_With_Long_Ones + # Locally set function arguments to take advantage of shift set -- "${HYBRID_command_line_options_to_parse[@]}" case "$1" in help | --help) @@ -27,6 +28,9 @@ function Parse_Execution_Mode() do) HYBRID_execution_mode='do' ;; + prepare-scan) + HYBRID_execution_mode='prepare-scan' + ;; *) exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ 'Specified mode ' --emph "$1" ' not valid! Run ' \ @@ -45,15 +49,20 @@ function Parse_Execution_Mode() fi } +# NOTE: The strategy of the following function is to +# 1) validate if command-line options are allowed in given mode; +# 2) parse mode-specific options invoking sub-parser; +# 3) parse remaining options (which are in common to two or more modes) all together. +# Since options are validated, step number 3 simplifies the implementation +# without the risk of accepting invalid options. function Parse_Command_Line_Options() { - if [[ ${HYBRID_execution_mode} != 'do' ]]; then - Print_Internal_And_Exit 'Command line options are allowed only in ' --emph 'do' ' mode for now.' - fi + __static__Validate_Command_Line_Options + Call_Function_If_Existing_Or_No_Op Parse_Specific_Mode_Options_${HYBRID_execution_mode} set -- "${HYBRID_command_line_options_to_parse[@]}" while [[ $# -gt 0 ]]; do case "$1" in - -o | --output-directory) + --output-directory) if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else @@ -65,7 +74,7 @@ function Parse_Command_Line_Options() fi shift 2 ;; - -c | --configuration-file) + --configuration-file) if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else @@ -82,4 +91,41 @@ function Parse_Command_Line_Options() done } +function __static__Replace_Short_Options_With_Long_Ones() +{ + declare -A options_map=( + ['-c']='--configuration-file' + ['-h']='--help' + ['-o']='--output-directory' + ['-s']='--scan-name' + ) + set -- "${HYBRID_command_line_options_to_parse[@]}" + HYBRID_command_line_options_to_parse=() + local option + for option in "$@"; do + if Element_In_Array_Equals_To "${option}" "${!options_map[@]}"; then + option=${options_map["${option}"]} + fi + HYBRID_command_line_options_to_parse+=( "${option}" ) + done +} + +function __static__Validate_Command_Line_Options() +{ + _HYBRID_Declare_Allowed_Command_Line_Options + # Let word splitting split valid options + local -r valid_options=( ${HYBRID_allowed_command_line_options["${HYBRID_execution_mode}"]} ) + local option + for option in "${HYBRID_command_line_options_to_parse[@]}"; do + if [[ ${option} =~ ^- ]]; then + if ! Element_In_Array_Equals_To "${option}" "${valid_options[@]}"; then + exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ + 'Option ' --emph "${option}" ' is not allowed in ' --emph "${HYBRID_execution_mode}" \ + ' execution mode!' 'Use the ' --emph '--help' ' option to get further information.' + fi + fi + done +} + + Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/command_line_parsers/sub_parser.bash b/bash/command_line_parsers/sub_parser.bash index 105d8d8..ffe238b 100644 --- a/bash/command_line_parsers/sub_parser.bash +++ b/bash/command_line_parsers/sub_parser.bash @@ -1,8 +1,31 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) # #=================================================== + +function Parse_Specific_Mode_Options_prepare-scan() +{ + set -- "${HYBRID_command_line_options_to_parse[@]}" + HYBRID_command_line_options_to_parse=() + while [[ $# -gt 0 ]]; do + case "$1" in + --scan-name ) + if [[ ${2-} =~ ^(-|$) ]]; then + Print_Option_Specification_Error_And_Exit "$1" + else + readonly HYBRID_scan_directory="${HYBRID_output_directory}/$2" + fi + shift 2 + ;; + * ) + HYBRID_command_line_options_to_parse+=( "$1" ) + shift + ;; + esac + done + +} From e064e4229dd051e0ac40387ea805af324c7795f6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 18:31:18 +0100 Subject: [PATCH 395/549] Make scan default folder a sub-folder of the output one --- bash/global_variables.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 497d42f..e87f534 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -107,7 +107,7 @@ function Define_Further_Global_Variables() HYBRID_execution_mode='help' HYBRID_configuration_file='./config.yaml' HYBRID_output_directory="$(realpath './data')" - HYBRID_scan_directory="$(realpath ./scan)" + HYBRID_scan_directory="${HYBRID_output_directory}/scan" # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_run_id="Run_$(date +'%Y-%m-%d_%H%M%S')" HYBRID_given_software_sections=() From 07e57fb3c6d0937ac75ebe0b644244b5cf7b1fa1 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 18:31:53 +0100 Subject: [PATCH 396/549] Implement new prepare-scan execution mode Now that all elements have been implemented, the new mode has been straightforwardly added. --- Hybrid-handler | 8 ++++++-- bash/execution_mode_do.bash | 1 + bash/execution_mode_prepare.bash | 26 ++++++++++++++++++++++++++ bash/source_codebase_files.bash | 2 ++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 bash/execution_mode_prepare.bash diff --git a/Hybrid-handler b/Hybrid-handler index b113055..3e3a5b2 100755 --- a/Hybrid-handler +++ b/Hybrid-handler @@ -110,13 +110,17 @@ function Act_And_Exit_If_User_Ran_Auxiliary_Modes() function Make_Needed_Operations_Depending_On_Execution_Mode() { case "${HYBRID_execution_mode}" in - do) - local software_section + do | prepare-scan) Perform_Internal_Sanity_Checks Validate_And_Parse_Configuration_File Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables + ;;& + do) Do_Needed_Operations_For_Given_Software ;; + prepare-scan) + Do_Needed_Operation_For_Parameter_Scan + ;; *) Print_Internal_And_Exit "Unexpected execution mode at top-level." ;; diff --git a/bash/execution_mode_do.bash b/bash/execution_mode_do.bash index 9ea3798..0800b8a 100644 --- a/bash/execution_mode_do.bash +++ b/bash/execution_mode_do.bash @@ -10,6 +10,7 @@ function Do_Needed_Operations_For_Given_Software() { printf '\e[1A' # Just aesthetics to 'skip' newline for first loop iteration + local software_section for software_section in "${HYBRID_given_software_sections[@]}"; do # Here abuse the logger to simply indent date Print_Info -l -- "\n$(printf '\e[0m%s' "$(date)")" diff --git a/bash/execution_mode_prepare.bash b/bash/execution_mode_prepare.bash new file mode 100644 index 0000000..f20f631 --- /dev/null +++ b/bash/execution_mode_prepare.bash @@ -0,0 +1,26 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Do_Needed_Operation_For_Parameter_Scan() +{ + # This array is going to contain a map between the parameter and its list + # of values. Since in general a scan run could request a scan in parameters + # of different stages, here the parameter name is stored as a period-separated + # list of keys as they appear in the Hybrid Handler configuration file, and + # precisely in the 'Software_keys' sections. For example a scan in Hydro 'etaS' + # would be stored here in the component with key 'Hydro.Software_keys.etaS'. + # This syntax is handy when it comes to prepare all the configuration files + # as it naturally interacts well with yq. The value is a YAML sequence of the + # parameter values. + declare -A list_of_parameters_values + Format_Scan_Parameters_Lists + Validate_And_Store_Scan_Parameters + Create_List_Of_Parameters_Values + Create_And_Populate_Scan_Folder +} diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 2846f84..81596ed 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -28,12 +28,14 @@ function __static__Source_Codebase_Files() 'configuration_parser.bash' 'dispatch_functions.bash' 'execution_mode_do.bash' + 'execution_mode_prepare.bash' 'formatter.bash' 'global_variables.bash' 'Hydro_functionality.bash' 'IC_functionality.bash' 'Sampler_functionality.bash' 'sanity_checks.bash' + 'scan_files_operations.bash' 'scan_validation.bash' 'scan_values_operations.bash' 'software_input_functionality.bash' From 3cb1092e43694d9a8db3efe2f339249e99532be7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 12 Feb 2024 19:25:49 +0100 Subject: [PATCH 397/549] Add information output and progress bar to prepare-scan mode --- bash/execution_mode_prepare.bash | 3 ++ bash/progress_bar.bash | 67 ++++++++++++++++++++++++++++++++ bash/scan_files_operations.bash | 3 ++ bash/source_codebase_files.bash | 1 + 4 files changed, 74 insertions(+) create mode 100644 bash/progress_bar.bash diff --git a/bash/execution_mode_prepare.bash b/bash/execution_mode_prepare.bash index f20f631..3255521 100644 --- a/bash/execution_mode_prepare.bash +++ b/bash/execution_mode_prepare.bash @@ -20,7 +20,10 @@ function Do_Needed_Operation_For_Parameter_Scan() # parameter values. declare -A list_of_parameters_values Format_Scan_Parameters_Lists + Print_Info 'Validating input scan parameters values' Validate_And_Store_Scan_Parameters + Print_Info 'Collecting parameters scan values' Create_List_Of_Parameters_Values + Print_Info 'Preparing scan configuration files:' Create_And_Populate_Scan_Folder } diff --git a/bash/progress_bar.bash b/bash/progress_bar.bash new file mode 100644 index 0000000..e7bf788 --- /dev/null +++ b/bash/progress_bar.bash @@ -0,0 +1,67 @@ +#=================================================== +# +# Copyright (c) 2024 +# SMASH Hybrid Team +# +# GNU General Public License (GPLv3 or later) +# +#=================================================== + +function Print_Progress_Bar +{ + __static__Validate_Progress_Bar_Input "$@" + Print_Info -l -- "\e[K$(__static__Get_Progress_Bar $1 $2)\r\e[1A" +} + +function Print_Final_Progress_Bar +{ + __static__Validate_Progress_Bar_Input "$@" + Print_Info -l -- "\e[K$(__static__Get_Progress_Bar $1 $2)" +} + +function __static__Validate_Progress_Bar_Input() +{ + if [[ $# -lt 2 ]] || [[ ! $1 =~ ^[0-9]+(.[0-9]+)*$ ]] || [[ ! $2 =~ ^[0-9]+(.[0-9]+)*$ ]]; then + Print_Internal_And_Exit --emph "${FUNCNAME[1]}" ' wrongly called (' --emph "$1 $2" ').' + fi +} + +function __static__Get_Progress_Bar() +{ + local -r \ + done=$1 \ + total=$2 \ + prefix="${3-}" \ + suffix="${4-}" \ + bar="━" \ + half_bar_right="╸" + local output="${prefix} " color + local -r percentage=$(awk '{printf "%.0f", 100*$1/$2}' <<< "${done} ${total}") + if [[ ${percentage} -lt 30 ]]; then + color='\e[91m' + elif [[ ${percentage} -lt 40 ]]; then + color='\e[38;5;202m' + elif [[ ${percentage} -lt 50 ]]; then + color='\e[38;5;208m' + elif [[ ${percentage} -lt 60 ]]; then + color='\e[38;5;221m' + elif [[ ${percentage} -lt 70 ]]; then + color='\e[38;5;191m' + elif [[ ${percentage} -lt 80 ]]; then + color='\e[38;5;148m' + elif [[ ${percentage} -lt 90 ]]; then + color='\e[38;5;118m' + else + color='\e[92m' + fi + output+=$(printf "${color}") + for ((i = 0; i < ${percentage}; i++)); do + output+="${bar}" + done + output+="${half_bar_right}" + for ((i = ${percentage}; i < 100; i++)); do + output+="\e[38;5;237m${bar}" + done + output+=$(printf '\e[96m %d%%\e[0m %s' ${percentage} "${suffix}") + printf '%s' "${output}" +} diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 0f94916..4a60d82 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -73,12 +73,15 @@ function __static__Create_Output_Files_In_Scan_Folder_And_Complete_Combinations_ Ensure_That_Given_Variables_Are_Set_And_Not_Empty \ list_of_parameters_values parameters parameters_combinations local id filename + Print_Progress_Bar 0 ${#parameters_combinations[@]} for id in "${!parameters_combinations[@]}"; do filename="$(__static__Get_Output_Filename "${id}")" # Let word splitting split values in each parameters combination __static__Add_Line_To_Combinations_File "${id}" ${parameters_combinations[id]} __static__Create_Single_Output_File_In_Scan_Folder "${id}" ${parameters_combinations[id]} + Print_Progress_Bar $((id+1)) ${#parameters_combinations[@]} done + Print_Final_Progress_Bar $((id+1)) ${#parameters_combinations[@]} } function __static__Get_Output_Filename() diff --git a/bash/source_codebase_files.bash b/bash/source_codebase_files.bash index 81596ed..c8139bf 100644 --- a/bash/source_codebase_files.bash +++ b/bash/source_codebase_files.bash @@ -33,6 +33,7 @@ function __static__Source_Codebase_Files() 'global_variables.bash' 'Hydro_functionality.bash' 'IC_functionality.bash' + 'progress_bar.bash' 'Sampler_functionality.bash' 'sanity_checks.bash' 'scan_files_operations.bash' From 36f9abb27bf7463a95647ece782878f2afd90aac Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 13 Feb 2024 10:27:56 +0100 Subject: [PATCH 398/549] Reformat note into admonition block --- docs/user/configuration-file.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/user/configuration-file.md b/docs/user/configuration-file.md index 172fd33..50fc1c2 100644 --- a/docs/user/configuration-file.md +++ b/docs/user/configuration-file.md @@ -154,8 +154,11 @@ Sampler: Afterburner: Executable: /path/to/smash ``` -**Note:** Such a configuration file will execute all the modules in production mode, involving a fine hydrodynamic grid and a large statistic of sampled events. -It is therefore better suited to be executed at a computer cluster. To test your set-up locally, we suggest using config_TEST.yaml, for more read the section [Predefined configuration files](predef-configs.md). + +!!! warning "This is going to be costly!" + Such a configuration file will execute all the modules in production mode, involving a fine hydrodynamic grid and a large statistic of sampled events. + It is therefore better suited to be executed on a computer cluster. + To test your setup locally, we suggest using the :material-file: *config_TEST.yaml* configuration file :material-arrow-right-box: [Predefined configuration files](predef-configs.md). ??? question "What if I want to omit some stages?" From 881e8af791a51b2310f42408e549ada7195b836c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 13 Feb 2024 10:35:31 +0100 Subject: [PATCH 399/549] Change main page of user guide and add parameters scan page --- docs/user/index.md | 58 +++++++++++++++++++++++++++++++++++++++------- docs/user/scans.md | 0 mkdocs.yml | 3 ++- 3 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 docs/user/scans.md diff --git a/docs/user/index.md b/docs/user/index.md index 4c3fae8..db4c8c1 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -1,17 +1,57 @@ +--- +hide: + - navigation + - toc +--- + # A unique wonderful tool The hybrid handler is a :simple-gnubash: **Bash script** and therefore it does not need any installation. +Once cloned the repository, you can simply run the `Hybrid-handler` script[^1]. + +[^1]: Be aware, that this might not work straightaway if only a very old Bash installation is available on your OS. + However, if your Bash version is `4.x` or higher, the main script will be able to give you a complete overview of system requirements.
-- :white_check_mark:   __Ready to go?__ – [Check it out!] -- :sunrise_over_mountains:   __What's the main idea?__ - [Here you go!] -- :screwdriver:   __Wanna run?__ - [Write your config file!] -- :bulb:   __Let's try it out!__ - [Use these config files!] +- :white_check_mark:{ .lg .middle }   __Are you ready to go?__ -
+ --- + + Software for the desired simulation stages as well as some OS utilities need to be installed. + + [:material-arrow-right-box:  Requirements](prerequisites.md) + +- :sunrise_over_mountains:{ .lg .middle }   __What's the main idea?__ + + --- + + Everything can be done using a handy script with different execution modes. + + [:material-arrow-right-box:  The hybrid handler main script](overview.md) + +- :screwdriver:{ .lg .middle }   __Wanna run?__ + + --- - [Check it out!]: prerequisites.md - [Here you go!]: overview.md - [Write your config file!]: configuration-file.md - [Use these config files!]: predef-configs.md + Build your configuration file to use the hybrid handler according to your needs. + + [:material-arrow-right-box:  The configuration file](configuration-file.md) + +- :bulb:{ .lg .middle }   __Let's try it out!__ + + --- + + If you want to make a test run or get inspired, predefined setups are available. + + [:material-arrow-right-box:  The predefined configuration files](predef-configs.md) + +- :material-barcode-scan:{ .lg .middle }   __Changing parameters__ + + --- + + Preparing simulations scanning over one or more parameters is straightforward. + + [:material-arrow-right-box:  Specifying parameters scans](scans.md) + + diff --git a/docs/user/scans.md b/docs/user/scans.md new file mode 100644 index 0000000..e69de29 diff --git a/mkdocs.yml b/mkdocs.yml index 093d28a..e7abd3d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,9 +22,10 @@ nav: - User Guide: - user/index.md - Getting started: user/prerequisites.md - - The hybrid handler: user/overview.md + - The execution modes: user/overview.md - Handler configuration file: user/configuration-file.md - Predefined configuration files: user/predef-configs.md + - Parameters scan syntax: user/scans.md - Developer Guide: - developer/index.md - Overview: developer/overview.md From 14b8d877ff325543428bfb44258deecbc8500aa3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 13 Feb 2024 10:39:57 +0100 Subject: [PATCH 400/549] Rename few files to use underscore instead of dashes --- .../user/{configuration-file.md => configuration_file.md} | 2 +- docs/user/{overview.md => execution_modes.md} | 0 docs/user/index.md | 8 ++++---- docs/user/{predef-configs.md => predefined_configs.md} | 0 docs/user/{scans.md => scans_syntax.md} | 0 mkdocs.yml | 8 ++++---- 6 files changed, 9 insertions(+), 9 deletions(-) rename docs/user/{configuration-file.md => configuration_file.md} (99%) rename docs/user/{overview.md => execution_modes.md} (100%) rename docs/user/{predef-configs.md => predefined_configs.md} (100%) rename docs/user/{scans.md => scans_syntax.md} (100%) diff --git a/docs/user/configuration-file.md b/docs/user/configuration_file.md similarity index 99% rename from docs/user/configuration-file.md rename to docs/user/configuration_file.md index 50fc1c2..310948d 100644 --- a/docs/user/configuration-file.md +++ b/docs/user/configuration_file.md @@ -158,7 +158,7 @@ Afterburner: !!! warning "This is going to be costly!" Such a configuration file will execute all the modules in production mode, involving a fine hydrodynamic grid and a large statistic of sampled events. It is therefore better suited to be executed on a computer cluster. - To test your setup locally, we suggest using the :material-file: *config_TEST.yaml* configuration file :material-arrow-right-box: [Predefined configuration files](predef-configs.md). + To test your setup locally, we suggest using the :material-file: *config_TEST.yaml* configuration file :material-arrow-right-box: [Predefined configuration files](predefined_configs.md). ??? question "What if I want to omit some stages?" diff --git a/docs/user/overview.md b/docs/user/execution_modes.md similarity index 100% rename from docs/user/overview.md rename to docs/user/execution_modes.md diff --git a/docs/user/index.md b/docs/user/index.md index db4c8c1..e8e301e 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -28,7 +28,7 @@ Once cloned the repository, you can simply run the `Hybrid-handler` script[^1]. Everything can be done using a handy script with different execution modes. - [:material-arrow-right-box:  The hybrid handler main script](overview.md) + [:material-arrow-right-box:  The hybrid handler main script](execution_modes.md) - :screwdriver:{ .lg .middle }   __Wanna run?__ @@ -36,7 +36,7 @@ Once cloned the repository, you can simply run the `Hybrid-handler` script[^1]. Build your configuration file to use the hybrid handler according to your needs. - [:material-arrow-right-box:  The configuration file](configuration-file.md) + [:material-arrow-right-box:  The configuration file](configuration_file.md) - :bulb:{ .lg .middle }   __Let's try it out!__ @@ -44,7 +44,7 @@ Once cloned the repository, you can simply run the `Hybrid-handler` script[^1]. If you want to make a test run or get inspired, predefined setups are available. - [:material-arrow-right-box:  The predefined configuration files](predef-configs.md) + [:material-arrow-right-box:  The predefined configuration files](predefined_configs.md) - :material-barcode-scan:{ .lg .middle }   __Changing parameters__ @@ -52,6 +52,6 @@ Once cloned the repository, you can simply run the `Hybrid-handler` script[^1]. Preparing simulations scanning over one or more parameters is straightforward. - [:material-arrow-right-box:  Specifying parameters scans](scans.md) + [:material-arrow-right-box:  Specifying parameters scans](scans_syntax.md) diff --git a/docs/user/predef-configs.md b/docs/user/predefined_configs.md similarity index 100% rename from docs/user/predef-configs.md rename to docs/user/predefined_configs.md diff --git a/docs/user/scans.md b/docs/user/scans_syntax.md similarity index 100% rename from docs/user/scans.md rename to docs/user/scans_syntax.md diff --git a/mkdocs.yml b/mkdocs.yml index e7abd3d..42e605b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,10 +22,10 @@ nav: - User Guide: - user/index.md - Getting started: user/prerequisites.md - - The execution modes: user/overview.md - - Handler configuration file: user/configuration-file.md - - Predefined configuration files: user/predef-configs.md - - Parameters scan syntax: user/scans.md + - The execution modes: user/execution_modes.md + - Handler configuration file: user/configuration_file.md + - Predefined configuration files: user/predefined_configs.md + - Parameters scan syntax: user/scans_syntax.md - Developer Guide: - developer/index.md - Overview: developer/overview.md From a209da97e8c6a2c664865b9b858552ff08f02db9 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 13 Feb 2024 18:31:15 +0100 Subject: [PATCH 401/549] Add sanity check and make most global variables readonly --- bash/sanity_checks.bash | 78 +++++++++++++++++++++++++++------------ bash/scan_validation.bash | 1 + 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index ef41eb0..f4db2af 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -21,10 +21,21 @@ function Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Var fi done __static__Set_Software_Input_Data_File_If_Not_Set_By_User 'Spectators' + __static__Set_Global_Variables_As_Readonly + __static__Perform_Logic_Checks_Depending_On_Execution_Mode +} + +function __static__Set_Global_Variables_As_Readonly() +{ readonly \ HYBRID_software_output_directory \ HYBRID_software_configuration_file \ - HYBRID_software_input_file + HYBRID_software_input_file \ + HYBRID_software_executable \ + HYBRID_software_user_custom_input_file \ + HYBRID_software_base_config_file \ + HYBRID_software_new_input_keys \ + HYBRID_optional_feature } function Perform_Internal_Sanity_Checks() @@ -37,6 +48,33 @@ function Perform_Internal_Sanity_Checks() "${HYBRID_software_base_config_file[@]}" } + +function Ensure_Consistency_Of_Afterburner_Input() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty 'HYBRID_software_input_file[Afterburner]' + if Has_YAML_String_Given_Key \ + "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'Filename'; then + local given_filename + given_filename=$(Read_From_YAML_String_Given_Key "$(< "${HYBRID_configuration_file}")" 'Afterburner' \ + 'Software_keys' 'Modi' 'List' 'Filename') + if [[ "${given_filename}" != "${HYBRID_software_input_file[Afterburner]}" ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'The Afterburner input particle list has to be modified via the ' \ + --emph 'Input_file' ' key,' 'not the ' --emph 'Software_keys' \ + ' specifying the input list filename!' + fi + fi + if Has_YAML_String_Given_Key \ + "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'Shift_ID' \ + || Has_YAML_String_Given_Key \ + "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'File_Prefix'; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'The Afterburner input particle list has to be modified via the ' \ + --emph 'Input_file' ' key,' 'not the ' --emph 'Software_keys' \ + ' specifying the input list prefix and ID!' + fi +} + function __static__Ensure_Executable_Exists() { local label=$1 executable @@ -117,30 +155,22 @@ function __static__Set_Software_Input_Data_File_If_Not_Set_By_User() fi } -function Ensure_Consistency_Of_Afterburner_Input() +function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty 'HYBRID_software_input_file[Afterburner]' - if Has_YAML_String_Given_Key \ - "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'Filename'; then - local given_filename - given_filename=$(Read_From_YAML_String_Given_Key "$(< "${HYBRID_configuration_file}")" 'Afterburner' \ - 'Software_keys' 'Modi' 'List' 'Filename') - if [[ "${given_filename}" != "${HYBRID_software_input_file[Afterburner]}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the ' \ - --emph 'Input_file' ' key,' 'not the ' --emph 'Software_keys' \ - ' specifying the input list filename!' - fi - fi - if Has_YAML_String_Given_Key \ - "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'Shift_ID' \ - || Has_YAML_String_Given_Key \ - "$(< "${HYBRID_configuration_file}")" 'Afterburner' 'Software_keys' 'Modi' 'List' 'File_Prefix'; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'The Afterburner input particle list has to be modified via the ' \ - --emph 'Input_file' ' key,' 'not the ' --emph 'Software_keys' \ - ' specifying the input list prefix and ID!' - fi + case "${HYBRID_execution_mode}" in + do) + if [[ "${HYBRID_scan_parameters[*]}" != '' ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'Configuration key ' --emph 'Scan_parameters' ' can ONLY be specified in '\ + --emph 'parameter-scan' ' execution mode.' + fi + ;; + prepare-scan) + ;; + *) + Print_Internal_And_Exit 'Unknown execution mode passed to ' --emph "${FUNCNAME}" ' function.' + ;; + esac } Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index b6c494a..42cbffd 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -16,6 +16,7 @@ function Format_Scan_Parameters_Lists() for key in "${!HYBRID_scan_parameters[@]}"; do HYBRID_scan_parameters["${key}"]=$(yq '.. style="flow"' <<< "${HYBRID_scan_parameters["${key}"]}") done + readonly HYBRID_scan_parameters } # NOTE: In the following functions the parameter(s) to be scanned are stored as a period-separated From e7cd1253360fdc458344be52cd3857de3ca8fe4c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 13 Feb 2024 18:35:22 +0100 Subject: [PATCH 402/549] Add user documentation for prepare-scan mode --- docs/user/configuration_file.md | 30 +++++++++++++ docs/user/execution_modes.md | 80 +++++++++++++++++++++++++++++---- docs/user/scans_syntax.md | 60 +++++++++++++++++++++++++ docs/user/scans_types.md | 59 ++++++++++++++++++++++++ mkdocs.yml | 13 +++--- 5 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 docs/user/scans_types.md diff --git a/docs/user/configuration_file.md b/docs/user/configuration_file.md index 310948d..36bf083 100644 --- a/docs/user/configuration_file.md +++ b/docs/user/configuration_file.md @@ -56,6 +56,36 @@ However, **it is strongly encouraged to exclusively use absolute paths** as rela If you need to add a key to the software default configuration file, you should create a custom one and specify it via the `Config_file` key. Depending on your needs, you could also create a more complete configuration file and change the values of some keys in your run(s) via this key. +???+ config-key "`Scan_parameters`" + + **This key can only be specified in `prepare-scan` execution mode.** + + List of software input keys whose value is meant to be scanned. + Each parameter has to be specified concatenating with a period all the keys as they would appear in the `Software_keys` map. + For example, if a software key reads + ```yaml + Software_keys: + foo: + bar: + baz: 42 + ``` + then it would be specified in the `Scan_parameters` list as `"foo.bar.baz"`. + Such a list is a YAML array and therefore it can be specified both in the compact and extended form. + + === "Compact form" + + ```yaml + Scan_parameters: ["foo.bar", "foo.baz"] + ``` + + === "Extended form" + + ```yaml + Scan_parameters: + - "foo.bar" + - "foo.baz" + ``` + ## The initial conditions section There is no specific key of the `IC` section and only the generic ones can be used. diff --git a/docs/user/execution_modes.md b/docs/user/execution_modes.md index 05bb1c2..93c3d4e 100644 --- a/docs/user/execution_modes.md +++ b/docs/user/execution_modes.md @@ -1,21 +1,52 @@ # The hybrid handler -To run any of the different stages of the model, the :simple-gnubash: `Hybrid-handler` executable should be used. -Such an executable has different execution modes and each of these can be invoked with the `--help` option to get specific documentation of such a mode. +Every operation can be done simply by running the :simple-gnubash: `Hybrid-handler` executable. +This has different execution modes and most of them can be invoked with the `--help` option to get specific documentation of such a mode. ``` title="Example about getting help for a given mode" ./Hybrid-handler do --help ``` -If the executable is run without any execution mode (or simply with `--help`), an overview of its features is given. - -Each run of the hybrid handler makes use of a configuration file and it is the user responsibility to provide one. +Each run of the hybrid handler (apart from auxiliary execution modes) makes use of a configuration file and it is the user responsibility to provide one. Few further customizations are possible using command line options, which are documented in the helper of each execution mode. -## The general behavior +!!! tip "A :fontawesome-brands-square-git: inspired user interface" + If you are used to [:simple-git: Git](https://git-scm.com), you will immediately recognize the analogy. + This has been implemented on purpose. + Different tasks can be achieved using different execution modes which are analogous to Git commands. + +## Auxiliary execution modes + +=== "Getting help" + + ``` + ./Hybrid-handler help + ``` + The `help` execution mode is the default one. + Therefore, if the :simple-gnubash: `Hybrid-handler` executable is run without any command line option, an overview of its features is given. + This is equivalent to run it with the `--help` command line option. + To be more user friendly, any additional command line option provided in `help` mode is ignored (even if wrong), and the help message is given. + +=== "Obtaining the software version" + + ``` + ./Hybrid-handler version + ``` + The hybrid handler can be asked to print its version. + This might be particularly useful to store the handler version as metadata for reproducibility reasons. + As many Unix OS tools support a `--version` command line option, an alias for this mode has been added and the hybrid handler can be run with the `--version` command line option, too. + + ??? question "Why do I just get a warning when asking the version?" + If for some reason you are not on tagged version of the codebase (e.g. you checked out a different commit), then you will be warned by the hybrid handler that you are not using an official release. + This is meant to raise awareness, as it is encouraged to use stable versions only, especially for physics projects. + + +## The `do` execution mode + +The main `do` execution mode runs stages of the model and creates a given output tree at the specified output directory (by default this is a subdirectory of the folder from where the handler is run named :file_folder: ***data***, but it can customized using the `--output-directory` command line option). + +Assuming all stages are run, this is the folder tree that the user will obtain. -The main `do` execution mode of the handler runs stages of the model and it will create a given output tree at the specified output directory (by default this is a subdirectory of the folder from where the handler is run named :file_folder: ***data***, but it can customized using the `-o` or `--output-directory` command line option). -Assuming all stages are run, this is what the user will obtain. ``` { .bash .no-copy } 📂 Output-directory │ @@ -35,3 +66,36 @@ Assuming all stages are run, this is what the user will obtain. └─── 📂 Run_ID └─ # output files ``` + +This might differ depending on how the used [handler configuration file](configuration_file.md) has been set up. + +## The `prepare-scan` execution mode + +This is a different mode, which per se won't run any simulation. +Instead, the hybrid handler will prepare future runs by creating configuration files in a sub-folder of the output folder. +The user can use the `--scan-name` command line option to provide a label to the scan, which will name the output sub-folder and will be used as filename prefix. +For example, using the default generic `scan` name, the user will obtain the following tree folder. +``` { .bash .no-copy } +📂 Output-directory +└─ 📂 scan + ├─── 🗒️ scan_combinations.dat + ├─── 🗒️ scan_run_1.yaml + ├─── 🗒️ scan_run_2.yaml + └─── 🗒️ ... +``` + +
+ +- :material-hammer-wrench: The scan parameters must be properly defined in the hybrid handler configuration file using [a dedicated syntax](scans_syntax.md). + +- :material-graph: How the parameters combinations are made is described in the documentation of the different [types of scans](scans_types.md). + +
+ +!!! note "Some useful remarks" + The :material-file: *`scan_combinations.dat`* file will contain a list of all parameters combinations in a easily parsable table. + + The :material-file: *`scan_run_*.yaml`* files are configuration files for the hybrid handler to run physics simulations. + For reproducibility reasons as well as to keep track that they belong to a scan, the scan name and parameters are printed in the beginning in commented lines. + These files will have leading zeroes in the run index contained in the filename, depending on the total number of prepared simulations. + This allows to keep them easily sorted. diff --git a/docs/user/scans_syntax.md b/docs/user/scans_syntax.md index e69de29..3183de2 100644 --- a/docs/user/scans_syntax.md +++ b/docs/user/scans_syntax.md @@ -0,0 +1,60 @@ +# The parameters scan syntax + +In order to properly run the hybrid handler in `prepare-scan` mode, the configuration file must be properly created and fulfil few additional constraints. + +!!! danger "Do not forget to declare scan parameters as such!" + Each parameter which must be scanned has to be declared so by using the `Scan_parameters` key in the corresponding stage section. + **If a parameter is not declared to be scanned** and is specified as a scan in the `Software_keys` section, this will probably not be caught by the hybrid handler and **the produced configurations are likely to be wrong**. + +!!! warning "Scanning only numerical parameters is possible" + At the moment, it is not possible to scan parameters whose value is not numerical. + More precisely, only integer, float or boolean YAML types are accepted. + Feel free to open an issue if this is a too strong restriction for you. + +Once scan parameters have been specified as such, they **must** appear in the `Software_keys` map. +However, their value should not be a simple parameter value, but a YAML map with a given format. +In the following we will refer to this map as "scan object". +The different allowed ways to specify scan objects are discussed in the following, providing an example for each of them. +The scan object shall always have a `Scan` key as single top-level key. +```yaml title="Generic parameter scan specification" +Software_keys: + Parameter: + Scan: + ... +``` + +### Explicitly specifying the parameter values + +The most basic way to specify a scan is by providing the list of its values. +This is possible in the `Values` YAML array inside the `Scan` map. + +=== "Compact style" + + ```yaml title="Example" + Software_keys: + foo: + bar: {Scan: {Values: [17, 42, 666]}} + ``` + +=== "Mixed style" + + ```yaml title="Example" + Software_keys: + foo: + bar: + Scan: + Values: [17, 42, 666] + ``` + +=== "Extended style" + + ```yaml title="Example" + Software_keys: + foo: + bar: + Scan: + Values: + - 17 + - 42 + - 666 + ``` diff --git a/docs/user/scans_types.md b/docs/user/scans_types.md new file mode 100644 index 0000000..7196e85 --- /dev/null +++ b/docs/user/scans_types.md @@ -0,0 +1,59 @@ +# Types of scans + +If a parameter scan is created with more than one scan parameter, it has to be decided how the different values for each parameter will be combined. + +## All combinations by default + +Unless differently specified(1), all combinations of parameters values are considered and one output handler configuration file per combination will be created. +{.annotate} + +1. :warning: Actually, different types of scan and a way to select them has still to be implemented. + +From the mathematical point of view, given $n$ scan parameters with set of values $X_1, ..., X_n\,$, the set of considered combinations is nothing but the $n$-ary Cartesian product over all sets of values, + +$$ +X_1 \times \dots \times X_n = \bigl\{(x_1, ..., x_n) \;|\; x_i \in X_i \quad\forall\, i \in \{1,...,n\} \bigr\} +$$ + +For example, specifying two different scan parameters with 2 and 5 values, respectively, 10 values combinations will be built and 10 output files produced. + +=== "Handler configuration" + + ```yaml + # ... + Scan_parameters: ["Foo.Bar", "Foo.Baz"] + Software_keys: + Foo: + Bar: {Scan: {Values: [-1, 0, 17, 42, 666]}} + Baz: {Scan: {Values: [True, False]}} + # ... + ``` + +=== ":file_folder: Scan folder" + + ``` { .console .no-copy } + $ ls scan + scan_combinations.dat scan_run_04.yaml scan_run_08.yaml + scan_run_01.yaml scan_run_05.yaml scan_run_09.yaml + scan_run_02.yaml scan_run_06.yaml scan_run_10.yaml + scan_run_03.yaml scan_run_07.yaml + ``` + +=== ":material-file: Scan combinations" + + ``` { .yaml .no-copy } + # Parameter_1: Foo.Bar + # Parameter_2: Foo.Baz + # + #___Run Parameter_1 Parameter_2 + 1 -1 True + 2 -1 False + 3 0 True + 4 0 False + 5 17 True + 6 17 False + 7 42 True + 8 42 False + 9 666 True + 10 666 False + ``` diff --git a/mkdocs.yml b/mkdocs.yml index 42e605b..4bb572fbf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,11 +21,14 @@ nav: - Home: index.md - User Guide: - user/index.md - - Getting started: user/prerequisites.md - - The execution modes: user/execution_modes.md - - Handler configuration file: user/configuration_file.md - - Predefined configuration files: user/predefined_configs.md - - Parameters scan syntax: user/scans_syntax.md + - Getting started: + - Prerequisites: user/prerequisites.md + - Execution modes: user/execution_modes.md + - Handler configuration file: user/configuration_file.md + - Predefined configuration files: user/predefined_configs.md + - Parameters scan: + - Scan syntax: user/scans_syntax.md + - Types of scans: user/scans_types.md - Developer Guide: - developer/index.md - Overview: developer/overview.md From 8c3d480bb29cfc1554d3df0055fa32224d6c2c29 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 15:23:27 +0100 Subject: [PATCH 403/549] Add cross links to user guide and missing key in examples --- docs/user/configuration_file.md | 1 + docs/user/scans_syntax.md | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/user/configuration_file.md b/docs/user/configuration_file.md index 36bf083..bcbe8d2 100644 --- a/docs/user/configuration_file.md +++ b/docs/user/configuration_file.md @@ -56,6 +56,7 @@ However, **it is strongly encouraged to exclusively use absolute paths** as rela If you need to add a key to the software default configuration file, you should create a custom one and specify it via the `Config_file` key. Depending on your needs, you could also create a more complete configuration file and change the values of some keys in your run(s) via this key. + ???+ config-key "`Scan_parameters`" **This key can only be specified in `prepare-scan` execution mode.** diff --git a/docs/user/scans_syntax.md b/docs/user/scans_syntax.md index 3183de2..86a8911 100644 --- a/docs/user/scans_syntax.md +++ b/docs/user/scans_syntax.md @@ -3,7 +3,7 @@ In order to properly run the hybrid handler in `prepare-scan` mode, the configuration file must be properly created and fulfil few additional constraints. !!! danger "Do not forget to declare scan parameters as such!" - Each parameter which must be scanned has to be declared so by using the `Scan_parameters` key in the corresponding stage section. + Each parameter which must be scanned has to be declared so by using the `Scan_parameters` key in the corresponding stage section [:material-arrow-right-box: key description](configuration_file.md#scan-parameters). **If a parameter is not declared to be scanned** and is specified as a scan in the `Software_keys` section, this will probably not be caught by the hybrid handler and **the produced configurations are likely to be wrong**. !!! warning "Scanning only numerical parameters is possible" @@ -17,6 +17,7 @@ In the following we will refer to this map as "scan object". The different allowed ways to specify scan objects are discussed in the following, providing an example for each of them. The scan object shall always have a `Scan` key as single top-level key. ```yaml title="Generic parameter scan specification" +Scan_parameters: ["Parameter"] Software_keys: Parameter: Scan: @@ -31,6 +32,7 @@ This is possible in the `Values` YAML array inside the `Scan` map. === "Compact style" ```yaml title="Example" + Scan_parameters: ["foo.bar"] Software_keys: foo: bar: {Scan: {Values: [17, 42, 666]}} @@ -39,6 +41,7 @@ This is possible in the `Values` YAML array inside the `Scan` map. === "Mixed style" ```yaml title="Example" + Scan_parameters: ["foo.bar"] Software_keys: foo: bar: @@ -49,6 +52,7 @@ This is possible in the `Values` YAML array inside the `Scan` map. === "Extended style" ```yaml title="Example" + Scan_parameters: ["foo.bar"] Software_keys: foo: bar: From 989f3bc439db3408418a4926d9ff9f79acaff004 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 15:23:42 +0100 Subject: [PATCH 404/549] Add developer documentation about parameters scan --- docs/developer/parameters_scan.md | 145 ++++++++++++++++++++++++++++++ mkdocs.yml | 9 +- 2 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 docs/developer/parameters_scan.md diff --git a/docs/developer/parameters_scan.md b/docs/developer/parameters_scan.md new file mode 100644 index 0000000..0255127 --- /dev/null +++ b/docs/developer/parameters_scan.md @@ -0,0 +1,145 @@ +# Design of parameter scans + +!!! info "Reading the code is always encouraged" + Reading the code starting from the main script and following the function calls will probably already give a good overview of how the `parameter-scan` mode is implemented. + However, a couple of comments might help understanding the workflow and the rationale behind it. + +
+ +```mermaid +graph LR + subgraph one["Parsing and validation"] + direction TB + A("Parse scan parameters") --> B("Validate and store scan YAML map") + B --> C("Create list of single parameters values") + end + subgraph two["Create and populate scan folder"] + direction TB + D("Get parameters combinations") --> E("Prepare scan folder and combination file") + E --> F("Append parameter combination to file") + F --> G("Create output file") + end + one --> two + + style one fill:none,stroke:#ff7f50; + style two fill:none,stroke:#ff7f50; +``` + +
+ +## Lack of multi-dimensional arrays + +In Bash it is not possible to have an array as entry of another array. +However, for the parameter scan, it is needed to somehow store a list of parameters, each with a list of possible values and this would be a natural usage of a two-dimensional array. + +By design, scan parameters must have either boolean or numerical value and this also means that they do not contain spaces. +Hence, it is possible to store a list of values as a single string and then use word splitting to e.g. create an array out of it with values stored as separate entries. + +```console title="Proof of concept" +$ list="1 2 3" +$ array=( ${list} ) # This expansion must be unquoted to let word splitting act +$ printf '%s\n' "${array[@]}" +1 +2 +3 +``` + +The same trick is used to store the list of scan parameters. +This is possible because the YAML keys happen not to have spaces in them. + +!!! danger "Spaces in YAML key or values would break the implementation" + Since **we rely on word splitting to correctly split values or parameter names**, this would be automatically broken if spaces were part of values, since word splitting would then split a single value in different ones. + If having spaces in keys or values turns out to be a need, some more involved implementation is required. + +## Interplay between Bash and YAML + +The scan information in the configuration file must be parsed into Bash variables and the first aspect to deal with is the fact that the user is free to use YAML syntax in its full glory. +For example, sequences can be specified using square brackets or an explicit list. +We do not want to deal this ambiguity on the Bash side and therefore we make sure that everything is stored in the same way. +The `yq` tool offers a way to print out sequences in a compact form. + +```console title="Enforcing compact style reading out YAML sequences" +$ echo $'- 17\n- 42\n- 666' +- 17 +- 42 +- 666 +$ yq '.. style="flow"' <<< $'- 17\n- 42\n- 666' +[17, 42, 666] +``` + +This is used both when parsing the `Scan_parameters` key and when parsing each parameter values. + +## Storing scan parameters values + +Before having a list of values for each scan parameter, its scan YAML map has to be extracted from the handler configuration file and then parsed to generate the list of values. +This might be done in a single swipe but would make it basically impossible to later add sampling algorithms that need to consider all scan parameters at the same time to produce their list of values (e.g. Latin Hypercube Sampling). +Therefore the scan YAML maps are first stored and then used at a later point to produce the list of values of each parameter. + +A single Bash associative array has been chosen for this purpose. +Its keys are the parameters stored as a period-separated list of YAML keys as they appear in the hybrid handler configuration file, and precisely in the 'Software_keys' sections. +The value of each array entry is, in a first phase, the YAML scan map. +Later, this is replaced by a string containing a list of parameter values. +In this case a YAML sequence style is kept, i.e. with squares and commas. + +=== "Final content" + + ``` {.bash .no-copy title="Example of parameter values array"} + list_of_parameters_values=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='[4.3, 7.7]' + ['Hydro.Software_keys.etaS']='[0.13, 0.15, 0.17]' + ) + ``` + +=== "First phase" + + ``` {.bash .no-copy title="Example of parameter values array"} + list_of_parameters_values=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Values: [4.3, 7.7]}' + ['Hydro.Software_keys.etaS']='{Values: [0.13, 0.15, 0.17]}' + ) + ``` + +## Generating all possible combinations + +Technically speaking, this is straightforward in Bash, thanks to the built-in brace expansion. + +=== "Example of brace expansion" + + ```console + $ printf '%s\n' {a,b,c}_{1,2} + a_1 + a_2 + b_1 + b_2 + c_1 + c_2 + ``` + +=== "Order of expansion" + + ```console + $ var="{a,b,c}_{1,2}" + $ printf '%s\n' ${var} + {a,b,c}_{1,2} + ``` + +=== "Workaround using eval" + + ```console + $ var="{a,b,c}_{1,2}" + $ eval printf '%s\\\n' "${var}" + a_1 + a_2 + b_1 + b_2 + c_1 + c_2 + ``` + +However, the Bash shell expansion mechanism works in a way such that brace expansion happens **before** variables expansion. +To make brace expansion act on a string that is contained in a variable requires using `eval`, which is always delicate, because it might open up to security flaws like enabling code injections. +In this case the strings passed to it are validated and guaranteed to be numbers only, though. + +!!! danger "Allowing string values requires action here!" + If at some point it is needed to support scans on parameter of string type, a different approach should be probably used to generate all combinations. + Probably, as it is, the code would also break in case of spaces into the strings. diff --git a/mkdocs.yml b/mkdocs.yml index 4bb572fbf..73edc85 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -32,6 +32,7 @@ nav: - Developer Guide: - developer/index.md - Overview: developer/overview.md + - Parameters scan: developer/parameters_scan.md markdown_extensions: # More markdown functionality @@ -51,14 +52,18 @@ markdown_extensions: # For LaTeX and maths - pymdownx.arithmatex: generic: true - # For code highlights and related aspects + # For code highlights and related aspects plus diagrams - pymdownx.highlight: anchor_linenums: true line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true # Customize tables of contents From aa31b17791d38a5ea872e9dad88ff3d8b5e79328 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 15:25:38 +0100 Subject: [PATCH 405/549] Improve progress bar output shortening it --- bash/progress_bar.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/progress_bar.bash b/bash/progress_bar.bash index e7bf788..5792376 100644 --- a/bash/progress_bar.bash +++ b/bash/progress_bar.bash @@ -58,9 +58,9 @@ function __static__Get_Progress_Bar() for ((i = 0; i < ${percentage}; i++)); do output+="${bar}" done - output+="${half_bar_right}" + output+="${half_bar_right}\e[38;5;237m" for ((i = ${percentage}; i < 100; i++)); do - output+="\e[38;5;237m${bar}" + output+="${bar}" done output+=$(printf '\e[96m %d%%\e[0m %s' ${percentage} "${suffix}") printf '%s' "${output}" From f79d2265ae62becfdd23d526d4841cb5076b1b06 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 15:26:25 +0100 Subject: [PATCH 406/549] Use built-in shell expansion instead of sed --- bash/scan_files_operations.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 4a60d82..2296654 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -35,8 +35,8 @@ function __static__Get_All_Parameters_Combinations() { local values string_to_be_expanded for values in "$@"; do - values=$(sed -e 's/ //g' -e 's/[[]/\{/' -e 's/[]]/\}/' <<< "${values}") - string_to_be_expanded+="${values}_" + # Get rid of spaces and square brackets and prepare brace expansion + string_to_be_expanded+="{${values//[][ ]/}}_" done # NOTE: The following use of 'eval' is fine since the string that is expanded # is guaranteed to be validated to contain only YAML int, bool or float. From c825eeedc13725fb471d6a043fb2965fda1a4cd4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 15:26:51 +0100 Subject: [PATCH 407/549] Fix failing test due to missing include and changed filename --- tests/unit_tests_scan_files_operations.bash | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index c381be3..a110ccb 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -12,6 +12,7 @@ function Make_Test_Preliminary_Operations__scan-create-single-file() local file_to_be_sourced list_of_files list_of_files=( 'global_variables.bash' + 'progress_bar.bash' 'scan_files_operations.bash' ) for file_to_be_sourced in "${list_of_files[@]}"; do @@ -49,7 +50,7 @@ function Unit_Test__scan-create-single-file() 6 7.7 0.17 EOF HYBRID_scan_directory='scan_test' - Call_Codebase_Function Create_And_Populate_Scan_Folder + Call_Codebase_Function Create_And_Populate_Scan_Folder &> /dev/null 9>&1 # Suppress progress bar, too cd "${HYBRID_scan_directory}" shopt -s nullglob local -r list_of_files=(*) @@ -67,7 +68,7 @@ EOF fi continue fi - if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_[1-6]\.yaml$ ]]; then + if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_run_[1-6]\.yaml$ ]]; then Print_Error 'Filename ' --emph "${file}" ' not matching expected name.' return 1 fi From e1fef2e751368aa49dfbc685ff6be4e2df9dbb4e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 17:11:01 +0100 Subject: [PATCH 408/549] Improve progress bar and make it more flexible --- bash/progress_bar.bash | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/bash/progress_bar.bash b/bash/progress_bar.bash index 5792376..a85f022 100644 --- a/bash/progress_bar.bash +++ b/bash/progress_bar.bash @@ -10,19 +10,28 @@ function Print_Progress_Bar { __static__Validate_Progress_Bar_Input "$@" - Print_Info -l -- "\e[K$(__static__Get_Progress_Bar $1 $2)\r\e[1A" + Print_Info -l -- "\e[K$(__static__Get_Progress_Bar "$@")\r\e[1A" } function Print_Final_Progress_Bar { __static__Validate_Progress_Bar_Input "$@" - Print_Info -l -- "\e[K$(__static__Get_Progress_Bar $1 $2)" + Print_Info -l -- "\e[K$(__static__Get_Progress_Bar "$@")" } function __static__Validate_Progress_Bar_Input() { - if [[ $# -lt 2 ]] || [[ ! $1 =~ ^[0-9]+(.[0-9]+)*$ ]] || [[ ! $2 =~ ^[0-9]+(.[0-9]+)*$ ]]; then - Print_Internal_And_Exit --emph "${FUNCNAME[1]}" ' wrongly called (' --emph "$1 $2" ').' + if [[ $# -lt 2 ]] ; then + Print_Internal_And_Exit \ + --emph "${FUNCNAME[1]}" ' wrongly called: ' --emph 'Missing arguments' '.' + elif [[ ! $1 =~ ^[0-9]+(.[0-9]+)*$ ]] || [[ ! $2 =~ ^[0-9]+(.[0-9]+)*$ ]]; then + Print_Internal_And_Exit \ + --emph "${FUNCNAME[1]}" ' wrongly called: ' --emph 'wrong first two arguments' '.' + elif [[ ${5-} != '' ]]; then + if [[ $5 -gt 100 ]] || [[ $5 -lt 1 ]]; then + Print_Internal_And_Exit \ + --emph "${FUNCNAME[1]}" ' wrongly called: ' --emph "$5 not in [0,100]" '.' + fi fi } @@ -33,10 +42,16 @@ function __static__Get_Progress_Bar() total=$2 \ prefix="${3-}" \ suffix="${4-}" \ + width_percentage="${5:-33}" \ bar="━" \ half_bar_right="╸" local output="${prefix} " color - local -r percentage=$(awk '{printf "%.0f", 100*$1/$2}' <<< "${done} ${total}") + local -r \ + width=$((COLUMNS * width_percentage / 100)) \ + percentage=$(awk '{printf "%.0f", 100*$1/$2}' <<< "${done} ${total}") + if [[ ${width} -lt 1 ]]; then + Print_Internal_And_Exit 'Progress bar too short: ' --emph "width = ${width}" + fi if [[ ${percentage} -lt 30 ]]; then color='\e[91m' elif [[ ${percentage} -lt 40 ]]; then @@ -55,13 +70,14 @@ function __static__Get_Progress_Bar() color='\e[92m' fi output+=$(printf "${color}") - for ((i = 0; i < ${percentage}; i++)); do + local -r width_done=$((width * percentage / 100)) + for ((i = 0; i < ${width_done}; i++)); do output+="${bar}" done output+="${half_bar_right}\e[38;5;237m" - for ((i = ${percentage}; i < 100; i++)); do + for ((i = ${width_done}; i < width; i++)); do output+="${bar}" done - output+=$(printf '\e[96m %d%%\e[0m %s' ${percentage} "${suffix}") + output+=$(printf '\e[96m %3d%%\e[0m %s' ${percentage} "${suffix}") printf '%s' "${output}" } From ba0954872b68289416e7fc4221f4935d06925881 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 17:12:52 +0100 Subject: [PATCH 409/549] Fix formatting failing test --- bash/command_line_parsers/helper.bash | 14 ++++++------ bash/command_line_parsers/main_parser.bash | 5 ++--- bash/command_line_parsers/sub_parser.bash | 6 ++--- bash/sanity_checks.bash | 5 ++--- bash/scan_files_operations.bash | 25 ++++++++++++--------- tests/unit_tests_scan_files_operations.bash | 4 ++-- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/bash/command_line_parsers/helper.bash b/bash/command_line_parsers/helper.bash index 15d03b2..dcf2cbb 100644 --- a/bash/command_line_parsers/helper.bash +++ b/bash/command_line_parsers/helper.bash @@ -32,16 +32,16 @@ function __static__Print_Main_Help_Message() # that handler prerequisites are missing. Hence, possibly only bash should be used. declare -A section_headers auxiliary_modes_description execution_modes_description section_headers=( - [auxiliary_modes_description]='Auxiliary modes for help, information or setup' - [execution_modes_description]='Prepare needed files and folders and/or submit/run new simulation(s)' + ['auxiliary_modes_description']='Auxiliary modes for help, information or setup' + ['execution_modes_description']='Prepare needed files and folders and/or submit/run new simulation(s)' ) auxiliary_modes_description=( - [help]='Display this help message' - [version]='Get information about the version in use' + ['help']='Display this help message' + ['version']='Get information about the version in use' ) execution_modes_description=( - [do]='Do everything is necessary to run the workflow given in the configuration file' - [prepare-scan]='Prepare configurations files for the handler scanning in the given parameters' + ['do']='Do everything is necessary to run the workflow given in the configuration file' + ['prepare-scan']='Prepare configurations files for the handler scanning in the given parameters' ) __static__Print_Handler_Header_And_Usage_Synopsis __static__Print_Modes_Description @@ -135,7 +135,7 @@ function __static__Print_Given_Command_Line_Option_Help() "Label of the scan used by the handler to produce output." \ "The new configuration files will be put in a sub-folder" \ "of the output directory named using the specified name" \ - "and the configuration files themselves will contain the"\ + "and the configuration files themselves will contain the" \ "scan name as part of their name." ;; *) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 31f1083..3bddac5 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -106,7 +106,7 @@ function __static__Replace_Short_Options_With_Long_Ones() if Element_In_Array_Equals_To "${option}" "${!options_map[@]}"; then option=${options_map["${option}"]} fi - HYBRID_command_line_options_to_parse+=( "${option}" ) + HYBRID_command_line_options_to_parse+=("${option}") done } @@ -114,7 +114,7 @@ function __static__Validate_Command_Line_Options() { _HYBRID_Declare_Allowed_Command_Line_Options # Let word splitting split valid options - local -r valid_options=( ${HYBRID_allowed_command_line_options["${HYBRID_execution_mode}"]} ) + local -r valid_options=(${HYBRID_allowed_command_line_options["${HYBRID_execution_mode}"]}) local option for option in "${HYBRID_command_line_options_to_parse[@]}"; do if [[ ${option} =~ ^- ]]; then @@ -127,5 +127,4 @@ function __static__Validate_Command_Line_Options() done } - Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/command_line_parsers/sub_parser.bash b/bash/command_line_parsers/sub_parser.bash index ffe238b..e157a8f 100644 --- a/bash/command_line_parsers/sub_parser.bash +++ b/bash/command_line_parsers/sub_parser.bash @@ -13,7 +13,7 @@ function Parse_Specific_Mode_Options_prepare-scan() HYBRID_command_line_options_to_parse=() while [[ $# -gt 0 ]]; do case "$1" in - --scan-name ) + --scan-name) if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else @@ -21,8 +21,8 @@ function Parse_Specific_Mode_Options_prepare-scan() fi shift 2 ;; - * ) - HYBRID_command_line_options_to_parse+=( "$1" ) + *) + HYBRID_command_line_options_to_parse+=("$1") shift ;; esac diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index f4db2af..35bb53d 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -161,12 +161,11 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() do) if [[ "${HYBRID_scan_parameters[*]}" != '' ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Configuration key ' --emph 'Scan_parameters' ' can ONLY be specified in '\ + 'Configuration key ' --emph 'Scan_parameters' ' can ONLY be specified in ' \ --emph 'parameter-scan' ' execution mode.' fi ;; - prepare-scan) - ;; + prepare-scan) ;; *) Print_Internal_And_Exit 'Unknown execution mode passed to ' --emph "${FUNCNAME}" ' function.' ;; diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 2296654..40195c7 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -11,7 +11,7 @@ function Create_And_Populate_Scan_Folder() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values local -r \ - parameters=( "${!list_of_parameters_values[@]}" ) \ + parameters=("${!list_of_parameters_values[@]}") \ scan_combinations_file="${HYBRID_scan_directory}/${HYBRID_scan_combinations_filename}" local parameters_combinations readarray -t parameters_combinations < \ @@ -58,11 +58,11 @@ function __static__Create_Combinations_File_With_Metadata_Header_Block() local index { for index in "${!parameters[@]}"; do - printf '# Parameter_%d: %s\n' $((index+1)) "${parameters[index]}" + printf '# Parameter_%d: %s\n' $((index + 1)) "${parameters[index]}" done printf '#\n#___Run' for index in "${!parameters[@]}"; do - printf ' Parameter_%d' $((index+1)) + printf ' Parameter_%d' $((index + 1)) done printf '\n' } > "${scan_combinations_file}" @@ -72,16 +72,19 @@ function __static__Create_Output_Files_In_Scan_Folder_And_Complete_Combinations_ { Ensure_That_Given_Variables_Are_Set_And_Not_Empty \ list_of_parameters_values parameters parameters_combinations + local -r number_of_files=${#parameters_combinations[@]} local id filename - Print_Progress_Bar 0 ${#parameters_combinations[@]} + Print_Progress_Bar 0 ${number_of_files} for id in "${!parameters_combinations[@]}"; do filename="$(__static__Get_Output_Filename "${id}")" # Let word splitting split values in each parameters combination __static__Add_Line_To_Combinations_File "${id}" ${parameters_combinations[id]} __static__Create_Single_Output_File_In_Scan_Folder "${id}" ${parameters_combinations[id]} - Print_Progress_Bar $((id+1)) ${#parameters_combinations[@]} + Print_Progress_Bar \ + $((id + 1)) ${number_of_files} '' "$(printf '%5d' $((id + 1)))/${number_of_files} files" done - Print_Final_Progress_Bar $((id+1)) ${#parameters_combinations[@]} + Print_Final_Progress_Bar \ + $((id + 1)) ${number_of_files} '' "$(printf '%5d' $((id + 1)))/${number_of_files} files" } function __static__Get_Output_Filename() @@ -91,7 +94,7 @@ function __static__Get_Output_Filename() # amount of leading zeroes in order to make sorting easier for the user. local -r \ number_of_combinations=${#parameters_combinations[@]} \ - run_number=$(($1+1)) + run_number=$(($1 + 1)) printf '%s_run_%0*d.yaml' \ "${HYBRID_scan_directory}/$(basename "${HYBRID_scan_directory}")" \ "${#number_of_combinations}" \ @@ -104,7 +107,7 @@ function __static__Add_Line_To_Combinations_File() # These fields lengths are hard-coded for the moment and are meant to # properly align the column content to the header line description { - printf '%7d' $(($1+1)) + printf '%7d' $(($1 + 1)) shift printf ' %11s' "$@" printf '\n' @@ -114,13 +117,13 @@ function __static__Add_Line_To_Combinations_File() function __static__Create_Single_Output_File_In_Scan_Folder() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters filename - local -r run_number=$(($1+1)) + local -r run_number=$(($1 + 1)) shift - local -r set_of_values=( "$@" ) + local -r set_of_values=("$@") Internally_Ensure_Given_Files_Do_Not_Exist "${filename}" __static__Add_Parameters_Comment_Line_To_New_Configuration_File local index yq_replacements - for ((index=0; index<${#parameters[@]}; index++)); do + for ((index = 0; index < ${#parameters[@]}; index++)); do yq_replacements+="( .${parameters[index]} ) = ${set_of_values[index]} |" done yq "${yq_replacements%?}" "${HYBRID_configuration_file}" >> "${filename}" diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index a110ccb..03b17e2 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -37,7 +37,7 @@ function Unit_Test__scan-create-single-file() Software_keys: etaS: {Scan: {Values: [0.13, 0.15, 0.17]}} ' > 'config.yaml' - cat > ref_scan_combinations.dat < ref_scan_combinations.dat << EOF # Parameter_1: IC.Software_keys.Modi.Collider.Sqrtsnn # Parameter_2: Hydro.Software_keys.etaS # @@ -72,7 +72,7 @@ EOF Print_Error 'Filename ' --emph "${file}" ' not matching expected name.' return 1 fi - values=( $1 ) # Use word splitting to split values + values=($1) # Use word splitting to split values shift sqrt_snn=$(Read_From_YAML_String_Given_Key "$(< "${file}")" 'IC' 'Software_keys' 'Modi' 'Collider' 'Sqrtsnn') if [[ "${sqrt_snn}" != "${values[0]}" ]]; then From 308920dd5ef4cbcb5df2227625fb5ca99b8a5518 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 17:56:01 +0100 Subject: [PATCH 410/549] Add conservative check on execution mode to main parser --- bash/command_line_parsers/main_parser.bash | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 3bddac5..134434c 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -57,6 +57,11 @@ function Parse_Execution_Mode() # without the risk of accepting invalid options. function Parse_Command_Line_Options() { + if [[ ! ${HYBRID_execution_mode} =~ ^(do|prepare-scan)$ ]]; then + Print_Internal_And_Exit \ + 'Function ' --emph "${FUNCNAME}" ' should not be called in ' \ + --emph "${HYBRID_execution_mode}" ' execution mode.' + fi __static__Validate_Command_Line_Options Call_Function_If_Existing_Or_No_Op Parse_Specific_Mode_Options_${HYBRID_execution_mode} set -- "${HYBRID_command_line_options_to_parse[@]}" From 4be74254094fea61350dbcc1aa3e8deb5bb46507 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 17:57:08 +0100 Subject: [PATCH 411/549] Fix failing unit tests and create tests files in test folder Now that each unit test is run in a dedicated folder, it is better to let each test create files in its folder and not at the level where all tests folders are. --- bash/progress_bar.bash | 2 +- bash/sanity_checks.bash | 1 + tests/unit_tests_Hydro_functionality.bash | 12 ++++++------ tests/unit_tests_command_line_parser.bash | 3 +++ tests/unit_tests_configuration.bash | 18 +++++++++--------- tests/unit_tests_help_for_user.bash | 11 +++++++++-- ...nit_tests_software_input_functionality.bash | 4 ++-- 7 files changed, 31 insertions(+), 20 deletions(-) diff --git a/bash/progress_bar.bash b/bash/progress_bar.bash index a85f022..094d235 100644 --- a/bash/progress_bar.bash +++ b/bash/progress_bar.bash @@ -21,7 +21,7 @@ function Print_Final_Progress_Bar function __static__Validate_Progress_Bar_Input() { - if [[ $# -lt 2 ]] ; then + if [[ $# -lt 2 ]]; then Print_Internal_And_Exit \ --emph "${FUNCNAME[1]}" ' wrongly called: ' --emph 'Missing arguments' '.' elif [[ ! $1 =~ ^[0-9]+(.[0-9]+)*$ ]] || [[ ! $2 =~ ^[0-9]+(.[0-9]+)*$ ]]; then diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 35bb53d..5f6b588 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -166,6 +166,7 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() fi ;; prepare-scan) ;; + help) ;; # This is the default mode which is set in tests -> do nothing, but catch it *) Print_Internal_And_Exit 'Unknown execution mode passed to ' --emph "${FUNCNAME}" ' function.' ;; diff --git a/tests/unit_tests_Hydro_functionality.bash b/tests/unit_tests_Hydro_functionality.bash index 6035a1a..e65d024 100644 --- a/tests/unit_tests_Hydro_functionality.bash +++ b/tests/unit_tests_Hydro_functionality.bash @@ -24,7 +24,9 @@ function Make_Test_Preliminary_Operations__Hydro-create-input-file() HYBRID_output_directory="${HYBRIDT_folder_to_run_tests}/test_dir_Hydro" HYBRID_software_base_config_file[Hydro]='vhlle_config_cool' HYBRID_given_software_sections=('Hydro') - HYBRID_software_executable[Hydro]="$(which echo)" + # To test the generic case, create symbolic link to dummy executable + ln -s "$(which echo)" "${PWD}/dummy_exec" + HYBRID_software_executable[Hydro]="${PWD}/dummy_exec" Perform_Sanity_Checks_On_Provided_Input_And_Define_Auxiliary_Global_Variables } @@ -32,8 +34,6 @@ function Unit_Test__Hydro-create-input-file() { local -r ic_file="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" touch "${HYBRID_software_base_config_file[Hydro]}" - ln -s "$(which ls)" dummy_exec - HYBRID_software_executable[Hydro]="${HYBRIDT_folder_to_run_tests}/dummy_exec" mkdir 'eos' Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ ! -f "${HYBRID_software_configuration_file[Hydro]}" ]]; then @@ -78,7 +78,7 @@ function Unit_Test__Hydro-create-input-file() return 1 fi rm -r "${HYBRID_software_output_directory[Hydro]}"/* - ln -s "${HYBRIDT_folder_to_run_tests}/eos" "${HYBRID_software_output_directory[Hydro]}/eos" + ln -s "${PWD}/eos" "${HYBRID_software_output_directory[Hydro]}/eos" Call_Codebase_Function_In_Subshell Prepare_Software_Input_File_Hydro if [[ $? -ne 0 ]]; then Print_Error 'Preparation failed although the correct symlink exists.' @@ -95,7 +95,7 @@ function Unit_Test__Hydro-create-input-file() function Clean_Tests_Environment_For_Following_Test__Hydro-create-input-file() { - rm "${HYBRID_software_base_config_file[Hydro]}" + rm "${HYBRID_software_base_config_file[Hydro]}" 'dummy_exec' rm -r "${HYBRID_output_directory}" } @@ -144,7 +144,7 @@ function Unit_Test__Hydro-check-all-input() function Clean_Tests_Environment_For_Following_Test__Hydro-check-all-input() { - rm -r "${HYBRID_output_directory}" + rm -r "${HYBRID_output_directory}" 'dummy_exec' } function Make_Test_Preliminary_Operations__Hydro-test-run-software() diff --git a/tests/unit_tests_command_line_parser.bash b/tests/unit_tests_command_line_parser.bash index 621a886..0031a47 100644 --- a/tests/unit_tests_command_line_parser.bash +++ b/tests/unit_tests_command_line_parser.bash @@ -11,6 +11,7 @@ function Make_Test_Preliminary_Operations__parse-execution-mode() { local file_to_be_sourced list_of_files list_of_files=( + 'command_line_parsers/allowed_options.bash' 'command_line_parsers/main_parser.bash' 'global_variables.bash' ) @@ -110,7 +111,9 @@ function Unit_Test__parse-command-line-options() return 1 fi HYBRID_command_line_options_to_parse=(-o "${HOME}") + __static__Replace_Short_Options_With_Long_Ones __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_output_directory "${HOME}" || return 1 HYBRID_command_line_options_to_parse=(-c "${HOME}") # Here it does not matter we use a folder instead of a file + __static__Replace_Short_Options_With_Long_Ones __static__Test_Single_CLO_Parsing_In_Subshell HYBRID_configuration_file "${HOME}" || return 1 } diff --git a/tests/unit_tests_configuration.bash b/tests/unit_tests_configuration.bash index de9e8d3..3432488 100644 --- a/tests/unit_tests_configuration.bash +++ b/tests/unit_tests_configuration.bash @@ -38,7 +38,7 @@ function Make_Test_Preliminary_Operations__configuration-validate-YAML() function Unit_Test__configuration-validate-YAML() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml printf 'Scalar\nKey: Value\n' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then @@ -57,7 +57,7 @@ function Make_Test_Preliminary_Operations__configuration-validate-section-labels function Unit_Test__configuration-validate-section-labels() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml printf 'Invalid: Value\n' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File &> /dev/null if [[ $? -eq 0 ]]; then @@ -100,7 +100,7 @@ function Make_Test_Preliminary_Operations__configuration-validate-all-keys() function Unit_Test__configuration-validate-all-keys() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml printf ' Hybrid_handler: Try: 1 @@ -157,7 +157,7 @@ function Make_Test_Preliminary_Operations__configuration-validate-boolean-values function Unit_Test__configuration-validate-boolean-values() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml local value counter=0 for value in y Y yes Yes YES n N no No NO on On ON off Off OFF 0 1; do printf ' @@ -202,7 +202,7 @@ function Make_Test_Preliminary_Operations__configuration-parse-general-section() function Unit_Test__configuration-parse-general-section() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml printf 'Hybrid_handler: {}\nIC:\n Executable: foo\n' > "${HYBRID_configuration_file}" Call_Codebase_Function_In_Subshell Validate_And_Parse_Configuration_File if [[ $? -ne 0 ]]; then @@ -252,7 +252,7 @@ function Make_Test_Preliminary_Operations__configuration-parse-IC-section() function Unit_Test__configuration-parse-IC-section() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml printf ' IC: Executable: foo @@ -280,7 +280,7 @@ function Make_Test_Preliminary_Operations__configuration-parse-Hydro-section() function Unit_Test__configuration-parse-Hydro-section() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml printf ' Hydro: Executable: foo @@ -307,7 +307,7 @@ function Make_Test_Preliminary_Operations__configuration-parse-Sampler-section() function Unit_Test__configuration-parse-Sampler-section() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml printf ' Sampler: Executable: foo @@ -333,7 +333,7 @@ function Make_Test_Preliminary_Operations__configuration-parse-Afterburner-secti function Unit_Test__configuration-parse-Afterburner-section() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${PWD}/${FUNCNAME}.yaml printf ' Afterburner: Executable: foo diff --git a/tests/unit_tests_help_for_user.bash b/tests/unit_tests_help_for_user.bash index 571874c..9b3fe42 100644 --- a/tests/unit_tests_help_for_user.bash +++ b/tests/unit_tests_help_for_user.bash @@ -1,6 +1,6 @@ #=================================================== # -# Copyright (c) 2023 +# Copyright (c) 2023-2024 # SMASH Hybrid Team # # GNU General Public License (GPLv3 or later) @@ -9,7 +9,14 @@ function Make_Test_Preliminary_Operations__give-requested-help() { - source "${HYBRIDT_repository_top_level_path}/bash/command_line_parsers/helper.bash" || exit ${HYBRID_fatal_builtin} + local file_to_be_sourced list_of_files + list_of_files=( + 'command_line_parsers/allowed_options.bash' + 'command_line_parsers/helper.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done HYBRID_configuration_file='./config.yaml' HYBRID_output_directory='.' } diff --git a/tests/unit_tests_software_input_functionality.bash b/tests/unit_tests_software_input_functionality.bash index 2623872..7545b95 100644 --- a/tests/unit_tests_software_input_functionality.bash +++ b/tests/unit_tests_software_input_functionality.bash @@ -18,7 +18,7 @@ function Unit_Test__replace-in-software-input-YAML() # NOTE: The following variables must be named exactly so as the are used # by __static__Replace_Keys_Into_YAML_File function local base_input_file keys_to_be_replaced expected_result - base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + base_input_file=${PWD}/${FUNCNAME}.yaml #--------------------------------------------------------------------------- printf 'Scalar\nKey: Value\n' > "${base_input_file}" Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_YAML_File &> /dev/null @@ -90,7 +90,7 @@ function Unit_Test__replace-in-software-input-TXT() # NOTE: The following variables must be named exactly so as the are used # by __static__Replace_Keys_Into_Txt_File function local base_input_file keys_to_be_replaced expected_result - base_input_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + base_input_file=${PWD}/${FUNCNAME}.yaml #--------------------------------------------------------------------------- printf 'Key Value Extra-field\n' > "${base_input_file}" Call_Codebase_Function_In_Subshell __static__Replace_Keys_Into_Txt_File &> /dev/null From ab373ec9a231db46ecd17444f5479e2bbda6e457 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 14 Feb 2024 18:03:34 +0100 Subject: [PATCH 412/549] Fix wrong test on Scan_parameters key The test was wrong as the [*] expansion was concatenating the array elements eith spaces, i.e. with the first IFS character. Therefore the if-clause was always entered and all functional tests of the do execution mode failed. --- bash/sanity_checks.bash | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 5f6b588..3d8920b 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -159,11 +159,14 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() { case "${HYBRID_execution_mode}" in do) - if [[ "${HYBRID_scan_parameters[*]}" != '' ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Configuration key ' --emph 'Scan_parameters' ' can ONLY be specified in ' \ - --emph 'parameter-scan' ' execution mode.' - fi + local key + for key in "${!HYBRID_scan_parameters[@]}"; do + if [[ "${HYBRID_scan_parameters["${key}"]}" != '' ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'Configuration key ' --emph 'Scan_parameters' ' can ONLY be specified in ' \ + --emph 'parameter-scan' ' execution mode.' + fi + done ;; prepare-scan) ;; help) ;; # This is the default mode which is set in tests -> do nothing, but catch it From 3a8466ac5eae894494af1191fd31b92aa68de595 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Feb 2024 09:23:23 +0100 Subject: [PATCH 413/549] Define array as empty to avoid unboud variable access This array would remain unset if shfmt wasn't found and would trigger an error because of nounset mode. --- tests/unit_tests_formatting.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit_tests_formatting.bash b/tests/unit_tests_formatting.bash index be0e349..a887c76 100644 --- a/tests/unit_tests_formatting.bash +++ b/tests/unit_tests_formatting.bash @@ -29,6 +29,7 @@ function Unit_Test__codebase-formatting() continue fi done + files_with_wrong_formatting=() if [[ ${formatter_found} = 'TRUE' ]]; then # Quoting shfmt manual: # "If a given path is a directory, all shell scripts found under that directory will be used." From 57e93d8d1057a5e473fc784aa7b0d4478a640ff4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Feb 2024 09:25:11 +0100 Subject: [PATCH 414/549] User older yq syntax not to have to bump version requirement According to the yq release notes, the 'type' operator has been introduced in version 4.25.1 as alias of the older 'tag' one. Since we require version 4.18.1 using the new more descriptive alias would fail on older accepted versions and it was now replaced by the older one. Furthermore the yq short -r option was introduced in version 4.25.3 and the longer --unwrapScalar should be preferred to stay compatible with older versions. The releas notes can be found in the yq repository (at https://github.com/mikefarah/yq/blob/master/release_notes.txt). --- bash/scan_validation.bash | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index 42cbffd..dcfc975 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -34,7 +34,7 @@ function Validate_And_Store_Scan_Parameters() if [[ "${HYBRID_scan_parameters["${key}"]}" = '' ]]; then continue else - readarray -t parameters < <(yq -r '.[]' <<< "${HYBRID_scan_parameters["${key}"]}") + readarray -t parameters < <(yq --unwrapScalar '.[]' <<< "${HYBRID_scan_parameters["${key}"]}") for parameter in "${parameters[@]}"; do if ! __static__Is_Parameter_To_Be_Scanned \ "${parameter}" "${HYBRID_software_new_input_keys["${key}"]}"; then @@ -97,7 +97,7 @@ function __static__Is_Given_Key_Value_A_Valid_Scan() function __static__Check_If_Given_Scan_Is_A_YAML_Map() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan - if [[ $(yq '. | type' <<< "${given_scan}") != '!!map' ]]; then + if [[ $(yq '. | tag' <<< "${given_scan}") != '!!map' ]]; then Print_Error 'The given scan\n' --emph "${given_scan}" '\nis not a YAML map.' return 1 fi @@ -145,14 +145,14 @@ function __static__Has_Valid_Scan_Correct_Values() Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys case "${sorted_scan_keys}" in "[Values]") - if [[ $(yq '.Scan.Values | type' <<< "${given_scan}") != '!!seq' ]]; then + if [[ $(yq '.Scan.Values | tag' <<< "${given_scan}") != '!!seq' ]]; then Print_Error \ 'The value ' --emph "$(yq '.Scan.Values' <<< "${given_scan}")" \ ' of the ' --emph 'Values' ' key is not a list of parameter values.' return 1 fi local list_of_value_types - list_of_value_types=($(yq '.Scan.Values[] | type' <<< "${given_scan}" | sort -u)) + list_of_value_types=($(yq '.Scan.Values[] | tag' <<< "${given_scan}" | sort -u)) if [[ ${#list_of_value_types[@]} -ne 1 ]]; then Print_Error \ 'The parameter values have different YAML types: ' --emph "${list_of_value_types[*]//!!/}" '.' From ad89a2862d60a08457a6c48352f6c97fe73b4999 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Feb 2024 09:42:47 +0100 Subject: [PATCH 415/549] Add files diff output to test in case of failure --- tests/unit_tests_scan_files_operations.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index 03b17e2..29cd81c 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -63,7 +63,8 @@ EOF for file in "${list_of_files[@]}"; do if [[ "${file}" = "${HYBRID_scan_combinations_filename}" ]]; then if ! diff -q "${file}" '../ref_scan_combinations.dat' &> /dev/null; then - Print_Error 'Scan combinations file expected different.' + Print_Error 'Scan combinations file expected different.\n' + diff "${file}" '../ref_scan_combinations.dat' return 1 fi continue From 552e3feac5d32c79927f43cb220f176c0cc379b5 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 15 Feb 2024 10:23:46 +0100 Subject: [PATCH 416/549] Fix scan parameters names ordering when creating output --- bash/scan_files_operations.bash | 58 +++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 40195c7..8cb5a65 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -7,21 +7,47 @@ # #=================================================== +# NOTE: The parameters names and values are stored in 'list_of_parameters_values' +# which is an associative array. No key order is guaranteed, but it is +# important for reproducibility across different machines to fix some +# ordering. Here we create two normal arrays to sort names and have values +# in the same order. function Create_And_Populate_Scan_Folder() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values local -r \ parameters=("${!list_of_parameters_values[@]}") \ scan_combinations_file="${HYBRID_scan_directory}/${HYBRID_scan_combinations_filename}" - local parameters_combinations + local parameters_names parameters_values parameters_combinations + readarray -t parameters_names < <(__static__Get_Fixed_Order_Parameters) + readarray -t parameters_values < <(__static__Get_Fixed_Order_Parameters_Values) readarray -t parameters_combinations < \ - <(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${list_of_parameters_values[@]}") - readonly parameters_combinations + <(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${parameters_values[@]}") + readonly parameters_names parameters_values parameters_combinations __static__Validate_And_Create_Scan_Folder __static__Create_Combinations_File_With_Metadata_Header_Block __static__Create_Output_Files_In_Scan_Folder_And_Complete_Combinations_File } +function __static__Get_Fixed_Order_Parameters() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values + # Sort parameters according to stage: IC, Hydro, Sampler, Afterburner (then alphabetically) + local key + for key in 'IC' 'Hydro' 'Sampler' 'Afterburner'; do + printf '%s\n' "${!list_of_parameters_values[@]}" | grep "^${key}" | sort + done +} + +function __static__Get_Fixed_Order_Parameters_Values() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values parameters_names + local name + for name in "${parameters_names[@]}"; do + printf '%s\n' "${list_of_parameters_values["${name}"]}" + done +} + function __static__Get_Parameters_Combinations_For_New_Configuration_Files() { # NOTE: This is were multiple ways of doing combinations will be implemented: @@ -53,15 +79,14 @@ function __static__Validate_And_Create_Scan_Folder() function __static__Create_Combinations_File_With_Metadata_Header_Block() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters scan_combinations_file + Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters_names scan_combinations_file Internally_Ensure_Given_Files_Do_Not_Exist "${scan_combinations_file}" - local index { - for index in "${!parameters[@]}"; do - printf '# Parameter_%d: %s\n' $((index + 1)) "${parameters[index]}" + for index in "${!parameters_names[@]}"; do + printf '# Parameter_%d: %s\n' $((index + 1)) "${parameters_names[index]}" done printf '#\n#___Run' - for index in "${!parameters[@]}"; do + for index in "${!parameters_names[@]}"; do printf ' Parameter_%d' $((index + 1)) done printf '\n' @@ -70,8 +95,7 @@ function __static__Create_Combinations_File_With_Metadata_Header_Block() function __static__Create_Output_Files_In_Scan_Folder_And_Complete_Combinations_File() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty \ - list_of_parameters_values parameters parameters_combinations + Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters_combinations local -r number_of_files=${#parameters_combinations[@]} local id filename Print_Progress_Bar 0 ${number_of_files} @@ -116,15 +140,15 @@ function __static__Add_Line_To_Combinations_File() function __static__Create_Single_Output_File_In_Scan_Folder() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters filename + Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters_names filename local -r run_number=$(($1 + 1)) shift local -r set_of_values=("$@") Internally_Ensure_Given_Files_Do_Not_Exist "${filename}" __static__Add_Parameters_Comment_Line_To_New_Configuration_File local index yq_replacements - for ((index = 0; index < ${#parameters[@]}; index++)); do - yq_replacements+="( .${parameters[index]} ) = ${set_of_values[index]} |" + for index in ${!parameters_names[@]}; do + yq_replacements+="( .${parameters_names[index]} ) = ${set_of_values[index]} |" done yq "${yq_replacements%?}" "${HYBRID_configuration_file}" >> "${filename}" } @@ -132,13 +156,13 @@ function __static__Create_Single_Output_File_In_Scan_Folder() function __static__Add_Parameters_Comment_Line_To_New_Configuration_File() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty \ - parameters filename run_number set_of_values - local -r longest_parameter_length=$(wc -L < <(printf '%s\n' "${parameters[@]}")) + parameters_names filename run_number set_of_values + local -r longest_parameter_length=$(wc -L < <(printf '%s\n' "${parameters_names[@]}")) { printf '# Run %d of parameter scan "%s"\n#\n' "${run_number}" "${HYBRID_scan_directory}" local index - for index in "${!parameters[@]}"; do - printf '# %*s: %s\n' "${longest_parameter_length}" "${parameters[index]}" "${set_of_values[index]}" + for index in "${!parameters_names[@]}"; do + printf '# %*s: %s\n' "${longest_parameter_length}" "${parameters_names[index]}" "${set_of_values[index]}" done printf '\n' } > "${filename}" From 09ec7f63e9a76d151c0fc02625ae5b107d91ae15 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Feb 2024 14:31:21 +0100 Subject: [PATCH 417/549] Fix bug about using grep with no matches in errexit mode Grep exits with code 1 if there is no match and this was triggering a premature exit when sorting paramters. Now this problem is fixed. --- bash/scan_files_operations.bash | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 8cb5a65..637b9fb 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -33,9 +33,16 @@ function __static__Get_Fixed_Order_Parameters() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values # Sort parameters according to stage: IC, Hydro, Sampler, Afterburner (then alphabetically) - local key + # + # NOTE: Using 'grep' would fail and make the function exit if no match was found + # and therefore it is simply easier to loop over parameters here. + local key parameter for key in 'IC' 'Hydro' 'Sampler' 'Afterburner'; do - printf '%s\n' "${!list_of_parameters_values[@]}" | grep "^${key}" | sort + for parameter in "${!list_of_parameters_values[@]}"; do + if [[ ${parameter} = ${key}* ]]; then + printf "${parameter}\n" + fi + done | sort --ignore-case done } From 2978899e00a0e8b89905c1dd9d5ca9d7aaf55ec4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Feb 2024 14:32:50 +0100 Subject: [PATCH 418/549] Improve code respecting errexit also in process substitution --- bash/scan_files_operations.bash | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 637b9fb..772a3e9 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -12,17 +12,26 @@ # important for reproducibility across different machines to fix some # ordering. Here we create two normal arrays to sort names and have values # in the same order. +# +# ATTENTION: Here we use process substitution together with readarray to store data +# into arrays. As getting the exit code of process substitution can be +# tricky (https://unix.stackexchange.com/q/128560/370049) we adopt here +# a workaround which consists in storing the data at first in a variable +# using a standard command substitution. This will exit on error and +# no further error handling is needed. function Create_And_Populate_Scan_Folder() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values local -r \ parameters=("${!list_of_parameters_values[@]}") \ scan_combinations_file="${HYBRID_scan_directory}/${HYBRID_scan_combinations_filename}" - local parameters_names parameters_values parameters_combinations - readarray -t parameters_names < <(__static__Get_Fixed_Order_Parameters) - readarray -t parameters_values < <(__static__Get_Fixed_Order_Parameters_Values) - readarray -t parameters_combinations < \ - <(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${parameters_values[@]}") + local auxiliary_string parameters_names parameters_values parameters_combinations + auxiliary_string=$(__static__Get_Fixed_Order_Parameters) + readarray -t parameters_names < <(printf "${auxiliary_string}") + auxiliary_string=$(__static__Get_Fixed_Order_Parameters_Values) + readarray -t parameters_values < <(printf "${auxiliary_string}") + auxiliary_string=$(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${parameters_values[@]}") + readarray -t parameters_combinations < <(printf "${auxiliary_string}") readonly parameters_names parameters_values parameters_combinations __static__Validate_And_Create_Scan_Folder __static__Create_Combinations_File_With_Metadata_Header_Block From 01113e93e07fbe122a13632714b82ee92e8b4693 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Feb 2024 14:56:53 +0100 Subject: [PATCH 419/549] Remove Scan_parameters key from prepare-scan output files This was making it impossible to directly use the output of the prepare-scan mode in do mode and this is definitely not what was desired. This problem is fixed now. --- bash/scan_files_operations.bash | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 772a3e9..be2f8b2 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -162,11 +162,8 @@ function __static__Create_Single_Output_File_In_Scan_Folder() local -r set_of_values=("$@") Internally_Ensure_Given_Files_Do_Not_Exist "${filename}" __static__Add_Parameters_Comment_Line_To_New_Configuration_File - local index yq_replacements - for index in ${!parameters_names[@]}; do - yq_replacements+="( .${parameters_names[index]} ) = ${set_of_values[index]} |" - done - yq "${yq_replacements%?}" "${HYBRID_configuration_file}" >> "${filename}" + __static__Add_YAML_Configuration_To_New_Configuration_File + __static__Remove_Scan_Parameters_Key_From_New_Configuration_File } function __static__Add_Parameters_Comment_Line_To_New_Configuration_File() @@ -183,3 +180,19 @@ function __static__Add_Parameters_Comment_Line_To_New_Configuration_File() printf '\n' } > "${filename}" } + +function __static__Add_YAML_Configuration_To_New_Configuration_File() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters_names filename set_of_values + local index yq_replacements + for index in ${!parameters_names[@]}; do + yq_replacements+="( .${parameters_names[index]} ) = ${set_of_values[index]} |" + done + yq "${yq_replacements%?}" "${HYBRID_configuration_file}" >> "${filename}" +} + +function __static__Remove_Scan_Parameters_Key_From_New_Configuration_File() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty filename + sed -i '/Scan_parameters/d' "${filename}" +} From 31909626f7aef690e6554248c78c1a1c10a18359 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Feb 2024 15:01:39 +0100 Subject: [PATCH 420/549] Make sed regular expression a bit stricter This is just an improvement to stay more on the safe side. Now only lines starting with optional space and then 'Scan_parametes:' are removed, just to avoid in the future to remove lines with "Scan_parameters" as substring of some other string. --- bash/scan_files_operations.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index be2f8b2..4599736 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -194,5 +194,5 @@ function __static__Add_YAML_Configuration_To_New_Configuration_File() function __static__Remove_Scan_Parameters_Key_From_New_Configuration_File() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty filename - sed -i '/Scan_parameters/d' "${filename}" + sed -i '/^[[:space:]]*Scan_parameters:/d' "${filename}" } From d5692a2d07bcab1981889b5408a797b4072b14d4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 16 Feb 2024 15:25:10 +0100 Subject: [PATCH 421/549] Add note about overall performance of prepare-scan mode --- docs/developer/parameters_scan.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/developer/parameters_scan.md b/docs/developer/parameters_scan.md index 0255127..ec93488 100644 --- a/docs/developer/parameters_scan.md +++ b/docs/developer/parameters_scan.md @@ -27,6 +27,12 @@ graph LR +!!! note "Premature optimization is the root of all evil. - Donald Knuth" + The present implementation of the `prepare-scan` mode might appear not that clever, since many intermediate parsing and storing lead to e.g. iterating over the same arrays many times. + On the same line, probably some `yq` invocation might be spared by combining more operation in the same call. + However, this has been done on purpose trying to always prefer clearer code over _claimed_ speed in the very first implementation. + Out of the box, generating a scan with few hundreds parameters combinations takes few seconds on an average laptop and it is reasonable to believe that this is an acceptable performance. + ## Lack of multi-dimensional arrays In Bash it is not possible to have an array as entry of another array. From a65f91a9c99b9099f2d97d4971486b409ff6e078 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 19 Feb 2024 09:07:58 +0100 Subject: [PATCH 422/549] Fix logic error in parsing command line options for scan --- bash/command_line_parsers/main_parser.bash | 1 + bash/command_line_parsers/sub_parser.bash | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 134434c..03065a6 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -76,6 +76,7 @@ function Parse_Command_Line_Options() 'Specified output directory ' --emph "$2" ' not found' fi readonly HYBRID_output_directory="$(realpath "$2")" + readonly HYBRID_scan_directory="${HYBRID_output_directory}/$(basename "${HYBRID_scan_directory}")" fi shift 2 ;; diff --git a/bash/command_line_parsers/sub_parser.bash b/bash/command_line_parsers/sub_parser.bash index e157a8f..3e1cb0b 100644 --- a/bash/command_line_parsers/sub_parser.bash +++ b/bash/command_line_parsers/sub_parser.bash @@ -17,7 +17,8 @@ function Parse_Specific_Mode_Options_prepare-scan() if [[ ${2-} =~ ^(-|$) ]]; then Print_Option_Specification_Error_And_Exit "$1" else - readonly HYBRID_scan_directory="${HYBRID_output_directory}/$2" + # This will be set readonly later as parsing '-o' will change it + HYBRID_scan_directory="${HYBRID_output_directory}/$2" fi shift 2 ;; From 185c92ca113c8fe42e8cda52e7acfae524dd52f9 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 19 Feb 2024 09:29:34 +0100 Subject: [PATCH 423/549] Make examples about Scan_parameters key consistent --- docs/user/configuration_file.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user/configuration_file.md b/docs/user/configuration_file.md index bcbe8d2..fd6b920 100644 --- a/docs/user/configuration_file.md +++ b/docs/user/configuration_file.md @@ -63,14 +63,14 @@ However, **it is strongly encouraged to exclusively use absolute paths** as rela List of software input keys whose value is meant to be scanned. Each parameter has to be specified concatenating with a period all the keys as they would appear in the `Software_keys` map. - For example, if a software key reads + For example, software keys which read ```yaml Software_keys: foo: - bar: - baz: 42 + bar: 42 + baz: 666 ``` - then it would be specified in the `Scan_parameters` list as `"foo.bar.baz"`. + would be specified in the `Scan_parameters` list as `"foo.bar"` and `"foo.baz"`. Such a list is a YAML array and therefore it can be specified both in the compact and extended form. === "Compact form" From c0bf337a8cb62cafed728a2fa55e9c9f181fd910 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 19 Feb 2024 11:12:02 +0100 Subject: [PATCH 424/549] Add warning and cross link about Scan_parameters key --- docs/user/configuration_file.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/user/configuration_file.md b/docs/user/configuration_file.md index fd6b920..2ca3b58 100644 --- a/docs/user/configuration_file.md +++ b/docs/user/configuration_file.md @@ -87,6 +87,9 @@ However, **it is strongly encouraged to exclusively use absolute paths** as rela - "foo.baz" ``` + ??? warning "Each parameter requires a scan specification" + Parameters specified in the `Scan_parameters` list need to be accompanied by their scan values to be specified in the `Software_keys` section of the same stage [:material-arrow-right-box: the parameters scan syntax](scans_syntax.md). + ## The initial conditions section There is no specific key of the `IC` section and only the generic ones can be used. From 94e6aab81baa9cb684bf72645f131f59a4dfde98 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Mon, 19 Feb 2024 15:07:26 +0100 Subject: [PATCH 425/549] Add few comments and make scan validation more user friendly --- bash/command_line_parsers/main_parser.bash | 2 +- bash/scan_validation.bash | 15 ++++++++++++--- docs/user/scans_syntax.md | 2 +- tests/unit_tests_scan_validation.bash | 20 +++++++++++++------- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/bash/command_line_parsers/main_parser.bash b/bash/command_line_parsers/main_parser.bash index 03065a6..963ebd2 100644 --- a/bash/command_line_parsers/main_parser.bash +++ b/bash/command_line_parsers/main_parser.bash @@ -127,7 +127,7 @@ function __static__Validate_Command_Line_Options() if ! Element_In_Array_Equals_To "${option}" "${valid_options[@]}"; then exit_code=${HYBRID_fatal_command_line} Print_Fatal_And_Exit \ 'Option ' --emph "${option}" ' is not allowed in ' --emph "${HYBRID_execution_mode}" \ - ' execution mode!' 'Use the ' --emph '--help' ' option to get further information.' + ' execution mode!' 'Use the ' --emph '--help' ' mode option to get further information.' fi fi done diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index dcfc975..c0a7e4e 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -34,6 +34,9 @@ function Validate_And_Store_Scan_Parameters() if [[ "${HYBRID_scan_parameters["${key}"]}" = '' ]]; then continue else + # Here the --unwrapScalar option of yq is meant to strip quotes and colors to properly + # store the content in a bash array. The yq input is assumed to be a sequence, which + # is basically true by construction, see 'Format_Scan_Parameters_Lists' function. readarray -t parameters < <(yq --unwrapScalar '.[]' <<< "${HYBRID_scan_parameters["${key}"]}") for parameter in "${parameters[@]}"; do if ! __static__Is_Parameter_To_Be_Scanned \ @@ -153,10 +156,16 @@ function __static__Has_Valid_Scan_Correct_Values() fi local list_of_value_types list_of_value_types=($(yq '.Scan.Values[] | tag' <<< "${given_scan}" | sort -u)) + # If there is more than a type it is in general an error, unless there are exactly two, + # which are 'int' and 'float' (this is accepted). Note that the test is done on the + # concatenation of the values against 'float int' as the types are guaranteed to be sorted. if [[ ${#list_of_value_types[@]} -ne 1 ]]; then - Print_Error \ - 'The parameter values have different YAML types: ' --emph "${list_of_value_types[*]//!!/}" '.' - return 1 + if [[ ${#list_of_value_types[@]} -ne 2 || "${list_of_value_types[*]//!!/}" != 'float int' ]]; then + Print_Error \ + 'The parameter values have different YAML types: ' \ + --emph "${list_of_value_types[*]//!!/}" '.' + return 1 + fi elif [[ ! ${list_of_value_types[0]} =~ ^!!(bool|int|float)$ ]]; then Print_Error \ 'Parameter scans with values of ' --emph "${list_of_value_types[0]//!!/}" \ diff --git a/docs/user/scans_syntax.md b/docs/user/scans_syntax.md index 86a8911..42ffde5 100644 --- a/docs/user/scans_syntax.md +++ b/docs/user/scans_syntax.md @@ -6,7 +6,7 @@ In order to properly run the hybrid handler in `prepare-scan` mode, the configur Each parameter which must be scanned has to be declared so by using the `Scan_parameters` key in the corresponding stage section [:material-arrow-right-box: key description](configuration_file.md#scan-parameters). **If a parameter is not declared to be scanned** and is specified as a scan in the `Software_keys` section, this will probably not be caught by the hybrid handler and **the produced configurations are likely to be wrong**. -!!! warning "Scanning only numerical parameters is possible" +!!! warning "Only scanning numerical parameters is possible" At the moment, it is not possible to scan parameters whose value is not numerical. More precisely, only integer, float or boolean YAML types are accepted. Feel free to open an issue if this is a too strong restriction for you. diff --git a/tests/unit_tests_scan_validation.bash b/tests/unit_tests_scan_validation.bash index 1f57569..6708988 100644 --- a/tests/unit_tests_scan_validation.bash +++ b/tests/unit_tests_scan_validation.bash @@ -62,7 +62,6 @@ function Unit_Test__scan-YAML-scan-syntax() '{Scan: {Values: True}}' '{Scan: {Values: 42}}' '{Scan: {Values: [42, False, 3.14]}}' - '{Scan: {Values: [42, 3.14]}}' '{Scan: {Values: ["Hi", "Bye"]}}' ) for value in "${values[@]}"; do @@ -72,12 +71,19 @@ function Unit_Test__scan-YAML-scan-syntax() return 1 fi done - value='{Scan: {Values: [1,2,3]}}' - Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" - if [[ $? -ne 0 ]]; then - Print_Error 'Scan syntax validation unexpectedly failed (' --emph "${value}" ').' - return 1 - fi + values=( + '{Scan: {Values: [True, False]}}' + '{Scan: {Values: [1,2,3]}}' + '{Scan: {Values: [42, 3.14]}}' + '{Scan: {Values: [3.14, 6.28]}}' + ) + for value in "${values[@]}"; do + Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" + if [[ $? -ne 0 ]]; then + Print_Error 'Scan syntax validation unexpectedly failed (' --emph "${value}" ').' + return 1 + fi + done } function Make_Test_Preliminary_Operations__scan-single-validation() From 9c332acbf2fb87c004a6948d4c569e8e7c5a179d Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 20 Feb 2024 10:15:16 +0100 Subject: [PATCH 426/549] Fix brace expansion for single value list --- bash/scan_files_operations.bash | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 4599736..7679d34 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -77,8 +77,13 @@ function __static__Get_All_Parameters_Combinations() { local values string_to_be_expanded for values in "$@"; do - # Get rid of spaces and square brackets and prepare brace expansion - string_to_be_expanded+="{${values//[][ ]/}}_" + # Get rid of spaces and square brackets and prepare brace expansion. If there + # is only one value, do not add braces as no expansion has to happen. + if [[ "${values}" = *,* ]]; then + string_to_be_expanded+="{${values//[][ ]/}}_" + else + string_to_be_expanded+="${values//[][ ]/}_" + fi done # NOTE: The following use of 'eval' is fine since the string that is expanded # is guaranteed to be validated to contain only YAML int, bool or float. @@ -188,6 +193,7 @@ function __static__Add_YAML_Configuration_To_New_Configuration_File() for index in ${!parameters_names[@]}; do yq_replacements+="( .${parameters_names[index]} ) = ${set_of_values[index]} |" done + echo "${yq_replacements}" yq "${yq_replacements%?}" "${HYBRID_configuration_file}" >> "${filename}" } From 655f7353b03086745bc316cdc451bd48f971fe94 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Tue, 20 Feb 2024 10:49:16 +0100 Subject: [PATCH 427/549] Add couple of admonitions and remove forgotten debug output --- bash/scan_files_operations.bash | 1 - docs/user/execution_modes.md | 3 +++ docs/user/scans_types.md | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 7679d34..4d8fdb2 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -193,7 +193,6 @@ function __static__Add_YAML_Configuration_To_New_Configuration_File() for index in ${!parameters_names[@]}; do yq_replacements+="( .${parameters_names[index]} ) = ${set_of_values[index]} |" done - echo "${yq_replacements}" yq "${yq_replacements%?}" "${HYBRID_configuration_file}" >> "${filename}" } diff --git a/docs/user/execution_modes.md b/docs/user/execution_modes.md index 93c3d4e..2b8591b 100644 --- a/docs/user/execution_modes.md +++ b/docs/user/execution_modes.md @@ -99,3 +99,6 @@ For example, using the default generic `scan` name, the user will obtain the fol For reproducibility reasons as well as to keep track that they belong to a scan, the scan name and parameters are printed in the beginning in commented lines. These files will have leading zeroes in the run index contained in the filename, depending on the total number of prepared simulations. This allows to keep them easily sorted. + +!!! warning "Good to know" + If you use the `--output-directory` in `prepare-scan` mode to customize the output directory, you will need to specify this command line option again later if you use the `do` mode and want to store the output of the simulations in the same folder. diff --git a/docs/user/scans_types.md b/docs/user/scans_types.md index 7196e85..87f9c85 100644 --- a/docs/user/scans_types.md +++ b/docs/user/scans_types.md @@ -57,3 +57,7 @@ For example, specifying two different scan parameters with 2 and 5 values, respe 9 666 True 10 666 False ``` + +??? question "What happens if I provide a single value for a scan parameter?" + If you provide a single-value list to `Values`, this will be accepted by the hybrid handler and the provided value will be considered in all combinations. + If this happen to be the only provided scan parameter, a single configuration file will be created together with a basically useless single-combination file. :sweat_smile: From d647e0ca4574656af0e819a1aa6a1645727070e7 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 23 Feb 2024 14:25:54 +0100 Subject: [PATCH 428/549] Fix unit test broken by accident in fixing rebase conflicts --- bash/sanity_checks.bash | 1 - tests/unit_tests_software_operations.bash | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 3d8920b..0d2ba88 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -48,7 +48,6 @@ function Perform_Internal_Sanity_Checks() "${HYBRID_software_base_config_file[@]}" } - function Ensure_Consistency_Of_Afterburner_Input() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty 'HYBRID_software_input_file[Afterburner]' diff --git a/tests/unit_tests_software_operations.bash b/tests/unit_tests_software_operations.bash index 38aa64f..1fd6ef9 100644 --- a/tests/unit_tests_software_operations.bash +++ b/tests/unit_tests_software_operations.bash @@ -9,7 +9,7 @@ function Unit_Test__copy-hybrid-handler-config-section() { - HYBRID_configuration_file=${HYBRIDT_folder_to_run_tests}/${FUNCNAME}.yaml + HYBRID_configuration_file=${FUNCNAME}.yaml # Avoid empty lines in the beginning in this test as yq behavior # might change with different versions (here we compare strings) printf '%s\n' \ @@ -23,9 +23,10 @@ function Unit_Test__copy-hybrid-handler-config-section() ' Config_file: confh' > "${HYBRID_configuration_file}" local -r git_description="$(git -C "${HYBRIDT_folder_to_run_tests}" describe --long --always --all)" local folder description expected_result + Print_Info "${HYBRID_handler_config_section_filename[IC]}" for folder in "${HYBRIDT_folder_to_run_tests}" ~; do - Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section 'IC' \ - "${HYBRIDT_folder_to_run_tests}" "${folder}" #&> /dev/null + Call_Codebase_Function_In_Subshell Copy_Hybrid_Handler_Config_Section \ + 'IC' "." "${folder}" &> /dev/null if [[ "${folder}" = "${HYBRIDT_folder_to_run_tests}" ]]; then description="${git_description}" else From f1745cbc939c868ba93a243b1c40225c1b1bbf56 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 23 Feb 2024 15:02:07 +0100 Subject: [PATCH 429/549] Remove inaccurate comment about Hydro function --- bash/Hydro_functionality.bash | 3 --- 1 file changed, 3 deletions(-) diff --git a/bash/Hydro_functionality.bash b/bash/Hydro_functionality.bash index 98bf3f8..5127228 100644 --- a/bash/Hydro_functionality.bash +++ b/bash/Hydro_functionality.bash @@ -50,9 +50,6 @@ function Run_Software_Hydro() #=============================================================================== -# NOTE: The IC file is assumed to exist here (its existence is checked later). -# If the file exists we will just use it; if it exists as a broken link -# we overwrite it with 'ln -f'. function __static__Create_Symbolic_Link_To_IC_File() { local -r target_link_name="${HYBRID_software_output_directory[Hydro]}/SMASH_IC.dat" From 86b7bbce4b3a2261a0237e45eb24944cf33e183a Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 28 Feb 2024 13:43:42 +0100 Subject: [PATCH 430/549] Fix gitignore file to exclude build* folders only --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 648a269..6237d7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build* +build*/ binaries python_scripts/*.pyc From 1425e3ee168b983eceeddccf2594267f6a5a9da6 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 23 Feb 2024 16:44:45 +0100 Subject: [PATCH 431/549] Restructure developer guide homepage and mention assumptions --- docs/developer/building_docs.md | 71 +++++++++++++++ .../{overview.md => contributing.md} | 0 docs/developer/index.md | 86 ++++++++----------- mkdocs.yml | 3 +- 4 files changed, 108 insertions(+), 52 deletions(-) create mode 100644 docs/developer/building_docs.md rename docs/developer/{overview.md => contributing.md} (100%) diff --git a/docs/developer/building_docs.md b/docs/developer/building_docs.md new file mode 100644 index 0000000..9aef972 --- /dev/null +++ b/docs/developer/building_docs.md @@ -0,0 +1,71 @@ +# Building the documentation + +The documentation is built using [MkDocs](https://www.mkdocs.org) and in particular the impressive [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme. +All documentation files and related material is located in :file_folder: ***docs***. +Therefore it is hosted in the codebase and it naturally evolves together with it. + +## Prerequisite + +Few packages are required and all can be installed using `pip`: +```bash +pip install mkdocs +pip install mkdocs-material +pip install mike +``` + +You can refer to the corresponding installation pages for further information (1). +{ .annotate } + +1. :fontawesome-solid-book: [MkDocs](https://www.mkdocs.org/user-guide/installation/)  + :simple-materialformkdocs: [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/getting-started/#installation)  :material-bike-fast: [mike](https://github.com/jimporter/mike#installation) + +## Serving, building and deploying + +The deployed version is hosted on the :octicons-git-branch-16: `gh-pages` branch, while documentation from any other branch can be comfortably visualized in a browser opening up [http://127.0.0.1:8000/](http://127.0.0.1:8000/) after having run `mkdocs serve` in a terminal from the top-level repository folder. + +The documentation website can also be locally built by running `mkdocs build`, which will create a :file_folder: ***site*** folder containing the website source code. + +Once changes to the website have been locally checked, they are ready to be deployed. +We use `mike` to deploy documentation, because it allows to make different documentation versions coexist and be easily selected. +Our usage of this tool is quite basic, but you can check out [mike's documentation](https://github.com/jimporter/mike) to know more about it. + +!!! warning "Be aware of few caveats" + + Even if we use `mike` to deploy documentation, the same warnings about the [MkDocs deployment](https://www.mkdocs.org/user-guide/deploying-your-docs/) feature apply: + + 1. Be aware that you will not be able to review the built site before it is pushed to GitHub. Therefore, you may want to verify any changes you make to the docs beforehand by using the build or serve commands and reviewing the built files locally. + 2. You should never edit files in your pages repository by hand if you're using the gh-deploy command because you will lose your work the next time you run the command. + 3. If there are untracked files or uncommitted work in the local repository where `mkdocs gh-deploy` is run, these will be included in the pages that are deployed. + + +### Our deployment scheme + +The documentation does not store one version for _all_ versions of the codebase. +In particular, releases like a bug-fix which only change the `patch` number in the version string won't have a new documentation version associated. +To say it differently, the documentation of version `X.Y` will always show the documentation of the corresponding highest patch. + +#### At new releases + +When a new release of the codebase is done, a new version of the documentation should be built and deployed. +This can be easily done via +``` +mike deploy --push --update-aliases X.Y latest +``` +where `X.Y` are the `major.minor` version numbers of the release. +The command will also update the `latest` alias to point to the new release documentation. + +??? tip "Good to know" + Omitting the `--push` option, nothing will be pushed and you have the possibility to check the changes on the `gh-pages` branch, which must be then manually pushed to publish the changes. + +#### At new patches + +When a new patch is released, it is usually worth adjusting the documentation title via `make retitle --push X.Y `, where new title might simply be `X.Y.Z`, i.e. the complete new version string. + +#### The development documentation + +Documentation between releases will naturally evolve and, whenever it makes sense, changes to the documentation should be deployed, too. +For this purpose, developers should run +``` +mike deploy --push develop +``` +reasonably often, since this documentation is thought to reflect the state of the :octicons-git-branch-16: `develop` branch. diff --git a/docs/developer/overview.md b/docs/developer/contributing.md similarity index 100% rename from docs/developer/overview.md rename to docs/developer/contributing.md diff --git a/docs/developer/index.md b/docs/developer/index.md index 9aef972..e76dc7e 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -1,71 +1,55 @@ -# Building the documentation +--- +hide: + - navigation + - toc +--- -The documentation is built using [MkDocs](https://www.mkdocs.org) and in particular the impressive [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme. -All documentation files and related material is located in :file_folder: ***docs***. -Therefore it is hosted in the codebase and it naturally evolves together with it. +# From great power comes great responsibility -## Prerequisite +The hybrid handler has been developed trying to make the entry point of a newbie developer as low as possible. +Although :simple-gnubash: **Bash** can be sometimes not that simple to read[^1], the integration-operation segregation principle ([IOSP](https://clean-code-developer.com/grades/grade-1-red/)) has been used most of the times at the the top-level to allow the reader to get a first understanding of what the main functions are doing, having then the possibility to deepen into lower levels if needed. +Said it differently, reading the code top-down should be straightforward, as the most top-level function is a series of function calls only, whose names should clearly describe what it is done. -Few packages are required and all can be installed using `pip`: -```bash -pip install mkdocs -pip install mkdocs-material -pip install mike -``` +Therefore, this guide is not meant to document any possible implementation detail, but rather provide the reader with important information, which might be difficult to grasp by reading the codebase. +Assumptions that are crucial to know before even getting to the code have a dedicated card here in the following. -You can refer to the corresponding installation pages for further information (1). -{ .annotate } +--- -1. :fontawesome-solid-book: [MkDocs](https://www.mkdocs.org/user-guide/installation/)  - :simple-materialformkdocs: [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/getting-started/#installation)  :material-bike-fast: [mike](https://github.com/jimporter/mike#installation) +[^1]: + Alessandro has offered many times a Bash crash-course and the full material is available on his GitHub profile :material-arrow-right-box: [:material-github: AxelKrypton](https://github.com/AxelKrypton/Bash-lecture). -## Serving, building and deploying +
-The deployed version is hosted on the :octicons-git-branch-16: `gh-pages` branch, while documentation from any other branch can be comfortably visualized in a browser opening up [http://127.0.0.1:8000/](http://127.0.0.1:8000/) after having run `mkdocs serve` in a terminal from the top-level repository folder. +!!! danger "No period or space in YAML keys" + The full implementation relies on the assumption that YAML keys contain neither spaces nor periods. + This is basically a design decision for this project and also a sensible thing to avoid (it would require another level of quoting when using `yq`). + The developer has to be aware that the code does break, if such an assumption is violated. + Although the external software used for each stage might violate this, it is not the case at the moment and it is considered unlikely to happen in the future. -The documentation website can also be locally built by running `mkdocs build`, which will create a :file_folder: ***site*** folder containing the website source code. +- :arrow_forward:{ .lg .middle }   __Building the documentation__ -Once changes to the website have been locally checked, they are ready to be deployed. -We use `mike` to deploy documentation, because it allows to make different documentation versions coexist and be easily selected. -Our usage of this tool is quite basic, but you can check out [mike's documentation](https://github.com/jimporter/mike) to know more about it. + --- -!!! warning "Be aware of few caveats" + Especially when changing it, it is important to locally build the documentation and see how it looks. + This is made trivial by MkDocs which also helps deploying it at every release. - Even if we use `mike` to deploy documentation, the same warnings about the [MkDocs deployment](https://www.mkdocs.org/user-guide/deploying-your-docs/) feature apply: + [:material-arrow-right-box:  Check it out!](building_docs.md) - 1. Be aware that you will not be able to review the built site before it is pushed to GitHub. Therefore, you may want to verify any changes you make to the docs beforehand by using the build or serve commands and reviewing the built files locally. - 2. You should never edit files in your pages repository by hand if you're using the gh-deploy command because you will lose your work the next time you run the command. - 3. If there are untracked files or uncommitted work in the local repository where `mkdocs gh-deploy` is run, these will be included in the pages that are deployed. +- :computer:{ .lg .middle }   __Ready to code?__ + --- -### Our deployment scheme + Not so quick. + Every project has its contributing rules and you are kindly requested to go over them at least once before starting. -The documentation does not store one version for _all_ versions of the codebase. -In particular, releases like a bug-fix which only change the `patch` number in the version string won't have a new documentation version associated. -To say it differently, the documentation of version `X.Y` will always show the documentation of the corresponding highest patch. + [:material-arrow-right-box:  Read more](contributing.md) -#### At new releases +- :material-barcode-scan:{ .lg .middle }   __Design of the parameter scan__ -When a new release of the codebase is done, a new version of the documentation should be built and deployed. -This can be easily done via -``` -mike deploy --push --update-aliases X.Y latest -``` -where `X.Y` are the `major.minor` version numbers of the release. -The command will also update the `latest` alias to point to the new release documentation. + --- -??? tip "Good to know" - Omitting the `--push` option, nothing will be pushed and you have the possibility to check the changes on the `gh-pages` branch, which must be then manually pushed to publish the changes. + Since the `prepare-scan` execution mode implementation is not trivial and it required a couple of design decisions, it has been decided to comment on each of these in a dedicated page. -#### At new patches + [:material-arrow-right-box:  Read more](parameters_scan.md) -When a new patch is released, it is usually worth adjusting the documentation title via `make retitle --push X.Y `, where new title might simply be `X.Y.Z`, i.e. the complete new version string. - -#### The development documentation - -Documentation between releases will naturally evolve and, whenever it makes sense, changes to the documentation should be deployed, too. -For this purpose, developers should run -``` -mike deploy --push develop -``` -reasonably often, since this documentation is thought to reflect the state of the :octicons-git-branch-16: `develop` branch. +
diff --git a/mkdocs.yml b/mkdocs.yml index 73edc85..dd8ce56 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,7 +31,8 @@ nav: - Types of scans: user/scans_types.md - Developer Guide: - developer/index.md - - Overview: developer/overview.md + - Building the documentation: developer/building_docs.md + - Contributing: developer/contributing.md - Parameters scan: developer/parameters_scan.md markdown_extensions: From ac52946de387c88296293f868fda579e7c2d8a08 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Fri, 23 Feb 2024 16:45:25 +0100 Subject: [PATCH 432/549] Make utilities interface more flexible and simplify code The YAML utility functions consistently rely on the assumption that no YAML key contains spaces or periods. Because of that, they were adjusted to be callable either with single keys as arguments, or with all keys concatenated with periods or a mix of both. Code that required a period replacement and a split to stick to the old interface has been simplified. --- bash/scan_validation.bash | 10 +++------- bash/utility_functions.bash | 21 ++++++++++++--------- tests/unit_tests_utility_functions.bash | 12 ++++++------ 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index c0a7e4e..a7fd2ee 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -19,10 +19,6 @@ function Format_Scan_Parameters_Lists() readonly HYBRID_scan_parameters } -# NOTE: In the following functions the parameter(s) to be scanned are stored as a period-separated -# list of keys to navigate the YAML tree. However, the utility functions are general and needs -# keys as separate arguments. Hence we let word-splitting help us when calling the function. -# By that it is also assumed that there are no spaces in keys (a kind of design decision). function Validate_And_Store_Scan_Parameters() { Ensure_That_Given_Variables_Are_Set list_of_parameters_values @@ -45,7 +41,7 @@ function Validate_And_Store_Scan_Parameters() else parameter_value=$( Read_From_YAML_String_Given_Key \ - "${HYBRID_software_new_input_keys["${key}"]}" ${parameter//./ } + "${HYBRID_software_new_input_keys["${key}"]}" "${parameter}" ) # Get rid of YAML top-level 'Scan' key list_of_parameters_values["${key}.Software_keys.${parameter}"]=$( @@ -69,14 +65,14 @@ function Validate_And_Store_Scan_Parameters() function __static__Is_Parameter_To_Be_Scanned() { local -r key="$1" yaml_section="$2" - if ! Has_YAML_String_Given_Key "${yaml_section}" ${key//./ }; then + if ! Has_YAML_String_Given_Key "${yaml_section}" "${key}"; then Print_Error \ 'The ' --emph "${key}" ' parameter is asked to be scanned but its value' \ 'was not specified in the corresponding ' --emph 'Software_keys' '.' return 1 fi local parameter_value - parameter_value=$(Read_From_YAML_String_Given_Key "${yaml_section}" ${key//./ }) + parameter_value=$(Read_From_YAML_String_Given_Key "${yaml_section}" "${key}") if ! __static__Is_Given_Key_Value_A_Valid_Scan "${parameter_value}"; then Print_Error -l -- 'The ' --emph "${key}" \ ' parameter is asked to be scanned but its value was not properly specified as a scan.' diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index adc39f6..42568e7 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -25,9 +25,11 @@ function Element_In_Array_Matches() return 1 } -# NOTE: This function needs to be called with the YAML string as first argument -# and the section key(s) as remaining argument(s). If YAML is invalid, -# an error is printed and the function exits. +# NOTE: This function needs to be called with the YAML string as first argument and the +# section key(s) as remaining argument(s). As it is assumed everywhere that no key +# contains a period (or a space), keys can be passed to this function also already +# concatenated (or in a mixed way). +# If YAML is invalid, an error is printed and the function exits. function Has_YAML_String_Given_Key() { local yaml_string section key @@ -39,6 +41,9 @@ function Has_YAML_String_Given_Key() if ! yq <<< "${yaml_string}" &> /dev/null; then Print_Internal_And_Exit 'Function ' --emph "${FUNCNAME}" ' called with invalid YAML string.' fi + # Reset parameters letting word splitting split keys after having replaced periods by spaces. + # This is needed to correctly deal with any possible way keys are passed to this function. + set -- ${@//./ } section="$(printf '.%s' "${@:1:$#-1}")" # All arguments but last key=${@: -1} # Last argument if [[ $(yq "${section}"' | has("'"${key}"'")' <<< "${yaml_string}") = 'true' ]]; then @@ -48,9 +53,8 @@ function Has_YAML_String_Given_Key() fi } -# NOTE: This function needs to be called with the YAML string as first argument -# and the section key(s) as remaining argument(s). If YAML does not contain -# the key (or it is invalid) the function exits with an error. +# NOTE: This function must be called like 'Has_YAML_String_Given_Key'. +# If YAML does not contain the key (or it is invalid) the function exits with an error. function Read_From_YAML_String_Given_Key() { local yaml_string key @@ -65,9 +69,8 @@ function Read_From_YAML_String_Given_Key() yq "${key}" <<< "${yaml_string}" } -# NOTE: This function needs to be called with the YAML string as first argument -# and the section key(s) as remaining argument(s). If YAML does not contain -# the key (or it is invalid) the function exits with an error. +# NOTE: This function must be called like 'Has_YAML_String_Given_Key'. +# If YAML does not contain the key (or it is invalid) the function exits with an error. function Print_YAML_String_Without_Given_Key() { local yaml_string key diff --git a/tests/unit_tests_utility_functions.bash b/tests/unit_tests_utility_functions.bash index 5342b90..08e1009 100644 --- a/tests/unit_tests_utility_functions.bash +++ b/tests/unit_tests_utility_functions.bash @@ -21,12 +21,12 @@ function Unit_Test__utility-has-YAML-string-given-key() Print_Error "Function called on invalid YAML succeeded." return 1 fi - Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c' &> /dev/null + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a.b' 'c' &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Existing key ' --emph '{a: {b: {c:}}}' ' not found.' return 1 fi - Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' &> /dev/null + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a.b' &> /dev/null if [[ $? -ne 0 ]]; then Print_Error 'Existing key ' --emph '{a: {b:}}' ' not found.' return 1 @@ -36,7 +36,7 @@ function Unit_Test__utility-has-YAML-string-given-key() Print_Error 'Existing key ' --emph '{a:}' ' not found.' return 1 fi - Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'nope' &> /dev/null + Call_Codebase_Function_In_Subshell Has_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a.b.nope' &> /dev/null if [[ $? -eq 0 ]]; then Print_Error "Not existing key found." return 1 @@ -61,12 +61,12 @@ function Unit_Test__utility-read-from-YAML-string-given-key() return 1 fi local result - result=$(Call_Codebase_Function Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b' 'c') + result=$(Call_Codebase_Function Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a.b' 'c') if [[ ${result} -ne 42 ]]; then Print_Error "Reading scalar key failed." return 1 fi - result=$(Call_Codebase_Function Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a' 'b') + result=$(Call_Codebase_Function Read_From_YAML_String_Given_Key $'a:\n b:\n c: 42\n' 'a.b') if [[ "${result}" != 'c: 42' ]]; then Print_Error "Reading map key failed." return 1 @@ -96,7 +96,7 @@ function Unit_Test__utility-print-YAML-string-without-given-key() Print_Error "Deleting scalar key failed." return 1 fi - result=$(Call_Codebase_Function Print_YAML_String_Without_Given_Key $'a:\n b:\n c: 17\n' 'a' 'b') + result=$(Call_Codebase_Function Print_YAML_String_Without_Given_Key $'a:\n b:\n c: 17\n' 'a.b') if [[ "${result}" != 'a: {}' ]]; then Print_Error "Deleting map key failed." return 1 From e374b1655a2dee5d59f19d12b0892f687b2fa6a3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 29 Feb 2024 15:58:15 +0100 Subject: [PATCH 433/549] Adjust one documentation sentence --- docs/developer/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer/index.md b/docs/developer/index.md index e76dc7e..f6744e9 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -8,7 +8,7 @@ hide: The hybrid handler has been developed trying to make the entry point of a newbie developer as low as possible. Although :simple-gnubash: **Bash** can be sometimes not that simple to read[^1], the integration-operation segregation principle ([IOSP](https://clean-code-developer.com/grades/grade-1-red/)) has been used most of the times at the the top-level to allow the reader to get a first understanding of what the main functions are doing, having then the possibility to deepen into lower levels if needed. -Said it differently, reading the code top-down should be straightforward, as the most top-level function is a series of function calls only, whose names should clearly describe what it is done. +Said differently, reading the code top-down should be straightforward, as the most top-level function is a series of function calls only, whose names should clearly describe what is done. Therefore, this guide is not meant to document any possible implementation detail, but rather provide the reader with important information, which might be difficult to grasp by reading the codebase. Assumptions that are crucial to know before even getting to the code have a dedicated card here in the following. From e6360f27ce331f92ab5db6ff95995aa3142e9685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Wed, 21 Feb 2024 16:09:59 +0100 Subject: [PATCH 434/549] Add first modifications and validations for LHS scan --- bash/global_variables.bash | 3 ++ bash/scan_validation.bash | 40 +++++++++++++++++ bash/scan_values_operations.bash | 77 ++++++++++++++++++++++++++------ 3 files changed, 107 insertions(+), 13 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index e87f534..f098420 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -61,6 +61,7 @@ function Define_Further_Global_Variables() # ATTENTION: The keys inside each YAML list must be alphabetically sorted to allow # the validation mechanism to work! readonly HYBRID_valid_scan_specification_keys=( + '[Range]' '[Values]' ) readonly HYBRID_scan_combinations_filename='scan_combinations.dat' @@ -68,6 +69,7 @@ function Define_Further_Global_Variables() # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=( [Run_ID]='HYBRID_run_id' + [Scan_strategy]='HYBRID_scan_strategy' ) declare -rgA HYBRID_ic_valid_keys=( [Executable]='HYBRID_software_executable[IC]' @@ -110,6 +112,7 @@ function Define_Further_Global_Variables() HYBRID_scan_directory="${HYBRID_output_directory}/scan" # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_run_id="Run_$(date +'%Y-%m-%d_%H%M%S')" + HYBRID_scan_strategy='Combinations' HYBRID_given_software_sections=() declare -gA HYBRID_software_executable=( [IC]='' diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index a7fd2ee..6cce2d6 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -169,6 +169,46 @@ function __static__Has_Valid_Scan_Correct_Values() return 1 fi ;; + "[Range]") + if [[ $(yq '.Scan.Range | tag' <<< "${given_scan}") != '!!seq' ]]; then + Print_Error \ + 'The value ' --emph "$(yq '.Scan.Range' <<< "${given_scan}")" \ + ' of the ' --emph 'Values' ' key is not a list of parameter values.' + return 1 + fi + local list_of_value_types num_values first_value second_value + list_of_value_types=($(yq '.Scan.Range[] | tag' <<< "${given_scan}" | sort -u)) + # If there is more than a type it is in general an error, unless there are exactly two, + # which are 'int' and 'float' (this is accepted). Note that the test is done on the + # concatenation of the values against 'float int' as the types are guaranteed to be sorted. + if [[ ${#list_of_value_types[@]} -ne 1 ]]; then + if [[ ${#list_of_value_types[@]} -ne 2 || "${list_of_value_types[*]//!!/}" != 'float int' ]]; then + Print_Error \ + 'The parameter values have different YAML types: ' \ + --emph "${list_of_value_types[*]//!!/}" '.' + return 1 + fi + elif [[ ! ${list_of_value_types[0]} =~ ^!!(bool|int|float)$ ]]; then + Print_Error \ + 'Parameter scans with values of ' --emph "${list_of_value_types[0]//!!/}" \ + ' type are not allowed.' 'Valid parameter types are ' --emph 'bool int float' ', only.' + return 1 + fi + num_values=$(yq '.Scan.Range | length' <<< "${given_scan}") + if (( num_values != 2 )); then + Print_Error \ + 'Exactly two values are expected for the range. '\ + 'The given range is ' --emph "${num_values}" ' long.' + return 1 + fi + first_value=$(yq '.Scan.Range[0]' <<< "${given_scan}") + second_value=$(yq '.Scan.Range[1]' <<< "${given_scan}") + if [[ $(awk 'BEGIN{print ('$first_value' < '$second_value') ? "true" : "false"}') == "false" ]]; then + Print_Error \ + 'The first value must be smaller than the second value in the Range. ' + return 1 + fi + ;; *) Print_Internal_And_Exit \ 'Unknown scan passed to ' --emph "${FUNCNAME}" ' function.' diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index c5d8854..da82e34 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -14,19 +14,26 @@ function Create_List_Of_Parameters_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values - if false; then - # This is where the Latin Hypercube Sampling algorithm has to be called - # in order to call the Python script and use its output to fill the - # 'list_of_parameters_values' array, e.g. in a 'while read' loop. - # The if-clause above should be on a new boolean key to be given in the - # generic Hybrid_handler section. - : - else - local parameter - for parameter in "${!list_of_parameters_values[@]}"; do - __static__Generate_And_Store_Parameter_List_Of_Values "${parameter}" - done - fi + echo "${list_of_parameters_values[@]}" + case "${HYBRID_scan_strategy}" in + 'LHS') + local parameter + for parameter in "${!list_of_parameters_values[@]}"; do + __static__Generate_And_Store_Parameter_List_Of_Ranges "${parameter}" + done + ;; + 'Combinations') + local parameter + for parameter in "${!list_of_parameters_values[@]}"; do + __static__Generate_And_Store_Parameter_List_Of_Values "${parameter}" + done + ;; + *) + Print_Internal_And_Exit \ + 'Unknown scan strategy in ' --emph "${FUNCNAME}" ' function.' + ;; + esac + echo "${list_of_parameters_values[@]}" } function __static__Generate_And_Store_Parameter_List_Of_Values() @@ -40,9 +47,53 @@ function __static__Generate_And_Store_Parameter_List_Of_Values() yq '.Values | .. style="flow"' <<< "${scan_map}" ) ;; + "[Range]") + Print_Internal_And_Exit \ + 'A range of sample values is currently only supported in Latin Hypercube Sampling.' + ;; + *) + Print_Internal_And_Exit \ + 'Unknown scan in ' --emph "${FUNCNAME}" ' function.' + ;; + esac +} + +function __static__Generate_And_Store_Parameter_List_Of_Ranges() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values + local -r parameter=$1 scan_map="${list_of_parameters_values["$1"]}" + local -r sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" + case "${sorted_scan_keys}" in + "[Values]") + Print_Internal_And_Exit \ + 'Explicit values are not allowed for Latin Hypercube Sampling.' + ;; + "[Range]") + # local values num_elements + # values=$( + # yq '.Values | .. style="flow"' <<< "${scan_map}" + # ) + # num_elements=$(tr -cd ',' <<< "${values}" | wc -c) + # if (( num_elements != 2 )); then + # Print_Internal_And_Exit \ + # 'The range has to be defined by a lower and an upper bound' + # fi + + # local first_element=${values%%,*} # Extract first element before comma + # local second_element=${values#*,} # Extract substring after the first comma + # if (( first_element >= second_element )); then + # Print_Internal_And_Exit \ + # 'The lower boundary has to be smaller than the upper boundary.' + # fi + + list_of_parameters_values["${parameter}"]=$( + yq '.Range | .. style="flow"' <<< "${scan_map}" + ) + ;; *) Print_Internal_And_Exit \ 'Unknown scan in ' --emph "${FUNCNAME}" ' function.' ;; esac } + From da931f2d226b6b3b1c3503a84e706dad899fa169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 26 Feb 2024 15:36:05 +0100 Subject: [PATCH 435/549] Adjustments after rebase --- bash/scan_validation.bash | 4 ++-- bash/scan_values_operations.bash | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index 6cce2d6..7a52712 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -195,9 +195,9 @@ function __static__Has_Valid_Scan_Correct_Values() return 1 fi num_values=$(yq '.Scan.Range | length' <<< "${given_scan}") - if (( num_values != 2 )); then + if ((num_values != 2)); then Print_Error \ - 'Exactly two values are expected for the range. '\ + 'Exactly two values are expected for the range. ' \ 'The given range is ' --emph "${num_values}" ' long.' return 1 fi diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index da82e34..ee59566 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -78,7 +78,7 @@ function __static__Generate_And_Store_Parameter_List_Of_Ranges() # Print_Internal_And_Exit \ # 'The range has to be defined by a lower and an upper bound' # fi - + # local first_element=${values%%,*} # Extract first element before comma # local second_element=${values#*,} # Extract substring after the first comma # if (( first_element >= second_element )); then @@ -96,4 +96,3 @@ function __static__Generate_And_Store_Parameter_List_Of_Ranges() ;; esac } - From 5a2175a9198f3386c27c4d17af07e6e7028ab9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 5 Mar 2024 17:15:02 +0100 Subject: [PATCH 436/549] Preliminary implementations of LHS --- bash/global_variables.bash | 2 ++ bash/sanity_checks.bash | 16 +++++++++++++++- bash/scan_files_operations.bash | 20 ++++++++++++++------ bash/scan_values_operations.bash | 19 ------------------- docs/user/scans_types.md | 11 +++++++---- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index f098420..0463379 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -70,6 +70,7 @@ function Define_Further_Global_Variables() declare -rgA HYBRID_hybrid_handler_valid_keys=( [Run_ID]='HYBRID_run_id' [Scan_strategy]='HYBRID_scan_strategy' + [Number_of_Samples]='HYBRID_number_of_samples' ) declare -rgA HYBRID_ic_valid_keys=( [Executable]='HYBRID_software_executable[IC]' @@ -113,6 +114,7 @@ function Define_Further_Global_Variables() # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_run_id="Run_$(date +'%Y-%m-%d_%H%M%S')" HYBRID_scan_strategy='Combinations' + HYBRID_number_of_samples=1 HYBRID_given_software_sections=() declare -gA HYBRID_software_executable=( [IC]='' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 0d2ba88..81d8bbd 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -167,7 +167,21 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() fi done ;; - prepare-scan) ;; + prepare-scan) + if [[ "${HYBRID_scan_strategy}" = 'LHS' ]]; then + if [[ -n "${HYBRID_number_of_samples//[0-9]}" ]]; then + Print_Error \ + 'The number of samples has to be a positive integer. ' \ + 'The number of samples is ' --emph "${HYBRID_number_of_samples}" '.' + return 1 + elif [[ ${HYBRID_number_of_samples} -lt 2 ]]; then + Print_Error \ + 'The number of samples has to be greater 1. ' \ + 'The number of samples is ' --emph "${HYBRID_number_of_samples}" '.' + return 1 + fi + fi + ;; help) ;; # This is the default mode which is set in tests -> do nothing, but catch it *) Print_Internal_And_Exit 'Unknown execution mode passed to ' --emph "${FUNCNAME}" ' function.' diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 4d8fdb2..1788483 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -31,7 +31,7 @@ function Create_And_Populate_Scan_Folder() auxiliary_string=$(__static__Get_Fixed_Order_Parameters_Values) readarray -t parameters_values < <(printf "${auxiliary_string}") auxiliary_string=$(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${parameters_values[@]}") - readarray -t parameters_combinations < <(printf "${auxiliary_string}") + readarray -t parameters_combinations < <(printf -- "${auxiliary_string}") readonly parameters_names parameters_values parameters_combinations __static__Validate_And_Create_Scan_Folder __static__Create_Combinations_File_With_Metadata_Header_Block @@ -66,11 +66,19 @@ function __static__Get_Fixed_Order_Parameters_Values() function __static__Get_Parameters_Combinations_For_New_Configuration_Files() { - # NOTE: This is were multiple ways of doing combinations will be implemented: - # For example, cartesian product VS all first values, all second ones, etc. - # At the moment only the cartesian product approach is implemented, i.e. - # all possible combinations of parameters values are considered. - __static__Get_All_Parameters_Combinations "$@" + case "${HYBRID_scan_strategy}" in + 'LHS') + printf "$(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py\ + --parameter_ranges "$*" --num_samples ${HYBRID_number_of_samples})" + ;; + 'Combinations') + __static__Get_All_Parameters_Combinations "$@" + ;; + *) + Print_Internal_And_Exit \ + 'Unknown scan strategy in ' --emph "${FUNCNAME}" ' function.' + ;; + esac } function __static__Get_All_Parameters_Combinations() diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index ee59566..163a9d5 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -14,7 +14,6 @@ function Create_List_Of_Parameters_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values - echo "${list_of_parameters_values[@]}" case "${HYBRID_scan_strategy}" in 'LHS') local parameter @@ -33,7 +32,6 @@ function Create_List_Of_Parameters_Values() 'Unknown scan strategy in ' --emph "${FUNCNAME}" ' function.' ;; esac - echo "${list_of_parameters_values[@]}" } function __static__Generate_And_Store_Parameter_List_Of_Values() @@ -69,23 +67,6 @@ function __static__Generate_And_Store_Parameter_List_Of_Ranges() 'Explicit values are not allowed for Latin Hypercube Sampling.' ;; "[Range]") - # local values num_elements - # values=$( - # yq '.Values | .. style="flow"' <<< "${scan_map}" - # ) - # num_elements=$(tr -cd ',' <<< "${values}" | wc -c) - # if (( num_elements != 2 )); then - # Print_Internal_And_Exit \ - # 'The range has to be defined by a lower and an upper bound' - # fi - - # local first_element=${values%%,*} # Extract first element before comma - # local second_element=${values#*,} # Extract substring after the first comma - # if (( first_element >= second_element )); then - # Print_Internal_And_Exit \ - # 'The lower boundary has to be smaller than the upper boundary.' - # fi - list_of_parameters_values["${parameter}"]=$( yq '.Range | .. style="flow"' <<< "${scan_map}" ) diff --git a/docs/user/scans_types.md b/docs/user/scans_types.md index 87f9c85..ec5459e 100644 --- a/docs/user/scans_types.md +++ b/docs/user/scans_types.md @@ -4,10 +4,7 @@ If a parameter scan is created with more than one scan parameter, it has to be d ## All combinations by default -Unless differently specified(1), all combinations of parameters values are considered and one output handler configuration file per combination will be created. -{.annotate} - -1. :warning: Actually, different types of scan and a way to select them has still to be implemented. +Unless differently specified, all combinations of parameters values are considered and one output handler configuration file per combination will be created. Alternativly, the key for specifying all combinations is the Hybrid handler key is `Scan_strategy: Combinations` From the mathematical point of view, given $n$ scan parameters with set of values $X_1, ..., X_n\,$, the set of considered combinations is nothing but the $n$-ary Cartesian product over all sets of values, @@ -61,3 +58,9 @@ For example, specifying two different scan parameters with 2 and 5 values, respe ??? question "What happens if I provide a single value for a scan parameter?" If you provide a single-value list to `Values`, this will be accepted by the hybrid handler and the provided value will be considered in all combinations. If this happen to be the only provided scan parameter, a single configuration file will be created together with a basically useless single-combination file. :sweat_smile: + +### Latin Hypercube Sampling + +This can be specified by choosing the `Scan_strategy: LHS` and also giving the number of samples to draw `Number_of_Samples: n`, +where `n` is an integer greater 2. LHS samples multidimensional parameters near random, while keeping the distance between +samples maximal, and is commonly used for Bayesian inference. Refer to the [:link: wikipedia page](https://en.wikipedia.org/wiki/Latin_hypercube_sampling) for more information. The sampling itself is done by calling the [:link: PyDoe](https://pythonhosted.org/pyDOE/randomized.html#latin-hypercube) Python library function, using the `maximin` setting. \ No newline at end of file From d241dca503d506865b4f0ed7ee63710b1ecfd33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 5 Mar 2024 18:38:03 +0100 Subject: [PATCH 437/549] Add tests and format --- bash/sanity_checks.bash | 4 +- bash/scan_files_operations.bash | 14 +++--- bash/scan_validation.bash | 2 +- tests/unit_tests_scan_validation.bash | 5 ++ tests/unit_tests_scan_values_operations.bash | 49 ++++++++++++++++++++ 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 81d8bbd..7d7ba73 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -169,10 +169,10 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() ;; prepare-scan) if [[ "${HYBRID_scan_strategy}" = 'LHS' ]]; then - if [[ -n "${HYBRID_number_of_samples//[0-9]}" ]]; then + if [[ -n "${HYBRID_number_of_samples//[0-9]/}" ]]; then Print_Error \ 'The number of samples has to be a positive integer. ' \ - 'The number of samples is ' --emph "${HYBRID_number_of_samples}" '.' + 'The number of samples is ' --emph "${HYBRID_number_of_samples}" '.' return 1 elif [[ ${HYBRID_number_of_samples} -lt 2 ]]; then Print_Error \ diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 1788483..60893b0 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -68,16 +68,16 @@ function __static__Get_Parameters_Combinations_For_New_Configuration_Files() { case "${HYBRID_scan_strategy}" in 'LHS') - printf "$(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py\ - --parameter_ranges "$*" --num_samples ${HYBRID_number_of_samples})" - ;; + printf "$(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py --parameter_ranges "$*" \ + --num_samples ${HYBRID_number_of_samples})" + ;; 'Combinations') __static__Get_All_Parameters_Combinations "$@" - ;; + ;; *) - Print_Internal_And_Exit \ - 'Unknown scan strategy in ' --emph "${FUNCNAME}" ' function.' - ;; + Print_Internal_And_Exit \ + 'Unknown scan strategy in ' --emph "${FUNCNAME}" ' function.' + ;; esac } diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index 7a52712..157dd26 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -173,7 +173,7 @@ function __static__Has_Valid_Scan_Correct_Values() if [[ $(yq '.Scan.Range | tag' <<< "${given_scan}") != '!!seq' ]]; then Print_Error \ 'The value ' --emph "$(yq '.Scan.Range' <<< "${given_scan}")" \ - ' of the ' --emph 'Values' ' key is not a list of parameter values.' + ' of the ' --emph 'Range' ' key is not a range of parameter values.' return 1 fi local list_of_value_types num_values first_value second_value diff --git a/tests/unit_tests_scan_validation.bash b/tests/unit_tests_scan_validation.bash index 6708988..db12027 100644 --- a/tests/unit_tests_scan_validation.bash +++ b/tests/unit_tests_scan_validation.bash @@ -63,6 +63,10 @@ function Unit_Test__scan-YAML-scan-syntax() '{Scan: {Values: 42}}' '{Scan: {Values: [42, False, 3.14]}}' '{Scan: {Values: ["Hi", "Bye"]}}' + '{Scan: {Range: ["Hi", "Bye"]}}' + '{Scan: {Range: [2, 1]}}' + '{Scan: {Range: [1, 2, 3]}}' + '{Scan: {Range: [1, "Bye"]}}' ) for value in "${values[@]}"; do Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" &> /dev/null @@ -76,6 +80,7 @@ function Unit_Test__scan-YAML-scan-syntax() '{Scan: {Values: [1,2,3]}}' '{Scan: {Values: [42, 3.14]}}' '{Scan: {Values: [3.14, 6.28]}}' + '{Scan: {Range: [-2, -1.2]}}' ) for value in "${values[@]}"; do Call_Codebase_Function __static__Is_Given_Key_Value_A_Valid_Scan "${value}" diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash index bcdd4c1..7360720 100644 --- a/tests/unit_tests_scan_values_operations.bash +++ b/tests/unit_tests_scan_values_operations.bash @@ -33,3 +33,52 @@ function Unit_Test__scan-create-list() return 1 fi } +function Make_Test_Preliminary_Operations__scan-create-list-LHS() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'global_variables.bash' + 'scan_values_operations.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables +} + +function Unit_Test__scan-create-list-LHS() +{ + declare -A list_of_parameters_values=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Range: [4.3, 7.7]}' + ['Hydro.Software_keys.etaS']='{Range: [0.13, 0.15, 0.17]}' + ) + declare -A parameter_ranges=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='4.3:7.7' + ['Hydro.Software_keys.etaS']='0.13:0.17' + ) + HYBRID_number_of_samples=2 + HYBRID_scan_strategy='LHS' + Call_Codebase_Function Create_List_Of_Parameters_Values + function check_range() + { + local value=$1 + local range=$2 + local min=$(cut -d: -f1 <<< "$range") + local max=$(cut -d: -f2 <<< "$range") + (($(bc <<< "$value >= $min && $value <= $max"))) + } + + # Loop through each parameter and its values + for param in "${!list_of_parameters_values[@]}"; do + values="${list_of_parameters_values[$param]}" + range="${parameter_ranges[$param]}" + # Check if each value lies within the specified range + for val in $(sed 's/[][]//g' <<< "$values" | tr ',' '\n'); do + if ! check_range "$val" "$range"; then + Print_Error "Value $val for parameter $param is not within the specified range $range.\ + LHS sampling has failed." + return 1 + fi + done + done +} From 593dec3c97491f85539671315db90103d543ed02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Wed, 6 Mar 2024 18:35:06 +0100 Subject: [PATCH 438/549] Add python script --- python/latin_hypercube_sampling.py | 49 ++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 python/latin_hypercube_sampling.py diff --git a/python/latin_hypercube_sampling.py b/python/latin_hypercube_sampling.py new file mode 100644 index 0000000..9e4fbc9 --- /dev/null +++ b/python/latin_hypercube_sampling.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import argparse +import numpy as np +import ast +from pyDOE import lhs + +#scale the hypercube to the specified ranges +def generate_points_from_ranges(ranges, probabilities): + points = [] + for i in range(len(probabilities)): + sample = [] + for j in range(len(probabilities[i])): + range_min, range_max = ranges[j] + probability = probabilities[i][j] + value = range_min + (range_max - range_min) * probability + sample.append(value) + points.append(sample) + return np.array(points) + +if __name__ == '__main__': + # pass arguments from the command line to the script + parser = argparse.ArgumentParser() + parser.add_argument("--parameter_ranges", required = True, + help="Ranges of the parameters to be sampled.") + parser.add_argument("--num_samples", required = True, + help="Number of samples to be drawn.") + args = parser.parse_args() + + list_of_strings = args.parameter_ranges.split('] [') + list_of_strings[0] = list_of_strings[0] + ']' + list_of_strings[-1] = '[' + list_of_strings[-1] + + for i in range(1, len(list_of_strings) - 1): + list_of_strings[i] = '[' + list_of_strings[i] + ']' + + # parse each string into a list + list_of_lists = [ast.literal_eval(sublist) for sublist in list_of_strings] + + # convert the list of lists into a numpy array + arr = np.array(list_of_lists) + unit = lhs(arr.shape[0], samples=int(args.num_samples), criterion='maximin') + result= generate_points_from_ranges(arr, unit) + return_string="" + for row in result: + for elem in row: + return_string += str(elem) + " " + return_string += "\n" + print(return_string) + From 41b87cff33640fd7fc2eafec1e998366b17d0ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Wed, 6 Mar 2024 21:20:34 +0100 Subject: [PATCH 439/549] Fix issue with test of python script, add sanity checks --- bash/sanity_checks.bash | 7 ++ bash/scan_files_operations.bash | 6 +- tests/unit_tests_scan_files_operations.bash | 70 ++++++++++++++++++++ tests/unit_tests_scan_values_operations.bash | 49 -------------- 4 files changed, 82 insertions(+), 50 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 7d7ba73..54e851e 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -181,6 +181,13 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() return 1 fi fi + if [[ "${HYBRID_scan_strategy}" = 'Combinations' ]]; then + if [[ ${HYBRID_number_of_samples} -gt 2 ]]; then + Print_Error \ + 'TA number of samples is only accepted for LHS strategy. ' + return 1 + fi + fi ;; help) ;; # This is the default mode which is set in tests -> do nothing, but catch it *) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 60893b0..6dfa29b 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -30,7 +30,11 @@ function Create_And_Populate_Scan_Folder() readarray -t parameters_names < <(printf "${auxiliary_string}") auxiliary_string=$(__static__Get_Fixed_Order_Parameters_Values) readarray -t parameters_values < <(printf "${auxiliary_string}") + echo "${list_of_parameters_values[@]}" + echo "${parameters_values[@]}" auxiliary_string=$(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${parameters_values[@]}") + echo "${list_of_parameters_values[@]}" + echo "${auxiliary_string}" readarray -t parameters_combinations < <(printf -- "${auxiliary_string}") readonly parameters_names parameters_values parameters_combinations __static__Validate_And_Create_Scan_Folder @@ -68,7 +72,7 @@ function __static__Get_Parameters_Combinations_For_New_Configuration_Files() { case "${HYBRID_scan_strategy}" in 'LHS') - printf "$(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py --parameter_ranges "$*" \ + printf -- "$(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py --parameter_ranges "$*" \ --num_samples ${HYBRID_number_of_samples})" ;; 'Combinations') diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index 29cd81c..f01b4e4 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -87,3 +87,73 @@ EOF fi done } + +function Make_Test_Preliminary_Operations__scan-create-single-file-LHS() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'global_variables.bash' + 'progress_bar.bash' + 'scan_files_operations.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables +} + +function Unit_Test__scan-create-single-file-LHS() +{ + declare -A list_of_parameters_values=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='[4.3, 7.7]' + ['Hydro.Software_keys.etaS']='[0.13, 0.17]' + ) + HYBRID_scan_strategy='LHS' + HYBRID_number_of_samples=2 + printf ' + Hybrid_handler: + Scan_strategy: LHS + Number_of_Samples: 2 + IC: + Software_keys: + Modi: + Collider: + Sqrtsnn: {Scan: {Range: [4.3, 7.7]}} + Hydro: + Software_keys: + etaS: {Scan: {Range: [0.13, 0.17]}} + ' > 'config.yaml' + HYBRID_scan_directory='scan_test' + Call_Codebase_Function Create_And_Populate_Scan_Folder &> /dev/null 9>&1 # Suppress progress bar, too + cd "${HYBRID_scan_directory}" + shopt -s nullglob + local -r list_of_files=(*) + if [[ ${#list_of_files[@]} -ne 3 ]]; then + Print_Error 'Expected ' --emph '3' ' files to be created, but ' --emph "${#list_of_files[@]}" ' found.' + return 1 + fi + local file values sqrt_snn eta_s sqrt_snn_gte sqrt_snn_lte eta_s_gte eta_s_lte + for file in "${list_of_files[@]}"; do + if [[ "${file}" = "${HYBRID_scan_combinations_filename}" ]]; then + continue + fi + if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_run_[1-2]\.yaml$ ]]; then + Print_Error 'Filename ' --emph "${file}" ' not matching expected name.' + return 1 + fi + values=($(awk 'NR > 3 {print $2, $3}' "${HYBRID_scan_combinations_filename}")) + sqrt_snn="${values[0]}" + eta_s="${values[1]}" + sqrt_snn_gte=$(bc <<< "${sqrt_snn} >= \ + ${list_of_parameters_values['IC.Software_keys.Modi.Collider.Sqrtsnn']:1:-1}") + sqrt_snn_lte=$(bc <<< "${sqrt_snn} <= \ + ${list_of_parameters_values['IC.Software_keys.Modi.Collider.Sqrtsnn']:1:-1}") + eta_s_gte=$(bc <<< "${eta_s} >= ${list_of_parameters_values['Hydro.Software_keys.etaS']:1:-1}") + eta_s_lte=$(bc <<< "${eta_s} <= ${list_of_parameters_values['Hydro.Software_keys.etaS']:1:-1}") + + if ((sqrt_snn_gte == 1 && sqrt_snn_lte == 1 && eta_s_gte == 1 && eta_s_lte == 1)); then + Print_Error 'Values of ' --emph 'Sqrtsnn' ' or ' --emph 'etaS' ' wrongly set in output file.' + return 1 + fi + done +} diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash index 7360720..bcdd4c1 100644 --- a/tests/unit_tests_scan_values_operations.bash +++ b/tests/unit_tests_scan_values_operations.bash @@ -33,52 +33,3 @@ function Unit_Test__scan-create-list() return 1 fi } -function Make_Test_Preliminary_Operations__scan-create-list-LHS() -{ - local file_to_be_sourced list_of_files - list_of_files=( - 'global_variables.bash' - 'scan_values_operations.bash' - ) - for file_to_be_sourced in "${list_of_files[@]}"; do - source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} - done - Define_Further_Global_Variables -} - -function Unit_Test__scan-create-list-LHS() -{ - declare -A list_of_parameters_values=( - ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Range: [4.3, 7.7]}' - ['Hydro.Software_keys.etaS']='{Range: [0.13, 0.15, 0.17]}' - ) - declare -A parameter_ranges=( - ['IC.Software_keys.Modi.Collider.Sqrtsnn']='4.3:7.7' - ['Hydro.Software_keys.etaS']='0.13:0.17' - ) - HYBRID_number_of_samples=2 - HYBRID_scan_strategy='LHS' - Call_Codebase_Function Create_List_Of_Parameters_Values - function check_range() - { - local value=$1 - local range=$2 - local min=$(cut -d: -f1 <<< "$range") - local max=$(cut -d: -f2 <<< "$range") - (($(bc <<< "$value >= $min && $value <= $max"))) - } - - # Loop through each parameter and its values - for param in "${!list_of_parameters_values[@]}"; do - values="${list_of_parameters_values[$param]}" - range="${parameter_ranges[$param]}" - # Check if each value lies within the specified range - for val in $(sed 's/[][]//g' <<< "$values" | tr ',' '\n'); do - if ! check_range "$val" "$range"; then - Print_Error "Value $val for parameter $param is not within the specified range $range.\ - LHS sampling has failed." - return 1 - fi - done - done -} From 38d215a81df5488260110e2d95fe8885777c398f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Wed, 6 Mar 2024 21:22:56 +0100 Subject: [PATCH 440/549] Cleanup --- bash/scan_files_operations.bash | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 6dfa29b..a7344b0 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -30,11 +30,7 @@ function Create_And_Populate_Scan_Folder() readarray -t parameters_names < <(printf "${auxiliary_string}") auxiliary_string=$(__static__Get_Fixed_Order_Parameters_Values) readarray -t parameters_values < <(printf "${auxiliary_string}") - echo "${list_of_parameters_values[@]}" - echo "${parameters_values[@]}" auxiliary_string=$(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${parameters_values[@]}") - echo "${list_of_parameters_values[@]}" - echo "${auxiliary_string}" readarray -t parameters_combinations < <(printf -- "${auxiliary_string}") readonly parameters_names parameters_values parameters_combinations __static__Validate_And_Create_Scan_Folder From 91b2d306b908ee51dcfebd5ced1c4663157cf1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 7 Mar 2024 14:01:57 +0100 Subject: [PATCH 441/549] Restructure code to be more consistent --- bash/execution_mode_prepare.bash | 2 +- bash/scan_files_operations.bash | 14 +++++- bash/scan_values_operations.bash | 4 ++ python/latin_hypercube_sampling.py | 51 +++++++++----------- tests/unit_tests_scan_files_operations.bash | 4 +- tests/unit_tests_scan_values_operations.bash | 44 +++++++++++++++++ 6 files changed, 86 insertions(+), 33 deletions(-) diff --git a/bash/execution_mode_prepare.bash b/bash/execution_mode_prepare.bash index 3255521..b273258 100644 --- a/bash/execution_mode_prepare.bash +++ b/bash/execution_mode_prepare.bash @@ -18,7 +18,7 @@ function Do_Needed_Operation_For_Parameter_Scan() # This syntax is handy when it comes to prepare all the configuration files # as it naturally interacts well with yq. The value is a YAML sequence of the # parameter values. - declare -A list_of_parameters_values + declare -gA list_of_parameters_values Format_Scan_Parameters_Lists Print_Info 'Validating input scan parameters values' Validate_And_Store_Scan_Parameters diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index a7344b0..f134286 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -68,8 +68,7 @@ function __static__Get_Parameters_Combinations_For_New_Configuration_Files() { case "${HYBRID_scan_strategy}" in 'LHS') - printf -- "$(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py --parameter_ranges "$*" \ - --num_samples ${HYBRID_number_of_samples})" + __static__Get_Unique_Sample_Points "$@" ;; 'Combinations') __static__Get_All_Parameters_Combinations "$@" @@ -98,6 +97,17 @@ function __static__Get_All_Parameters_Combinations() eval printf '%s\\\n' "${string_to_be_expanded%?}" | sed 's/_/ /g' } +function __static__Get_Unique_Sample_Points() +{ + local index parameter + for ((index = 0; index < ${HYBRID_number_of_samples}; index++)); do + for parameter in "$@"; do + printf '%s ' $(python3 -c "print(${parameter}[${index}])") + done + printf '\n' + done +} + function __static__Validate_And_Create_Scan_Folder() { Ensure_Given_Folders_Do_Not_Exist \ diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index 163a9d5..0ebb9f2 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -20,6 +20,10 @@ function Create_List_Of_Parameters_Values() for parameter in "${!list_of_parameters_values[@]}"; do __static__Generate_And_Store_Parameter_List_Of_Ranges "${parameter}" done + declare -gA "$(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py \ + --parameter_names "${!list_of_parameters_values[@]}" \ + --parameter_ranges "${list_of_parameters_values[@]}" \ + --num_samples ${HYBRID_number_of_samples})" ;; 'Combinations') local parameter diff --git a/python/latin_hypercube_sampling.py b/python/latin_hypercube_sampling.py index 9e4fbc9..cd7023a 100644 --- a/python/latin_hypercube_sampling.py +++ b/python/latin_hypercube_sampling.py @@ -5,14 +5,14 @@ from pyDOE import lhs #scale the hypercube to the specified ranges -def generate_points_from_ranges(ranges, probabilities): +def generate_points_from_ranges(ranges, unit_points): points = [] - for i in range(len(probabilities)): + for i in range(len(unit_points)): sample = [] - for j in range(len(probabilities[i])): + for j in range(len(unit_points[i])): range_min, range_max = ranges[j] - probability = probabilities[i][j] - value = range_min + (range_max - range_min) * probability + unit_point = unit_points[i][j] + value = range_min + (range_max - range_min) * unit_point sample.append(value) points.append(sample) return np.array(points) @@ -20,30 +20,25 @@ def generate_points_from_ranges(ranges, probabilities): if __name__ == '__main__': # pass arguments from the command line to the script parser = argparse.ArgumentParser() - parser.add_argument("--parameter_ranges", required = True, + parser.add_argument("--parameter_names", required = True, nargs='+', + help="Names of the parameters to be sampled.") + parser.add_argument("--parameter_ranges", required = True, nargs='+', help="Ranges of the parameters to be sampled.") - parser.add_argument("--num_samples", required = True, + parser.add_argument("--num_samples", required = True, help="Number of samples to be drawn.") args = parser.parse_args() - - list_of_strings = args.parameter_ranges.split('] [') - list_of_strings[0] = list_of_strings[0] + ']' - list_of_strings[-1] = '[' + list_of_strings[-1] - - for i in range(1, len(list_of_strings) - 1): - list_of_strings[i] = '[' + list_of_strings[i] + ']' - - # parse each string into a list - list_of_lists = [ast.literal_eval(sublist) for sublist in list_of_strings] - - # convert the list of lists into a numpy array - arr = np.array(list_of_lists) - unit = lhs(arr.shape[0], samples=int(args.num_samples), criterion='maximin') - result= generate_points_from_ranges(arr, unit) + parameter_names = args.parameter_names + parameter_ranges = args.parameter_ranges + parameter_ranges = np.array([ast.literal_eval(i) for i in parameter_ranges]) + unit = lhs(parameter_ranges.shape[0], samples=int(args.num_samples), criterion='centermaximin') + result=generate_points_from_ranges(parameter_ranges, unit).transpose() return_string="" - for row in result: - for elem in row: - return_string += str(elem) + " " - return_string += "\n" - print(return_string) - + for i in range(len(parameter_names)): + return_string += "["+parameter_names[i] + "]='[" + for j in range(result.shape[1]): + return_string += str(result[i][j]) + if j != result.shape[1]-1: + return_string += "," + else: + return_string += "]'\n" + print("list_of_parameters_values=(\n"+return_string+"\n)") \ No newline at end of file diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index f01b4e4..4f19882 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -106,7 +106,7 @@ function Unit_Test__scan-create-single-file-LHS() { declare -A list_of_parameters_values=( ['IC.Software_keys.Modi.Collider.Sqrtsnn']='[4.3, 7.7]' - ['Hydro.Software_keys.etaS']='[0.13, 0.17]' + ['Hydro.Software_keys.etaS']='[-0.13, 0.17]' ) HYBRID_scan_strategy='LHS' HYBRID_number_of_samples=2 @@ -121,7 +121,7 @@ function Unit_Test__scan-create-single-file-LHS() Sqrtsnn: {Scan: {Range: [4.3, 7.7]}} Hydro: Software_keys: - etaS: {Scan: {Range: [0.13, 0.17]}} + etaS: {Scan: {Range: [-0.13, 0.17]}} ' > 'config.yaml' HYBRID_scan_directory='scan_test' Call_Codebase_Function Create_And_Populate_Scan_Folder &> /dev/null 9>&1 # Suppress progress bar, too diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash index bcdd4c1..58316fc 100644 --- a/tests/unit_tests_scan_values_operations.bash +++ b/tests/unit_tests_scan_values_operations.bash @@ -33,3 +33,47 @@ function Unit_Test__scan-create-list() return 1 fi } + +function Make_Test_Preliminary_Operations__scan-create-list-LHS() +{ + local file_to_be_sourced list_of_files + list_of_files=( + 'global_variables.bash' + 'scan_values_operations.bash' + ) + for file_to_be_sourced in "${list_of_files[@]}"; do + source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} + done + Define_Further_Global_Variables + HYBRID_scan_strategy='LHS' + HYBRID_number_of_samples=3 +} + +function Unit_Test__scan-create-list-LHS() +{ + declare -gA list_of_parameters_values=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Range: [4.3, 7.7]}' + ['Hydro.Software_keys.etaS']='{Range: [-0.13, 0.17]}' + ) + Call_Codebase_Function Create_List_Of_Parameters_Values + for key in "${!list_of_parameters_values[@]}"; do + expected_value="${list_of_parameters_values[$key]}" + actual_value="${list_of_parameters_values[$key]}" + if [[ "$actual_value" != "$expected_value" ]]; then + Print_Error "Parameter values list for '$key' was not correctly created." \ + "Expected: '$expected_value', Actual: '$actual_value'." + return 1 + fi + done + + # Check if each key has a list with exactly three values + for key in "${!list_of_parameters_values[@]}"; do + value="${list_of_parameters_values[$key]}" + value_count=$(echo "$value" | tr -cd ',' | wc -c) + if [ "$value_count" -ne 2 ]; then + Print_Error "Key $key does not have exactly three values in its list." + return 1 + fi + done + +} From dad81fb9186c7aa737e523f5234bb8170c0fcfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Thu, 7 Mar 2024 14:12:03 +0100 Subject: [PATCH 442/549] Add Python requirements --- python/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 python/requirements.txt diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 0000000..5c9052d --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,4 @@ +numpy==1.26.4 +pyDOE==0.3.8 +PyYAML==6.0.1 +scipy==1.12.0 From b211880be309ff8723a52aedbeec77ae0a9e9450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Fri, 8 Mar 2024 11:07:24 +0100 Subject: [PATCH 443/549] Make list_of_parameters_values local again --- bash/execution_mode_prepare.bash | 2 +- bash/sanity_checks.bash | 2 +- bash/scan_values_operations.bash | 12 ++++++++++-- python/latin_hypercube_sampling.py | 8 +++++--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/bash/execution_mode_prepare.bash b/bash/execution_mode_prepare.bash index b273258..3255521 100644 --- a/bash/execution_mode_prepare.bash +++ b/bash/execution_mode_prepare.bash @@ -18,7 +18,7 @@ function Do_Needed_Operation_For_Parameter_Scan() # This syntax is handy when it comes to prepare all the configuration files # as it naturally interacts well with yq. The value is a YAML sequence of the # parameter values. - declare -gA list_of_parameters_values + declare -A list_of_parameters_values Format_Scan_Parameters_Lists Print_Info 'Validating input scan parameters values' Validate_And_Store_Scan_Parameters diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 54e851e..3727dd0 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -184,7 +184,7 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() if [[ "${HYBRID_scan_strategy}" = 'Combinations' ]]; then if [[ ${HYBRID_number_of_samples} -gt 2 ]]; then Print_Error \ - 'TA number of samples is only accepted for LHS strategy. ' + 'Number of samples is only accepted for LHS strategy. ' return 1 fi fi diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index 0ebb9f2..f8e4080 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -20,10 +20,18 @@ function Create_List_Of_Parameters_Values() for parameter in "${!list_of_parameters_values[@]}"; do __static__Generate_And_Store_Parameter_List_Of_Ranges "${parameter}" done - declare -gA "$(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py \ + local key value + while IFS='=' read -r key value; do + if [[ "${!list_of_parameters_values[@]}" =~ $key ]]; then + list_of_parameters_values["${key}"]="${value}" + else + Print_Internal_And_Exit \ + 'Processing of parameters failed in ' --emph "${FUNCNAME}" ' function.' + fi + done < <(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py \ --parameter_names "${!list_of_parameters_values[@]}" \ --parameter_ranges "${list_of_parameters_values[@]}" \ - --num_samples ${HYBRID_number_of_samples})" + --num_samples ${HYBRID_number_of_samples}) ;; 'Combinations') local parameter diff --git a/python/latin_hypercube_sampling.py b/python/latin_hypercube_sampling.py index cd7023a..970f40c 100644 --- a/python/latin_hypercube_sampling.py +++ b/python/latin_hypercube_sampling.py @@ -34,11 +34,13 @@ def generate_points_from_ranges(ranges, unit_points): result=generate_points_from_ranges(parameter_ranges, unit).transpose() return_string="" for i in range(len(parameter_names)): - return_string += "["+parameter_names[i] + "]='[" + return_string += parameter_names[i] + "=[" for j in range(result.shape[1]): return_string += str(result[i][j]) if j != result.shape[1]-1: return_string += "," + elif i != len(parameter_names)-1: + return_string += "]\n" else: - return_string += "]'\n" - print("list_of_parameters_values=(\n"+return_string+"\n)") \ No newline at end of file + return_string += "]" + print(return_string) \ No newline at end of file From 12cdcbadfeebbd471cbad5e3a8558438b5bae38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Mon, 11 Mar 2024 17:51:50 +0100 Subject: [PATCH 444/549] Remove code duplication and change scan configuration --- bash/global_variables.bash | 8 +- bash/sanity_checks.bash | 33 ++++---- bash/scan_files_operations.bash | 25 ++++-- bash/scan_validation.bash | 88 ++++++++------------ bash/scan_values_operations.bash | 31 ++++--- docs/user/scans_syntax.md | 13 +++ docs/user/scans_types.md | 3 +- python/latin_hypercube_sampling.py | 10 ++- tests/unit_tests_scan_files_operations.bash | 45 ++++++---- tests/unit_tests_scan_values_operations.bash | 2 +- 10 files changed, 138 insertions(+), 120 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index 0463379..a9d5dba 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -29,6 +29,7 @@ function Define_Further_Global_Variables() readonly HYBRID_default_configurations_folder="${HYBRID_top_level_path}/configs" readonly HYBRID_python_folder="${HYBRID_top_level_path}/python" readonly HYBRID_afterburner_list_filename="sampled_particles_list.oscar" + readonly HYBRID_default_number_of_samples=0 declare -rgA HYBRID_external_python_scripts=( [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" ) @@ -69,8 +70,7 @@ function Define_Further_Global_Variables() # file and bash variables in which the input information will be stored once parsed. declare -rgA HYBRID_hybrid_handler_valid_keys=( [Run_ID]='HYBRID_run_id' - [Scan_strategy]='HYBRID_scan_strategy' - [Number_of_Samples]='HYBRID_number_of_samples' + [LHS_scan]='HYBRID_number_of_samples' ) declare -rgA HYBRID_ic_valid_keys=( [Executable]='HYBRID_software_executable[IC]' @@ -111,10 +111,10 @@ function Define_Further_Global_Variables() HYBRID_configuration_file='./config.yaml' HYBRID_output_directory="$(realpath './data')" HYBRID_scan_directory="${HYBRID_output_directory}/scan" + HYBRID_scan_strategy='Combinations' # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_run_id="Run_$(date +'%Y-%m-%d_%H%M%S')" - HYBRID_scan_strategy='Combinations' - HYBRID_number_of_samples=1 + HYBRID_number_of_samples="${HYBRID_default_number_of_samples}" HYBRID_given_software_sections=() declare -gA HYBRID_software_executable=( [IC]='' diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 3727dd0..a5feea1 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -166,25 +166,24 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() --emph 'parameter-scan' ' execution mode.' fi done + if [[ "${HYBRID_number_of_samples}" =~ '^[1-9][0-9]*$' ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'Number of samples can only be specified in ' \ + 'the prepare-scan mode!' + return 1 + fi ;; prepare-scan) - if [[ "${HYBRID_scan_strategy}" = 'LHS' ]]; then - if [[ -n "${HYBRID_number_of_samples//[0-9]/}" ]]; then - Print_Error \ - 'The number of samples has to be a positive integer. ' \ - 'The number of samples is ' --emph "${HYBRID_number_of_samples}" '.' - return 1 - elif [[ ${HYBRID_number_of_samples} -lt 2 ]]; then - Print_Error \ - 'The number of samples has to be greater 1. ' \ - 'The number of samples is ' --emph "${HYBRID_number_of_samples}" '.' - return 1 - fi - fi - if [[ "${HYBRID_scan_strategy}" = 'Combinations' ]]; then - if [[ ${HYBRID_number_of_samples} -gt 2 ]]; then - Print_Error \ - 'Number of samples is only accepted for LHS strategy. ' + if [[ "${HYBRID_number_of_samples}" -eq 0 ]]; then + readonly HYBRID_scan_strategy='Combinations' + else + if [[ "${HYBRID_number_of_samples}" -ge 2 ]]; then + readonly HYBRID_scan_strategy='LHS' + readonly HYBRID_number_of_samples + else + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'Number of samples has to be a positive integer greater than 1 ' \ + 'for Latin Hypercube Sampling scan.' return 1 fi fi diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index f134286..2ea5212 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -29,7 +29,7 @@ function Create_And_Populate_Scan_Folder() auxiliary_string=$(__static__Get_Fixed_Order_Parameters) readarray -t parameters_names < <(printf "${auxiliary_string}") auxiliary_string=$(__static__Get_Fixed_Order_Parameters_Values) - readarray -t parameters_values < <(printf "${auxiliary_string}") + readarray -t parameters_values < <(printf -- "${auxiliary_string}") auxiliary_string=$(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${parameters_values[@]}") readarray -t parameters_combinations < <(printf -- "${auxiliary_string}") readonly parameters_names parameters_values parameters_combinations @@ -68,7 +68,7 @@ function __static__Get_Parameters_Combinations_For_New_Configuration_Files() { case "${HYBRID_scan_strategy}" in 'LHS') - __static__Get_Unique_Sample_Points "$@" + __static__Get_Samples_for_LHS "$@" ;; 'Combinations') __static__Get_All_Parameters_Combinations "$@" @@ -97,14 +97,24 @@ function __static__Get_All_Parameters_Combinations() eval printf '%s\\\n' "${string_to_be_expanded%?}" | sed 's/_/ /g' } -function __static__Get_Unique_Sample_Points() +# For Latin Hypercube Sampling, a python script creates for each parameter a list of +# values. Each single sample is then created by taking one value from each list. +function __static__Get_Samples_for_LHS() { - local index parameter for ((index = 0; index < ${HYBRID_number_of_samples}; index++)); do + first_parameter=true for parameter in "$@"; do - printf '%s ' $(python3 -c "print(${parameter}[${index}])") + value=$(python3 -c "print(${parameter}[${index}])") + if $first_parameter; then + printf '%s' "$value" + first_parameter=false + else + printf ' %s' "$value" + fi done - printf '\n' + if [[ $index -ne $((${HYBRID_number_of_samples} - 1)) ]]; then + printf '\n' + fi done } @@ -182,6 +192,7 @@ function __static__Create_Single_Output_File_In_Scan_Folder() Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters_names filename local -r run_number=$(($1 + 1)) shift + local -r set_of_values=("$@") Internally_Ensure_Given_Files_Do_Not_Exist "${filename}" __static__Add_Parameters_Comment_Line_To_New_Configuration_File @@ -218,4 +229,6 @@ function __static__Remove_Scan_Parameters_Key_From_New_Configuration_File() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty filename sed -i '/^[[:space:]]*Scan_parameters:/d' "${filename}" + sed -i '/^[[:space:]]*LHS_scan:/d' "${filename}" + } diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index 157dd26..42ec239 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -139,61 +139,42 @@ function __static__Check_If_Keys_Of_Given_Scan_Have_Correct_Values() fi } +function __static__Validate_YAML_Numeric_Sequence_Key() +{ + if [[ $(yq '.Scan.'$1' | tag' <<< "${given_scan}") != '!!seq' ]]; then + Print_Error \ + 'The value ' --emph "$(yq '.Scan.'$1 <<< "${given_scan}")" \ + ' of the ' --emph $1 ' key is not a range of parameter values.' + return 1 + fi + local list_of_value_types num_values first_value second_value + list_of_value_types=($(yq '.Scan.'$1'[] | tag' <<< "${given_scan}" | sort -u)) + # If there is more than a type it is in general an error, unless there are exactly two, + # which are 'int' and 'float' (this is accepted). Note that the test is done on the + # concatenation of the values against 'float int' as the types are guaranteed to be sorted. + if [[ ${#list_of_value_types[@]} -ne 1 ]]; then + if [[ ${#list_of_value_types[@]} -ne 2 || "${list_of_value_types[*]//!!/}" != 'float int' ]]; then + Print_Error \ + 'The parameter values have different YAML types: ' \ + --emph "${list_of_value_types[*]//!!/}" '.' + return 1 + fi + elif [[ ! ${list_of_value_types[0]} =~ ^!!(bool|int|float)$ ]]; then + Print_Error \ + 'Parameter scans with values of ' --emph "${list_of_value_types[0]//!!/}" \ + ' type are not allowed.' 'Valid parameter types are ' --emph 'bool int float' ', only.' + return 1 + fi +} + function __static__Has_Valid_Scan_Correct_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys case "${sorted_scan_keys}" in "[Values]") - if [[ $(yq '.Scan.Values | tag' <<< "${given_scan}") != '!!seq' ]]; then - Print_Error \ - 'The value ' --emph "$(yq '.Scan.Values' <<< "${given_scan}")" \ - ' of the ' --emph 'Values' ' key is not a list of parameter values.' - return 1 - fi - local list_of_value_types - list_of_value_types=($(yq '.Scan.Values[] | tag' <<< "${given_scan}" | sort -u)) - # If there is more than a type it is in general an error, unless there are exactly two, - # which are 'int' and 'float' (this is accepted). Note that the test is done on the - # concatenation of the values against 'float int' as the types are guaranteed to be sorted. - if [[ ${#list_of_value_types[@]} -ne 1 ]]; then - if [[ ${#list_of_value_types[@]} -ne 2 || "${list_of_value_types[*]//!!/}" != 'float int' ]]; then - Print_Error \ - 'The parameter values have different YAML types: ' \ - --emph "${list_of_value_types[*]//!!/}" '.' - return 1 - fi - elif [[ ! ${list_of_value_types[0]} =~ ^!!(bool|int|float)$ ]]; then - Print_Error \ - 'Parameter scans with values of ' --emph "${list_of_value_types[0]//!!/}" \ - ' type are not allowed.' 'Valid parameter types are ' --emph 'bool int float' ', only.' - return 1 - fi + __static__Validate_YAML_Numeric_Sequence_Key 'Values' ;; "[Range]") - if [[ $(yq '.Scan.Range | tag' <<< "${given_scan}") != '!!seq' ]]; then - Print_Error \ - 'The value ' --emph "$(yq '.Scan.Range' <<< "${given_scan}")" \ - ' of the ' --emph 'Range' ' key is not a range of parameter values.' - return 1 - fi - local list_of_value_types num_values first_value second_value - list_of_value_types=($(yq '.Scan.Range[] | tag' <<< "${given_scan}" | sort -u)) - # If there is more than a type it is in general an error, unless there are exactly two, - # which are 'int' and 'float' (this is accepted). Note that the test is done on the - # concatenation of the values against 'float int' as the types are guaranteed to be sorted. - if [[ ${#list_of_value_types[@]} -ne 1 ]]; then - if [[ ${#list_of_value_types[@]} -ne 2 || "${list_of_value_types[*]//!!/}" != 'float int' ]]; then - Print_Error \ - 'The parameter values have different YAML types: ' \ - --emph "${list_of_value_types[*]//!!/}" '.' - return 1 - fi - elif [[ ! ${list_of_value_types[0]} =~ ^!!(bool|int|float)$ ]]; then - Print_Error \ - 'Parameter scans with values of ' --emph "${list_of_value_types[0]//!!/}" \ - ' type are not allowed.' 'Valid parameter types are ' --emph 'bool int float' ', only.' - return 1 - fi num_values=$(yq '.Scan.Range | length' <<< "${given_scan}") if ((num_values != 2)); then Print_Error \ @@ -201,11 +182,14 @@ function __static__Has_Valid_Scan_Correct_Values() 'The given range is ' --emph "${num_values}" ' long.' return 1 fi - first_value=$(yq '.Scan.Range[0]' <<< "${given_scan}") - second_value=$(yq '.Scan.Range[1]' <<< "${given_scan}") - if [[ $(awk 'BEGIN{print ('$first_value' < '$second_value') ? "true" : "false"}') == "false" ]]; then + __static__Validate_YAML_Numeric_Sequence_Key 'Range' + if [[ $? -eq 1 ]]; then + return 1 + fi + if [[ $(yq '.Scan.Range[0] > .Scan.Range[1]' <<< "${given_scan}") == "true" ]]; then Print_Error \ - 'The first value must be smaller than the second value in the Range. ' + 'The first value must be smaller than the second value in the Range. ' \ + 'The given range is ' --emph "$(yq '.Scan.Range' <<< "${given_scan}")" '.' return 1 fi ;; diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index f8e4080..70453f4 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -17,20 +17,21 @@ function Create_List_Of_Parameters_Values() case "${HYBRID_scan_strategy}" in 'LHS') local parameter + declare -A list_of_parameters_ranges for parameter in "${!list_of_parameters_values[@]}"; do __static__Generate_And_Store_Parameter_List_Of_Ranges "${parameter}" done local key value while IFS='=' read -r key value; do - if [[ "${!list_of_parameters_values[@]}" =~ $key ]]; then + if Element_In_Array_Equals_To "${key}" "${!list_of_parameters_values[@]}"; then list_of_parameters_values["${key}"]="${value}" else Print_Internal_And_Exit \ 'Processing of parameters failed in ' --emph "${FUNCNAME}" ' function.' fi done < <(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py \ - --parameter_names "${!list_of_parameters_values[@]}" \ - --parameter_ranges "${list_of_parameters_values[@]}" \ + --parameter_names "${!list_of_parameters_ranges[@]}" \ + --parameter_ranges "${list_of_parameters_ranges[@]}" \ --num_samples ${HYBRID_number_of_samples}) ;; 'Combinations') @@ -50,42 +51,38 @@ function __static__Generate_And_Store_Parameter_List_Of_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values local -r parameter=$1 scan_map="${list_of_parameters_values["$1"]}" - local -r sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" + local sorted_scan_keys + sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" + readonly sorted_scan_keys case "${sorted_scan_keys}" in "[Values]") list_of_parameters_values["${parameter}"]=$( yq '.Values | .. style="flow"' <<< "${scan_map}" ) ;; - "[Range]") - Print_Internal_And_Exit \ - 'A range of sample values is currently only supported in Latin Hypercube Sampling.' - ;; *) Print_Internal_And_Exit \ - 'Unknown scan in ' --emph "${FUNCNAME}" ' function.' + 'Unknown ' --emph "${sorted_scan_keys}" 'scan in ' --emph "${FUNCNAME}" ' function.' ;; esac } function __static__Generate_And_Store_Parameter_List_Of_Ranges() { - Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values + Ensure_That_Given_Variables_Are_Set list_of_parameters_ranges local -r parameter=$1 scan_map="${list_of_parameters_values["$1"]}" - local -r sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" + local sorted_scan_keys + sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" + readonly sorted_scan_keys case "${sorted_scan_keys}" in - "[Values]") - Print_Internal_And_Exit \ - 'Explicit values are not allowed for Latin Hypercube Sampling.' - ;; "[Range]") - list_of_parameters_values["${parameter}"]=$( + list_of_parameters_ranges["${parameter}"]=$( yq '.Range | .. style="flow"' <<< "${scan_map}" ) ;; *) Print_Internal_And_Exit \ - 'Unknown scan in ' --emph "${FUNCNAME}" ' function.' + 'Unknown ' --emph "${sorted_scan_keys}" 'scan in ' --emph "${FUNCNAME}" ' function.' ;; esac } diff --git a/docs/user/scans_syntax.md b/docs/user/scans_syntax.md index 42ffde5..d052362 100644 --- a/docs/user/scans_syntax.md +++ b/docs/user/scans_syntax.md @@ -62,3 +62,16 @@ This is possible in the `Values` YAML array inside the `Scan` map. - 42 - 666 ``` +### Latin Hypercube Sampling + +Here, one has to give the range within which one wants to sample +This is possible in the `Range` YAML array inside the `Scan` map. + +=== "Compact style" + + ```yaml title="Example" + Scan_parameters: ["foo.bar"] + Software_keys: + foo: + bar: {Scan: {Range: [17, 666]}} + ``` diff --git a/docs/user/scans_types.md b/docs/user/scans_types.md index ec5459e..cc145cc 100644 --- a/docs/user/scans_types.md +++ b/docs/user/scans_types.md @@ -61,6 +61,5 @@ For example, specifying two different scan parameters with 2 and 5 values, respe ### Latin Hypercube Sampling -This can be specified by choosing the `Scan_strategy: LHS` and also giving the number of samples to draw `Number_of_Samples: n`, -where `n` is an integer greater 2. LHS samples multidimensional parameters near random, while keeping the distance between +If `LHS_scan: n` is specified with any value but 0, Latin Hypercube Sampling is used. The value `n` is an integer greater 2. LHS samples multidimensional parameters near random, while keeping the distance between samples maximal, and is commonly used for Bayesian inference. Refer to the [:link: wikipedia page](https://en.wikipedia.org/wiki/Latin_hypercube_sampling) for more information. The sampling itself is done by calling the [:link: PyDoe](https://pythonhosted.org/pyDOE/randomized.html#latin-hypercube) Python library function, using the `maximin` setting. \ No newline at end of file diff --git a/python/latin_hypercube_sampling.py b/python/latin_hypercube_sampling.py index 970f40c..06d590d 100644 --- a/python/latin_hypercube_sampling.py +++ b/python/latin_hypercube_sampling.py @@ -29,6 +29,9 @@ def generate_points_from_ranges(ranges, unit_points): args = parser.parse_args() parameter_names = args.parameter_names parameter_ranges = args.parameter_ranges + if len(parameter_names) != len(parameter_ranges) or int(args.num_samples) < 2: + raise ValueError("The number of parameter names and parameter ranges must match and" + +"number of samples must be greater 0") parameter_ranges = np.array([ast.literal_eval(i) for i in parameter_ranges]) unit = lhs(parameter_ranges.shape[0], samples=int(args.num_samples), criterion='centermaximin') result=generate_points_from_ranges(parameter_ranges, unit).transpose() @@ -39,8 +42,7 @@ def generate_points_from_ranges(ranges, unit_points): return_string += str(result[i][j]) if j != result.shape[1]-1: return_string += "," - elif i != len(parameter_names)-1: - return_string += "]\n" else: - return_string += "]" - print(return_string) \ No newline at end of file + return_string += "]\n" + + print(return_string, end='') diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index 4f19882..623b8fb 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -108,12 +108,11 @@ function Unit_Test__scan-create-single-file-LHS() ['IC.Software_keys.Modi.Collider.Sqrtsnn']='[4.3, 7.7]' ['Hydro.Software_keys.etaS']='[-0.13, 0.17]' ) - HYBRID_scan_strategy='LHS' HYBRID_number_of_samples=2 + HYBRID_scan_strategy='LHS' printf ' Hybrid_handler: - Scan_strategy: LHS - Number_of_Samples: 2 + LHS_scan: 2 IC: Software_keys: Modi: @@ -123,6 +122,14 @@ function Unit_Test__scan-create-single-file-LHS() Software_keys: etaS: {Scan: {Range: [-0.13, 0.17]}} ' > 'config.yaml' + cat > ref_scan_combinations.dat << EOF +# Parameter_1: IC.Software_keys.Modi.Collider.Sqrtsnn +# Parameter_2: Hydro.Software_keys.etaS +# +#___Run Parameter_1 Parameter_2 + 1 4.3 -0.13 + 2 7.7 0.17 +EOF HYBRID_scan_directory='scan_test' Call_Codebase_Function Create_And_Populate_Scan_Folder &> /dev/null 9>&1 # Suppress progress bar, too cd "${HYBRID_scan_directory}" @@ -132,27 +139,31 @@ function Unit_Test__scan-create-single-file-LHS() Print_Error 'Expected ' --emph '3' ' files to be created, but ' --emph "${#list_of_files[@]}" ' found.' return 1 fi - local file values sqrt_snn eta_s sqrt_snn_gte sqrt_snn_lte eta_s_gte eta_s_lte + local file values sqrt_snn eta_s + set -- "4.3 -0.13" "7.7 0.17" for file in "${list_of_files[@]}"; do if [[ "${file}" = "${HYBRID_scan_combinations_filename}" ]]; then + if ! diff -q "${file}" '../ref_scan_combinations.dat' &> /dev/null; then + Print_Error 'Scan combinations file expected different.\n' + diff "${file}" '../ref_scan_combinations.dat' + return 1 + fi continue fi - if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_run_[1-2]\.yaml$ ]]; then + if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_run_[1-6]\.yaml$ ]]; then Print_Error 'Filename ' --emph "${file}" ' not matching expected name.' return 1 fi - values=($(awk 'NR > 3 {print $2, $3}' "${HYBRID_scan_combinations_filename}")) - sqrt_snn="${values[0]}" - eta_s="${values[1]}" - sqrt_snn_gte=$(bc <<< "${sqrt_snn} >= \ - ${list_of_parameters_values['IC.Software_keys.Modi.Collider.Sqrtsnn']:1:-1}") - sqrt_snn_lte=$(bc <<< "${sqrt_snn} <= \ - ${list_of_parameters_values['IC.Software_keys.Modi.Collider.Sqrtsnn']:1:-1}") - eta_s_gte=$(bc <<< "${eta_s} >= ${list_of_parameters_values['Hydro.Software_keys.etaS']:1:-1}") - eta_s_lte=$(bc <<< "${eta_s} <= ${list_of_parameters_values['Hydro.Software_keys.etaS']:1:-1}") - - if ((sqrt_snn_gte == 1 && sqrt_snn_lte == 1 && eta_s_gte == 1 && eta_s_lte == 1)); then - Print_Error 'Values of ' --emph 'Sqrtsnn' ' or ' --emph 'etaS' ' wrongly set in output file.' + values=($1) # Use word splitting to split values + shift + sqrt_snn=$(Read_From_YAML_String_Given_Key "$(< "${file}")" 'IC' 'Software_keys' 'Modi' 'Collider' 'Sqrtsnn') + if [[ "${sqrt_snn}" != "${values[0]}" ]]; then + Print_Error 'Value of ' --emph 'Sqrtsnn' ' wrongly set in output file.' + return 1 + fi + eta_s=$(Read_From_YAML_String_Given_Key "$(< "${file}")" 'Hydro' 'Software_keys' 'etaS') + if [[ "${eta_s}" != "${values[1]}" ]]; then + Print_Error 'Value of ' --emph 'etaS' ' wrongly set in output file.' return 1 fi done diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash index 58316fc..d7b3a80 100644 --- a/tests/unit_tests_scan_values_operations.bash +++ b/tests/unit_tests_scan_values_operations.bash @@ -45,8 +45,8 @@ function Make_Test_Preliminary_Operations__scan-create-list-LHS() source "${HYBRIDT_repository_top_level_path}/bash/${file_to_be_sourced}" || exit ${HYBRID_fatal_builtin} done Define_Further_Global_Variables - HYBRID_scan_strategy='LHS' HYBRID_number_of_samples=3 + HYBRID_scan_strategy='LHS' } function Unit_Test__scan-create-list-LHS() From 061a14edebad59f7d0f006323d5713f7e9924041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Tue, 12 Mar 2024 19:15:55 +0100 Subject: [PATCH 445/549] Improve removing scan fields from sampled configs --- bash/global_variables.bash | 2 +- bash/sanity_checks.bash | 21 +++---- bash/scan_files_operations.bash | 39 +++++------- bash/scan_validation.bash | 65 ++++++++++---------- bash/scan_values_operations.bash | 4 +- python/latin_hypercube_sampling.py | 7 +-- tests/unit_tests_scan_files_operations.bash | 20 +++--- tests/unit_tests_scan_values_operations.bash | 35 ++++++----- 8 files changed, 92 insertions(+), 101 deletions(-) diff --git a/bash/global_variables.bash b/bash/global_variables.bash index a9d5dba..ab0b45a 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -111,7 +111,6 @@ function Define_Further_Global_Variables() HYBRID_configuration_file='./config.yaml' HYBRID_output_directory="$(realpath './data')" HYBRID_scan_directory="${HYBRID_output_directory}/scan" - HYBRID_scan_strategy='Combinations' # Variables to be set (and possibly made readonly) from configuration/setup HYBRID_run_id="Run_$(date +'%Y-%m-%d_%H%M%S')" HYBRID_number_of_samples="${HYBRID_default_number_of_samples}" @@ -169,6 +168,7 @@ function Define_Further_Global_Variables() [Spectators]='' [Afterburner]='' ) + HYBRID_scan_strategy='Combinations' } Make_Functions_Defined_In_This_File_Readonly diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index a5feea1..97c191b 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -166,26 +166,23 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() --emph 'parameter-scan' ' execution mode.' fi done - if [[ "${HYBRID_number_of_samples}" =~ '^[1-9][0-9]*$' ]]; then + if [[ "${HYBRID_number_of_samples}" != "${HYBRID_default_number_of_samples}" ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'Number of samples can only be specified in ' \ 'the prepare-scan mode!' - return 1 fi ;; prepare-scan) - if [[ "${HYBRID_number_of_samples}" -eq 0 ]]; then + if [[ ! "${HYBRID_number_of_samples}" =~ ^[1-9][0-9]*$ ]] \ + || [[ "${HYBRID_number_of_samples}" -eq 1 ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'The number of samples for Latin Hypercube Sampling scan ' \ + 'has to be ' --emph 'a positive integer greater than 1' '.' + elif [[ "${HYBRID_number_of_samples}" -eq 0 ]]; then readonly HYBRID_scan_strategy='Combinations' else - if [[ "${HYBRID_number_of_samples}" -ge 2 ]]; then - readonly HYBRID_scan_strategy='LHS' - readonly HYBRID_number_of_samples - else - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Number of samples has to be a positive integer greater than 1 ' \ - 'for Latin Hypercube Sampling scan.' - return 1 - fi + readonly HYBRID_scan_strategy='LHS' + readonly HYBRID_number_of_samples fi ;; help) ;; # This is the default mode which is set in tests -> do nothing, but catch it diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 2ea5212..aae4e71 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -27,11 +27,11 @@ function Create_And_Populate_Scan_Folder() scan_combinations_file="${HYBRID_scan_directory}/${HYBRID_scan_combinations_filename}" local auxiliary_string parameters_names parameters_values parameters_combinations auxiliary_string=$(__static__Get_Fixed_Order_Parameters) - readarray -t parameters_names < <(printf "${auxiliary_string}") + readarray -t parameters_names < <(printf '%s'"${auxiliary_string}") auxiliary_string=$(__static__Get_Fixed_Order_Parameters_Values) - readarray -t parameters_values < <(printf -- "${auxiliary_string}") + readarray -t parameters_values < <(printf '%s' "${auxiliary_string}") auxiliary_string=$(__static__Get_Parameters_Combinations_For_New_Configuration_Files "${parameters_values[@]}") - readarray -t parameters_combinations < <(printf -- "${auxiliary_string}") + readarray -t parameters_combinations < <(printf '%s' "${auxiliary_string}") readonly parameters_names parameters_values parameters_combinations __static__Validate_And_Create_Scan_Folder __static__Create_Combinations_File_With_Metadata_Header_Block @@ -101,21 +101,16 @@ function __static__Get_All_Parameters_Combinations() # values. Each single sample is then created by taking one value from each list. function __static__Get_Samples_for_LHS() { - for ((index = 0; index < ${HYBRID_number_of_samples}; index++)); do - first_parameter=true - for parameter in "$@"; do - value=$(python3 -c "print(${parameter}[${index}])") - if $first_parameter; then - printf '%s' "$value" - first_parameter=false - else - printf ' %s' "$value" - fi - done - if [[ $index -ne $((${HYBRID_number_of_samples} - 1)) ]]; then - printf '\n' - fi - done + local -r series_of_lists=$( + IFS=',' + printf '%s' "$*" + ) + python -c " +import numpy as np +data = np.array([${series_of_lists}]) +for i in range(${HYBRID_number_of_samples}): + print(*data.transpose()[i]) +" } function __static__Validate_And_Create_Scan_Folder() @@ -182,7 +177,7 @@ function __static__Add_Line_To_Combinations_File() { printf '%7d' $(($1 + 1)) shift - printf ' %11s' "$@" + printf ' %20s' "$@" printf '\n' } >> "${scan_combinations_file}" } @@ -192,7 +187,6 @@ function __static__Create_Single_Output_File_In_Scan_Folder() Ensure_That_Given_Variables_Are_Set_And_Not_Empty parameters_names filename local -r run_number=$(($1 + 1)) shift - local -r set_of_values=("$@") Internally_Ensure_Given_Files_Do_Not_Exist "${filename}" __static__Add_Parameters_Comment_Line_To_New_Configuration_File @@ -228,7 +222,6 @@ function __static__Add_YAML_Configuration_To_New_Configuration_File() function __static__Remove_Scan_Parameters_Key_From_New_Configuration_File() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty filename - sed -i '/^[[:space:]]*Scan_parameters:/d' "${filename}" - sed -i '/^[[:space:]]*LHS_scan:/d' "${filename}" - + yq -i 'del(.Hybrid_handler.LHS_scan) | del(.Hybrid_handler | select(length==0))' "${filename}" + yq -i 'del(.. | select(has("Scan_parameters")).Scan_parameters)' "${filename}" } diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index 42ec239..3daf43a 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -139,42 +139,15 @@ function __static__Check_If_Keys_Of_Given_Scan_Have_Correct_Values() fi } -function __static__Validate_YAML_Numeric_Sequence_Key() -{ - if [[ $(yq '.Scan.'$1' | tag' <<< "${given_scan}") != '!!seq' ]]; then - Print_Error \ - 'The value ' --emph "$(yq '.Scan.'$1 <<< "${given_scan}")" \ - ' of the ' --emph $1 ' key is not a range of parameter values.' - return 1 - fi - local list_of_value_types num_values first_value second_value - list_of_value_types=($(yq '.Scan.'$1'[] | tag' <<< "${given_scan}" | sort -u)) - # If there is more than a type it is in general an error, unless there are exactly two, - # which are 'int' and 'float' (this is accepted). Note that the test is done on the - # concatenation of the values against 'float int' as the types are guaranteed to be sorted. - if [[ ${#list_of_value_types[@]} -ne 1 ]]; then - if [[ ${#list_of_value_types[@]} -ne 2 || "${list_of_value_types[*]//!!/}" != 'float int' ]]; then - Print_Error \ - 'The parameter values have different YAML types: ' \ - --emph "${list_of_value_types[*]//!!/}" '.' - return 1 - fi - elif [[ ! ${list_of_value_types[0]} =~ ^!!(bool|int|float)$ ]]; then - Print_Error \ - 'Parameter scans with values of ' --emph "${list_of_value_types[0]//!!/}" \ - ' type are not allowed.' 'Valid parameter types are ' --emph 'bool int float' ', only.' - return 1 - fi -} - function __static__Has_Valid_Scan_Correct_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys case "${sorted_scan_keys}" in "[Values]") - __static__Validate_YAML_Numeric_Sequence_Key 'Values' + __static__Validate_YAML_Numeric_Sequence_Value 'Values' ;; "[Range]") + __static__Validate_YAML_Numeric_Sequence_Value 'Range' || return 1 num_values=$(yq '.Scan.Range | length' <<< "${given_scan}") if ((num_values != 2)); then Print_Error \ @@ -182,10 +155,6 @@ function __static__Has_Valid_Scan_Correct_Values() 'The given range is ' --emph "${num_values}" ' long.' return 1 fi - __static__Validate_YAML_Numeric_Sequence_Key 'Range' - if [[ $? -eq 1 ]]; then - return 1 - fi if [[ $(yq '.Scan.Range[0] > .Scan.Range[1]' <<< "${given_scan}") == "true" ]]; then Print_Error \ 'The first value must be smaller than the second value in the Range. ' \ @@ -199,3 +168,33 @@ function __static__Has_Valid_Scan_Correct_Values() ;; esac } + +function __static__Validate_YAML_Numeric_Sequence_Value() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan + local -r key=$1 + if [[ $(yq ".Scan.${key} | tag" <<< "${given_scan}") != '!!seq' ]]; then + Print_Error \ + 'The value ' --emph "$(yq ".Scan.${key}" <<< "${given_scan}")" \ + ' of the ' --emph "${key}" ' key is not a range of parameter values.' + return 1 + fi + local list_of_value_types num_values first_value second_value + list_of_value_types=($(yq ".Scan.${key}[] | tag" <<< "${given_scan}" | sort -u)) + # If there is more than a type it is in general an error, unless there are exactly two, + # which are 'int' and 'float' (this is accepted). Note that the test is done on the + # concatenation of the values against 'float int' as the types are guaranteed to be sorted. + if [[ ${#list_of_value_types[@]} -ne 1 ]]; then + if [[ ${#list_of_value_types[@]} -ne 2 || "${list_of_value_types[*]//!!/}" != 'float int' ]]; then + Print_Error \ + 'The parameter values have different YAML types: ' \ + --emph "${list_of_value_types[*]//!!/}" '.' + return 1 + fi + elif [[ ! ${list_of_value_types[0]} =~ ^!!(bool|int|float)$ ]]; then + Print_Error \ + 'Parameter scans with values of ' --emph "${list_of_value_types[0]//!!/}" \ + ' type are not allowed.' 'Valid parameter types are ' --emph 'bool int float' ', only.' + return 1 + fi +} diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index 70453f4..e6035c8 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -19,7 +19,7 @@ function Create_List_Of_Parameters_Values() local parameter declare -A list_of_parameters_ranges for parameter in "${!list_of_parameters_values[@]}"; do - __static__Generate_And_Store_Parameter_List_Of_Ranges "${parameter}" + __static__Generate_And_Store_Parameter_Ranges "${parameter}" done local key value while IFS='=' read -r key value; do @@ -67,7 +67,7 @@ function __static__Generate_And_Store_Parameter_List_Of_Values() esac } -function __static__Generate_And_Store_Parameter_List_Of_Ranges() +function __static__Generate_And_Store_Parameter_Ranges() { Ensure_That_Given_Variables_Are_Set list_of_parameters_ranges local -r parameter=$1 scan_map="${list_of_parameters_values["$1"]}" diff --git a/python/latin_hypercube_sampling.py b/python/latin_hypercube_sampling.py index 06d590d..43cc9be 100644 --- a/python/latin_hypercube_sampling.py +++ b/python/latin_hypercube_sampling.py @@ -24,14 +24,14 @@ def generate_points_from_ranges(ranges, unit_points): help="Names of the parameters to be sampled.") parser.add_argument("--parameter_ranges", required = True, nargs='+', help="Ranges of the parameters to be sampled.") - parser.add_argument("--num_samples", required = True, + parser.add_argument("--num_samples", required = True, help="Number of samples to be drawn.") args = parser.parse_args() parameter_names = args.parameter_names parameter_ranges = args.parameter_ranges if len(parameter_names) != len(parameter_ranges) or int(args.num_samples) < 2: raise ValueError("The number of parameter names and parameter ranges must match and" - +"number of samples must be greater 0") + +"number of samples must be greater 1") parameter_ranges = np.array([ast.literal_eval(i) for i in parameter_ranges]) unit = lhs(parameter_ranges.shape[0], samples=int(args.num_samples), criterion='centermaximin') result=generate_points_from_ranges(parameter_ranges, unit).transpose() @@ -39,10 +39,9 @@ def generate_points_from_ranges(ranges, unit_points): for i in range(len(parameter_names)): return_string += parameter_names[i] + "=[" for j in range(result.shape[1]): - return_string += str(result[i][j]) + return_string += str(result[i][j]) if j != result.shape[1]-1: return_string += "," else: return_string += "]\n" - print(return_string, end='') diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index 623b8fb..f97715c 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -42,12 +42,12 @@ function Unit_Test__scan-create-single-file() # Parameter_2: Hydro.Software_keys.etaS # #___Run Parameter_1 Parameter_2 - 1 4.3 0.13 - 2 4.3 0.15 - 3 4.3 0.17 - 4 7.7 0.13 - 5 7.7 0.15 - 6 7.7 0.17 + 1 4.3 0.13 + 2 4.3 0.15 + 3 4.3 0.17 + 4 7.7 0.13 + 5 7.7 0.15 + 6 7.7 0.17 EOF HYBRID_scan_directory='scan_test' Call_Codebase_Function Create_And_Populate_Scan_Folder &> /dev/null 9>&1 # Suppress progress bar, too @@ -127,8 +127,8 @@ function Unit_Test__scan-create-single-file-LHS() # Parameter_2: Hydro.Software_keys.etaS # #___Run Parameter_1 Parameter_2 - 1 4.3 -0.13 - 2 7.7 0.17 + 1 4.3 -0.13 + 2 7.7 0.17 EOF HYBRID_scan_directory='scan_test' Call_Codebase_Function Create_And_Populate_Scan_Folder &> /dev/null 9>&1 # Suppress progress bar, too @@ -150,11 +150,11 @@ EOF fi continue fi - if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_run_[1-6]\.yaml$ ]]; then + if [[ ! "${file}" =~ ^${HYBRID_scan_directory}_run_[12]\.yaml$ ]]; then Print_Error 'Filename ' --emph "${file}" ' not matching expected name.' return 1 fi - values=($1) # Use word splitting to split values + values=(${1}) # Use word splitting to split values shift sqrt_snn=$(Read_From_YAML_String_Given_Key "$(< "${file}")" 'IC' 'Software_keys' 'Modi' 'Collider' 'Sqrtsnn') if [[ "${sqrt_snn}" != "${values[0]}" ]]; then diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash index d7b3a80..3dc6c30 100644 --- a/tests/unit_tests_scan_values_operations.bash +++ b/tests/unit_tests_scan_values_operations.bash @@ -51,29 +51,32 @@ function Make_Test_Preliminary_Operations__scan-create-list-LHS() function Unit_Test__scan-create-list-LHS() { - declare -gA list_of_parameters_values=( + declare -A list_of_parameters_values=( ['IC.Software_keys.Modi.Collider.Sqrtsnn']='{Range: [4.3, 7.7]}' ['Hydro.Software_keys.etaS']='{Range: [-0.13, 0.17]}' ) + declare -A ranges=( + ['IC.Software_keys.Modi.Collider.Sqrtsnn']='[4.3, 7.7]' + ['Hydro.Software_keys.etaS']='[-0.13, 0.17]' + ) Call_Codebase_Function Create_List_Of_Parameters_Values for key in "${!list_of_parameters_values[@]}"; do - expected_value="${list_of_parameters_values[$key]}" - actual_value="${list_of_parameters_values[$key]}" - if [[ "$actual_value" != "$expected_value" ]]; then - Print_Error "Parameter values list for '$key' was not correctly created." \ - "Expected: '$expected_value', Actual: '$actual_value'." - return 1 - fi - done - - # Check if each key has a list with exactly three values - for key in "${!list_of_parameters_values[@]}"; do - value="${list_of_parameters_values[$key]}" - value_count=$(echo "$value" | tr -cd ',' | wc -c) + local temp_array lower_bound upper_bound actual_values value_count + temp_array=($(printf "%s" "${ranges[$key]}" | grep -o '[0-9.-]*')) + lower_bound="${temp_array[0]}" + upper_bound="${temp_array[1]}" + actual_values="${list_of_parameters_values[$key]}" + value_count=$(grep -o ',' <<< "${actual_values}" | wc -l) if [ "$value_count" -ne 2 ]; then - Print_Error "Key $key does not have exactly three values in its list." + Print_Error "Key ${key} does not have exactly three values in its list." return 1 fi + IFS=',' read -r -a actual_values <<< "${actual_values:1:-1}" + for value in "${actual_values[@]}"; do + if (($(bc <<< "${value} < ${lower_bound}"))) || (($(bc <<< "${value} > ${upper_bound}"))); then + Print_Error "Parameter values list for key ${key} was not correctly created." + return 1 + fi + done done - } From bb249b311c5831d0db433e89d4db103ddf32ad37 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 08:53:09 +0100 Subject: [PATCH 446/549] Fix header width in scan output configuration files --- bash/scan_files_operations.bash | 2 +- tests/unit_tests_scan_files_operations.bash | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index aae4e71..20aef1b 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -131,7 +131,7 @@ function __static__Create_Combinations_File_With_Metadata_Header_Block() done printf '#\n#___Run' for index in "${!parameters_names[@]}"; do - printf ' Parameter_%d' $((index + 1)) + printf ' %20s' "Parameter_$((index + 1))" done printf '\n' } > "${scan_combinations_file}" diff --git a/tests/unit_tests_scan_files_operations.bash b/tests/unit_tests_scan_files_operations.bash index f97715c..e98a0d4 100644 --- a/tests/unit_tests_scan_files_operations.bash +++ b/tests/unit_tests_scan_files_operations.bash @@ -41,7 +41,7 @@ function Unit_Test__scan-create-single-file() # Parameter_1: IC.Software_keys.Modi.Collider.Sqrtsnn # Parameter_2: Hydro.Software_keys.etaS # -#___Run Parameter_1 Parameter_2 +#___Run Parameter_1 Parameter_2 1 4.3 0.13 2 4.3 0.15 3 4.3 0.17 @@ -126,7 +126,7 @@ function Unit_Test__scan-create-single-file-LHS() # Parameter_1: IC.Software_keys.Modi.Collider.Sqrtsnn # Parameter_2: Hydro.Software_keys.etaS # -#___Run Parameter_1 Parameter_2 +#___Run Parameter_1 Parameter_2 1 4.3 -0.13 2 7.7 0.17 EOF From 41c862e21c6e8e94d5d201eaf84400d5b7cbba81 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 08:58:27 +0100 Subject: [PATCH 447/549] Improve how LHS python script is run and use python3 command --- bash/global_variables.bash | 1 + bash/scan_files_operations.bash | 2 +- bash/scan_values_operations.bash | 2 +- python/latin_hypercube_sampling.py | 0 4 files changed, 3 insertions(+), 2 deletions(-) mode change 100644 => 100755 python/latin_hypercube_sampling.py diff --git a/bash/global_variables.bash b/bash/global_variables.bash index ab0b45a..6da88e7 100644 --- a/bash/global_variables.bash +++ b/bash/global_variables.bash @@ -32,6 +32,7 @@ function Define_Further_Global_Variables() readonly HYBRID_default_number_of_samples=0 declare -rgA HYBRID_external_python_scripts=( [Add_spectators_from_IC]="${HYBRID_python_folder}/add_spectators.py" + [Latin_hypercube_sampling]="${HYBRID_python_folder}/latin_hypercube_sampling.py" ) declare -rgA HYBRID_software_default_input_filename=( [IC]='' diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 20aef1b..82b4f48 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -105,7 +105,7 @@ function __static__Get_Samples_for_LHS() IFS=',' printf '%s' "$*" ) - python -c " + python3 -c " import numpy as np data = np.array([${series_of_lists}]) for i in range(${HYBRID_number_of_samples}): diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index e6035c8..93b8c14 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -29,7 +29,7 @@ function Create_List_Of_Parameters_Values() Print_Internal_And_Exit \ 'Processing of parameters failed in ' --emph "${FUNCNAME}" ' function.' fi - done < <(python3 ${HYBRID_python_folder}/latin_hypercube_sampling.py \ + done < <(${HYBRID_external_python_scripts[Latin_hypercube_sampling]} \ --parameter_names "${!list_of_parameters_ranges[@]}" \ --parameter_ranges "${list_of_parameters_ranges[@]}" \ --num_samples ${HYBRID_number_of_samples}) diff --git a/python/latin_hypercube_sampling.py b/python/latin_hypercube_sampling.py old mode 100644 new mode 100755 From 3ff47c5ab2eaed194f2f860f07dfc60c74069efc Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 09:20:47 +0100 Subject: [PATCH 448/549] Simplify yq code to validate range --- bash/scan_validation.bash | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index 3daf43a..49518c1 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -148,17 +148,19 @@ function __static__Has_Valid_Scan_Correct_Values() ;; "[Range]") __static__Validate_YAML_Numeric_Sequence_Value 'Range' || return 1 - num_values=$(yq '.Scan.Range | length' <<< "${given_scan}") + local range num_values + range=$(yq '.Scan.Range | .. style="flow"' <<< "${given_scan}") + num_values=$(yq '. | length' <<< "${range}") if ((num_values != 2)); then Print_Error \ 'Exactly two values are expected for the range. ' \ - 'The given range is ' --emph "${num_values}" ' long.' + 'The given range contains ' --emph "${num_values}" ' values.' return 1 fi - if [[ $(yq '.Scan.Range[0] > .Scan.Range[1]' <<< "${given_scan}") == "true" ]]; then + if [[ $(yq '.[0] > .[1]' <<< "${range}") == "true" ]]; then Print_Error \ 'The first value must be smaller than the second value in the Range. ' \ - 'The given range is ' --emph "$(yq '.Scan.Range' <<< "${given_scan}")" '.' + 'The given range is ' --emph "${range}" '.' return 1 fi ;; From 9eb6399983be350c4f5e57ff02101692d8244988 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 09:46:23 +0100 Subject: [PATCH 449/549] Extract function to avoid code duplication --- bash/scan_values_operations.bash | 36 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index 93b8c14..8819292 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -50,39 +50,35 @@ function Create_List_Of_Parameters_Values() function __static__Generate_And_Store_Parameter_List_Of_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values - local -r parameter=$1 scan_map="${list_of_parameters_values["$1"]}" - local sorted_scan_keys - sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" - readonly sorted_scan_keys - case "${sorted_scan_keys}" in - "[Values]") - list_of_parameters_values["${parameter}"]=$( - yq '.Values | .. style="flow"' <<< "${scan_map}" - ) - ;; - *) - Print_Internal_And_Exit \ - 'Unknown ' --emph "${sorted_scan_keys}" 'scan in ' --emph "${FUNCNAME}" ' function.' - ;; - esac + __static__Extract_Parameter_Attribute_And_Store_It 'Values' "$1" 'list_of_parameters_values' } function __static__Generate_And_Store_Parameter_Ranges() { Ensure_That_Given_Variables_Are_Set list_of_parameters_ranges - local -r parameter=$1 scan_map="${list_of_parameters_values["$1"]}" + __static__Extract_Parameter_Attribute_And_Store_It 'Range' "$1" 'list_of_parameters_ranges' +} + +function __static__Extract_Parameter_Attribute_And_Store_It() +{ + Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values + local -r \ + entity=$1 \ + parameter=$2 \ + scan_map="${list_of_parameters_values[$2]}" + declare -n reference_to_storage=$3 local sorted_scan_keys sorted_scan_keys="$(yq '. | keys | sort | .. style="flow"' <<< "${scan_map}")" readonly sorted_scan_keys case "${sorted_scan_keys}" in - "[Range]") - list_of_parameters_ranges["${parameter}"]=$( - yq '.Range | .. style="flow"' <<< "${scan_map}" + "[Range]" | "[Values]") + reference_to_storage["${parameter}"]=$( + yq '.'"${entity}"' | .. style="flow"' <<< "${scan_map}" ) ;; *) Print_Internal_And_Exit \ - 'Unknown ' --emph "${sorted_scan_keys}" 'scan in ' --emph "${FUNCNAME}" ' function.' + 'Unknown ' --emph "${sorted_scan_keys}" 'scan in ' --emph "${FUNCNAME[1]}" ' function.' ;; esac } From c18e7aa6aecc280b4b9f66a489d690b8108cff6c Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 09:58:30 +0100 Subject: [PATCH 450/549] Improve unit test implementation --- tests/unit_tests_scan_values_operations.bash | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/unit_tests_scan_values_operations.bash b/tests/unit_tests_scan_values_operations.bash index 3dc6c30..572ff7f 100644 --- a/tests/unit_tests_scan_values_operations.bash +++ b/tests/unit_tests_scan_values_operations.bash @@ -60,18 +60,16 @@ function Unit_Test__scan-create-list-LHS() ['Hydro.Software_keys.etaS']='[-0.13, 0.17]' ) Call_Codebase_Function Create_List_Of_Parameters_Values + local lower_bound upper_bound actual_values value for key in "${!list_of_parameters_values[@]}"; do - local temp_array lower_bound upper_bound actual_values value_count - temp_array=($(printf "%s" "${ranges[$key]}" | grep -o '[0-9.-]*')) - lower_bound="${temp_array[0]}" - upper_bound="${temp_array[1]}" - actual_values="${list_of_parameters_values[$key]}" - value_count=$(grep -o ',' <<< "${actual_values}" | wc -l) - if [ "$value_count" -ne 2 ]; then + lower_bound=$(yq '.[0]' <<< "${ranges[${key}]}") + upper_bound=$(yq '.[1]' <<< "${ranges[${key}]}") + actual_values="${list_of_parameters_values[${key}]}" + readarray -t actual_values < <(yq --unwrapScalar '.[]' <<< "${list_of_parameters_values[${key}]}") + if [ "${#actual_values[@]}" -ne 3 ]; then Print_Error "Key ${key} does not have exactly three values in its list." return 1 fi - IFS=',' read -r -a actual_values <<< "${actual_values:1:-1}" for value in "${actual_values[@]}"; do if (($(bc <<< "${value} < ${lower_bound}"))) || (($(bc <<< "${value} > ${upper_bound}"))); then Print_Error "Parameter values list for key ${key} was not correctly created." From 6a54927973720542d9b42179b53a059f962ee4e4 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 09:58:55 +0100 Subject: [PATCH 451/549] Add amplification comment and minor adjustments --- bash/sanity_checks.bash | 2 +- bash/scan_files_operations.bash | 2 +- bash/scan_validation.bash | 6 +++--- bash/scan_values_operations.bash | 6 ++++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 97c191b..06bc1de 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -177,7 +177,7 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() || [[ "${HYBRID_number_of_samples}" -eq 1 ]]; then exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ 'The number of samples for Latin Hypercube Sampling scan ' \ - 'has to be ' --emph 'a positive integer greater than 1' '.' + 'has to be ' --emph 'an integer greater than 1' '.' elif [[ "${HYBRID_number_of_samples}" -eq 0 ]]; then readonly HYBRID_scan_strategy='Combinations' else diff --git a/bash/scan_files_operations.bash b/bash/scan_files_operations.bash index 82b4f48..17eb0c9 100644 --- a/bash/scan_files_operations.bash +++ b/bash/scan_files_operations.bash @@ -98,7 +98,7 @@ function __static__Get_All_Parameters_Combinations() } # For Latin Hypercube Sampling, a python script creates for each parameter a list of -# values. Each single sample is then created by taking one value from each list. +# values. Each single sample is then created by taking one value from each list in order. function __static__Get_Samples_for_LHS() { local -r series_of_lists=$( diff --git a/bash/scan_validation.bash b/bash/scan_validation.bash index 49518c1..b776b83 100644 --- a/bash/scan_validation.bash +++ b/bash/scan_validation.bash @@ -144,10 +144,10 @@ function __static__Has_Valid_Scan_Correct_Values() Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan sorted_scan_keys case "${sorted_scan_keys}" in "[Values]") - __static__Validate_YAML_Numeric_Sequence_Value 'Values' + __static__Has_YAML_Key_A_Numeric_Sequence_As_Value 'Values' || return 1 ;; "[Range]") - __static__Validate_YAML_Numeric_Sequence_Value 'Range' || return 1 + __static__Has_YAML_Key_A_Numeric_Sequence_As_Value 'Range' || return 1 local range num_values range=$(yq '.Scan.Range | .. style="flow"' <<< "${given_scan}") num_values=$(yq '. | length' <<< "${range}") @@ -171,7 +171,7 @@ function __static__Has_Valid_Scan_Correct_Values() esac } -function __static__Validate_YAML_Numeric_Sequence_Value() +function __static__Has_YAML_Key_A_Numeric_Sequence_As_Value() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty given_scan local -r key=$1 diff --git a/bash/scan_values_operations.bash b/bash/scan_values_operations.bash index 8819292..9f16574 100644 --- a/bash/scan_values_operations.bash +++ b/bash/scan_values_operations.bash @@ -14,13 +14,16 @@ function Create_List_Of_Parameters_Values() { Ensure_That_Given_Variables_Are_Set_And_Not_Empty list_of_parameters_values + local parameter case "${HYBRID_scan_strategy}" in 'LHS') - local parameter declare -A list_of_parameters_ranges for parameter in "${!list_of_parameters_values[@]}"; do __static__Generate_And_Store_Parameter_Ranges "${parameter}" done + # Here the python script which generates the parameters values is assumed to print + # lists of values in the form: 'parameter.name=[x1,x2,x3,...]' and this is parsed + # back into the 'list_of_parameters_values' array in a while-read construct. local key value while IFS='=' read -r key value; do if Element_In_Array_Equals_To "${key}" "${!list_of_parameters_values[@]}"; then @@ -35,7 +38,6 @@ function Create_List_Of_Parameters_Values() --num_samples ${HYBRID_number_of_samples}) ;; 'Combinations') - local parameter for parameter in "${!list_of_parameters_values[@]}"; do __static__Generate_And_Store_Parameter_List_Of_Values "${parameter}" done From 8c7a8109182d660c4c7338e2aa180bdea245c109 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 11:49:07 +0100 Subject: [PATCH 452/549] Make stricter check on LHS_scan key not in prepare-scan mode --- bash/configuration_parser.bash | 9 ++++++++- bash/sanity_checks.bash | 5 ----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bash/configuration_parser.bash b/bash/configuration_parser.bash index e6518d4..32d587d 100644 --- a/bash/configuration_parser.bash +++ b/bash/configuration_parser.bash @@ -146,7 +146,14 @@ function __static__Parse_Section() local yaml_section valid_key if Has_YAML_String_Given_Key "${yaml_config}" "${section_label}"; then yaml_section="$(Read_From_YAML_String_Given_Key "${yaml_config}" "${section_label}")" - if [[ ${section_label} != 'Hybrid_handler' ]]; then + if [[ ${section_label} = 'Hybrid_handler' ]]; then + if Has_YAML_String_Given_Key "${yaml_section}" 'LHS_scan' \ + && [[ "${HYBRID_execution_mode}" != 'prepare-scan' ]]; then + exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ + 'The ' --emph 'LHS_scan' ' key can only be specified in the ' \ + --emph 'prepare-scan' ' execution mode!' + fi + else HYBRID_given_software_sections+=("${section_label}") fi declare -n reference_to_map="HYBRID_${section_label,,}_valid_keys" diff --git a/bash/sanity_checks.bash b/bash/sanity_checks.bash index 06bc1de..2022062 100644 --- a/bash/sanity_checks.bash +++ b/bash/sanity_checks.bash @@ -166,11 +166,6 @@ function __static__Perform_Logic_Checks_Depending_On_Execution_Mode() --emph 'parameter-scan' ' execution mode.' fi done - if [[ "${HYBRID_number_of_samples}" != "${HYBRID_default_number_of_samples}" ]]; then - exit_code=${HYBRID_fatal_logic_error} Print_Fatal_And_Exit \ - 'Number of samples can only be specified in ' \ - 'the prepare-scan mode!' - fi ;; prepare-scan) if [[ ! "${HYBRID_number_of_samples}" =~ ^[1-9][0-9]*$ ]] \ From 74a55253adb135e1f869792ff580e1b0e821cb51 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 11:57:13 +0100 Subject: [PATCH 453/549] Review and complete documentation --- docs/user/configuration_file.md | 11 +++++++++++ docs/user/scans_syntax.md | 33 +++++++++++++++++++++++++++++---- docs/user/scans_types.md | 7 ++++--- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/docs/user/configuration_file.md b/docs/user/configuration_file.md index 2ca3b58..478b900 100644 --- a/docs/user/configuration_file.md +++ b/docs/user/configuration_file.md @@ -24,6 +24,17 @@ Hybrid_handler: Run_ID: Cool_stuff_1 ``` + +???+ config-key "`LHS_scan`" + + This key can be provided only in `prepare-scan` execution mode and its presence enables the [Latin Hypercube Sampling](scans_syntax.md#latin-hypercube-sampling) algorithm to generate the combinations of parameters values. + **Its value** refers to the number of desired samples and it **must be an integer larger than 1**. + ```yaml title="Example" + Hybrid_handler: + LHS_scan: 10 + ``` + + ## The software sections Each stage of the model has a dedicated section. diff --git a/docs/user/scans_syntax.md b/docs/user/scans_syntax.md index d052362..156fd9f 100644 --- a/docs/user/scans_syntax.md +++ b/docs/user/scans_syntax.md @@ -15,7 +15,7 @@ Once scan parameters have been specified as such, they **must** appear in the `S However, their value should not be a simple parameter value, but a YAML map with a given format. In the following we will refer to this map as "scan object". The different allowed ways to specify scan objects are discussed in the following, providing an example for each of them. -The scan object shall always have a `Scan` key as single top-level key. +**The scan object shall always have a `Scan` key as single top-level key.** ```yaml title="Generic parameter scan specification" Scan_parameters: ["Parameter"] Software_keys: @@ -62,10 +62,11 @@ This is possible in the `Values` YAML array inside the `Scan` map. - 42 - 666 ``` + ### Latin Hypercube Sampling -Here, one has to give the range within which one wants to sample -This is possible in the `Range` YAML array inside the `Scan` map. +This type of scan has to be [explicitly enabled](configuration_file.md#LHS-scan) by using the `LHS_scan` key. +Once done so, for each scan parameter, a range in which the values will be sampled has to be specified using the `Range` YAML array inside the `Scan` map. === "Compact style" @@ -73,5 +74,29 @@ This is possible in the `Range` YAML array inside the `Scan` map. Scan_parameters: ["foo.bar"] Software_keys: foo: - bar: {Scan: {Range: [17, 666]}} + bar: {Scan: {Range: [-17, 666]}} + ``` + +=== "Mixed style" + + ```yaml title="Example" + Scan_parameters: ["foo.bar"] + Software_keys: + foo: + bar: + Scan: + Range: [-17, 666] + ``` + +=== "Extended style" + + ```yaml title="Example" + Scan_parameters: ["foo.bar"] + Software_keys: + foo: + bar: + Scan: + Range: + - 17 + - 666 ``` diff --git a/docs/user/scans_types.md b/docs/user/scans_types.md index cc145cc..09bdba4 100644 --- a/docs/user/scans_types.md +++ b/docs/user/scans_types.md @@ -4,7 +4,7 @@ If a parameter scan is created with more than one scan parameter, it has to be d ## All combinations by default -Unless differently specified, all combinations of parameters values are considered and one output handler configuration file per combination will be created. Alternativly, the key for specifying all combinations is the Hybrid handler key is `Scan_strategy: Combinations` +Unless differently specified, all combinations of parameters values are considered and one output handler configuration file per combination will be created. From the mathematical point of view, given $n$ scan parameters with set of values $X_1, ..., X_n\,$, the set of considered combinations is nothing but the $n$-ary Cartesian product over all sets of values, @@ -61,5 +61,6 @@ For example, specifying two different scan parameters with 2 and 5 values, respe ### Latin Hypercube Sampling -If `LHS_scan: n` is specified with any value but 0, Latin Hypercube Sampling is used. The value `n` is an integer greater 2. LHS samples multidimensional parameters near random, while keeping the distance between -samples maximal, and is commonly used for Bayesian inference. Refer to the [:link: wikipedia page](https://en.wikipedia.org/wiki/Latin_hypercube_sampling) for more information. The sampling itself is done by calling the [:link: PyDoe](https://pythonhosted.org/pyDOE/randomized.html#latin-hypercube) Python library function, using the `maximin` setting. \ No newline at end of file +This algorithm, [if enabled](configuration_file.md#LHS-scan), samples multidimensional parameters randomly, while keeping the distance between samples maximal, and is commonly used for Bayesian inference. +Refer to e.g. [:simple-wikipedia: this page](https://en.wikipedia.org/wiki/Latin_hypercube_sampling) for more information. +The sampling itself is done by calling the `lhs` function from the [:fontawesome-brands-python: pyDoe](https://pythonhosted.org/pyDOE/randomized.html#latin-hypercube) Python library function, using the `maximin` criterion. From 7beae637623e708367cdd8098b0b3dd0cb32124e Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 12:00:57 +0100 Subject: [PATCH 454/549] Install Python requirements in GitHub action --- .github/workflows/github-actions-on-github-servers.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index 2f3fa6d..37b6db0 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -47,9 +47,11 @@ jobs: # print the picked Python version - name: Display Python version run: python -c "import sys; print(sys.version)" - # install needed non-standard packages + # this Action should follow steps to set up Python build environment - name: Install Python dependencies - run: python -m pip install --upgrade pip numpy pyyaml + uses: py-actions/py-dependency-install@v4 + with: + path: "python/requirements.txt" # now everything should be ready to setup bash and run codebase tests - name: code_check # we set some environment variables which depend on the specific matrix element From 26b26df314d87847e3781e198a61ba1de2d570d9 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 13 Mar 2024 12:40:34 +0100 Subject: [PATCH 455/549] Bump system requirement for yq to version 4.24.2 This is the release of yq in which the comparison operators have been introduced. Since yq is such a handy and powerful tool, in a project already depending on it, I'd require a version that enables comparisons as this make yq handy in the shell to be used for floating point comparisons. Its installation is so trivial that bumping this requirement is not a problem at all. --- .github/workflows/github-actions-on-github-servers.yml | 4 ++-- bash/system_requirements.bash | 2 +- tests/unit_tests_system_requirements.bash | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/github-actions-on-github-servers.yml b/.github/workflows/github-actions-on-github-servers.yml index 37b6db0..c402d92 100644 --- a/.github/workflows/github-actions-on-github-servers.yml +++ b/.github/workflows/github-actions-on-github-servers.yml @@ -79,8 +79,8 @@ jobs: make -j$(nproc) > "${output_file}" 2>&1 || { cat "${output_file}"; exit 1; } BASH_EXE=$(pwd)/bash # now we install yq in the minimum version supported by the handler (otherwise a more recent version is available in GitHub actions) - printf "Download yq-v4.18.1...\n" - wget -nv -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.18.1/yq_linux_amd64 + printf "Download yq-v4.24.2...\n" + wget -nv -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.24.2/yq_linux_amd64 chmod +x /usr/local/bin/yq else BASH_EXE=$(which bash) diff --git a/bash/system_requirements.bash b/bash/system_requirements.bash index c259d4e..0a0ada3 100644 --- a/bash/system_requirements.bash +++ b/bash/system_requirements.bash @@ -24,7 +24,7 @@ function __static__Declare_System_Requirements() [git]='1.8.5' [sed]='4.2.1' [tput]='5.7' - [yq]='4.18.1' + [yq]='4.24.2' [python3]='3.0.0' ) declare -rga HYBRID_programs_just_required=( diff --git a/tests/unit_tests_system_requirements.bash b/tests/unit_tests_system_requirements.bash index 69d8e75..c6cd822 100644 --- a/tests/unit_tests_system_requirements.bash +++ b/tests/unit_tests_system_requirements.bash @@ -61,7 +61,7 @@ function Unit_Test__system-requirements() git_version=2.0 sed_version=4.2.1 tput_version=5.9 - yq_version=4.18.1 + yq_version=4.24.2 Call_Codebase_Function_In_Subshell Check_System_Requirements if [[ $? -ne 0 ]]; then Print_Error "Check system requirements of good system failed." From a6f1a5ad1a4893d9c83c2063397647c22ea89e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20G=C3=B6tz?= Date: Wed, 13 Mar 2024 14:30:44 +0100 Subject: [PATCH 456/549] Correct statement about LHS in user guide --- docs/user/scans_types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/scans_types.md b/docs/user/scans_types.md index 09bdba4..ae1f83f 100644 --- a/docs/user/scans_types.md +++ b/docs/user/scans_types.md @@ -63,4 +63,4 @@ For example, specifying two different scan parameters with 2 and 5 values, respe This algorithm, [if enabled](configuration_file.md#LHS-scan), samples multidimensional parameters randomly, while keeping the distance between samples maximal, and is commonly used for Bayesian inference. Refer to e.g. [:simple-wikipedia: this page](https://en.wikipedia.org/wiki/Latin_hypercube_sampling) for more information. -The sampling itself is done by calling the `lhs` function from the [:fontawesome-brands-python: pyDoe](https://pythonhosted.org/pyDOE/randomized.html#latin-hypercube) Python library function, using the `maximin` criterion. +The sampling itself is done by calling the `lhs` function from the [:fontawesome-brands-python: pyDoe](https://pythonhosted.org/pyDOE/randomized.html#latin-hypercube) Python library function, using the `centermaximin` criterion. From 4648dc17cb420bb5dc772fbc1af79fec5d7d7524 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Wed, 28 Feb 2024 15:15:45 +0100 Subject: [PATCH 457/549] Complete user guide with relevant information and tips --- docs/user/configuration_file.md | 7 +++++++ docs/user/prerequisites.md | 21 +++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/docs/user/configuration_file.md b/docs/user/configuration_file.md index 478b900..35d9b96 100644 --- a/docs/user/configuration_file.md +++ b/docs/user/configuration_file.md @@ -48,6 +48,13 @@ These are (with the corresponding software to be used): As a general comment, whenever a path has to be specified, both an absolute and a relative one are accepted. However, **it is strongly encouraged to exclusively use absolute paths** as relative ones should be specified w.r.t. different folders (most of the times relatively to the stage output directory). +!!! info "Enforced sanity rules" + Since the hybrid handler understands which software should be run from the presence of the corresponding section, there are a couple of totally natural rules that are enforced and will make the handler fail if violated. + + 1. At least one software section must be present in the configuration file. + 2. Software sections must be specified in order and without gaps. + This means that it is not possible to e.g. ask the handler to run the initial condition and the sampler stages. + ## Keys common to all software sections ???+ config-key "`Executable`" diff --git a/docs/user/prerequisites.md b/docs/user/prerequisites.md index 6b117cb..dff84eb 100644 --- a/docs/user/prerequisites.md +++ b/docs/user/prerequisites.md @@ -10,18 +10,35 @@ | [Hadron sampler](https://github.com/smash-transport/smash-hadron-sampler) | 1.0 or higher | | [Python](https://www.python.org) | 3.0 or higher | -[^1]: Version 3.1 is only needed for the afterburner functionality. Otherwise version 1.8 is sufficient. +[^1]: Version `3.1` is only needed for the afterburner functionality. Otherwise version `1.8` is sufficient. Instructions on how to compile or install the software above can be found at the provided links either in the official documentation or in the corresponding README files. -!!! warning "Be consistent w.r.t dependencies" +!!! warning "Be consistent about dependencies" The above prerequisites have in general additional dependencies and it is important to be consistent in compiler options when compiling these and the software itself. We particularly highlight that the newer versions of ROOT require C++17 bindings or higher, which calls for proper treatment of compiler options in SMASH and the hadron sampler. It is also recommended to start from a clean build directory whenever changing the compiler or linking to external libraries that were compiled with different compiler flags. +--- + # Unix system requirements The hybrid handler makes use of many tools which are usually installed on Unix systems. For some of them a minimum version is required and for the others their availability is enough. However, in some cases, the GNU version is required and on some Linux distribution or on Apple machines the default installation might not be suitable. To check out what is required and what is available on your system, simply run the `Hybrid-handler` executable without options: An overview of the main functionality as well as a system requirements overview will be produced. + +!!! tip "Use `brew` to install needed GNU utilities on Apple machines" + Often macOS is shipped with the BSD implementation of tools like `awk`, `sed`, `wc`, `sort` and many others. + Since the GNU version of some tools offers more functionality, it has been decided in few cases to prefer these, especially since most supercomputers in the scientific field have such a version installed by default. + However, on Apple machines, the [`brew` package manager](https://brew.sh) can be easily used to install the possibly missing utilities. + + Please, note that the needed commands must be available and automatically findable by your bash shell. + For example, installing GNU AWK via `brew install gawk` is not enough as, by default, it only provides the `gawk` command and the hybrid handler needs `awk` instead. + You then need to adjust the `PATH` environment variable as suggested by `brew` itself: + ```bash + export PATH="${HOMEBREW_PREFIX}/opt/gawk/libexec/gnubin:${PATH}" + ``` + Unfortunately, there is no standard way to figure out which implementation a command offers. + However, all GNU commands support the `--version` command line option and their output contains the `GNU` word. + This allows to understand if the needed GNU version is available or if the commands refers to something else. From 5ac8a29bf3fecc935bc49ece4e44e998e9cc428b Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 29 Feb 2024 11:31:56 +0100 Subject: [PATCH 458/549] Remove unnecessary paramter from utility function --- bash/utility_functions.bash | 7 +++---- tests/tests_runner | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 42568e7..84b8b23 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -89,10 +89,9 @@ function Print_Line_of_Equals() { local length indentation prefix postfix length="$1" - indentation="${2-}" # Input arg. or empty string - prefix="${3-}" # Input arg. or empty string - postfix="${4-\n}" # Input arg. or endline - printf "${prefix}${indentation}" + prefix="${2-}" # Input arg. or empty string + postfix="${3-\n}" # Input arg. or endline + printf "${prefix}" for ((i = 0; i < ${length}; i++)); do printf '=' done diff --git a/tests/tests_runner b/tests/tests_runner index a1242b9..af4181f 100755 --- a/tests/tests_runner +++ b/tests/tests_runner @@ -188,10 +188,10 @@ function Print_Tests_Report() separator_length=$((test_name_string_length + 3 + 2 * ${#left_margin})) passed_string="$(printf "Run %d test(s): %2d passed" ${HYBRIDT_tests_run} ${HYBRIDT_tests_passed})" failed_string="$(printf " ${HYBRIDT_tests_run//?/ } %2d failed" ${HYBRIDT_tests_failed})" - Print_Line_of_Equals "${separator_length}" "${indentation}" '\n\e[96m' '\e[0m\n' + Print_Line_of_Equals "${separator_length}" "\n\e[96m${indentation}" '\e[0m\n' Print_Centered_Line "${passed_string}" ${separator_length} "${indentation}" Print_Centered_Line "${failed_string}" ${separator_length} "${indentation}" - Print_Line_of_Equals "${separator_length}" "${indentation}" '\e[96m' '\e[0m\n' + Print_Line_of_Equals "${separator_length}" "\e[96m${indentation}" '\e[0m\n' fi if [[ ${HYBRIDT_report_level} -ge 2 ]]; then local name percentage @@ -199,13 +199,13 @@ function Print_Tests_Report() if($2!=0) {printf "%.0f%%", 100*$1/$2} else {printf "-- %"} }' <<< "${HYBRIDT_tests_passed} ${HYBRIDT_tests_run}") Print_Centered_Line "${percentage} of tests passed!" ${separator_length} "${indentation}" - Print_Line_of_Equals "${separator_length}" "${indentation}" '\e[96m' '\e[0m\n' + Print_Line_of_Equals "${separator_length}" "\e[96m${indentation}" '\e[0m\n' if [[ ${HYBRIDT_tests_failed} -ne 0 ]]; then printf "${indentation}${left_margin}\e[91mThe following tests failed:\e[0m\n" for name in "${HYBRIDT_which_tests_failed[@]}"; do printf "${indentation}${left_margin} - \e[93m${name}\e[0m\n" done - Print_Line_of_Equals "${separator_length}" "${indentation}" '\e[96m' '\e[0m\n' + Print_Line_of_Equals "${separator_length}" "\e[96m${indentation}" '\e[0m\n' fi fi if [[ ${HYBRIDT_tests_failed} -ne 0 ]]; then From ea84962058f53bfb0560de5ddb8048c1ce250ec3 Mon Sep 17 00:00:00 2001 From: Alessandro Sciarra Date: Thu, 29 Feb 2024 11:32:36 +0100 Subject: [PATCH 459/549] Add documentation of utility functions to develoepr guide --- bash/utility_functions.bash | 261 ++++++++++++++++++++++++---- docs/developer/utility_functions.md | 176 +++++++++++++++++++ docs/stylesheets/extra.css | 20 +++ mkdocs.yml | 9 +- 4 files changed, 425 insertions(+), 41 deletions(-) create mode 100644 docs/developer/utility_functions.md diff --git a/bash/utility_functions.bash b/bash/utility_functions.bash index 84b8b23..506ef31 100644 --- a/bash/utility_functions.bash +++ b/bash/utility_functions.bash @@ -7,6 +7,30 @@ # #=================================================== +# ATTENTION: Unless it makes sense to do it differently, each function here +# should have a short documentation before and then be included in +# the developer guide. Be consistent when adding a new function. +# Here a simple bash trick is used: A no-operation ':' followed by +# a here-doc is used to be able to put the markdown documentation +# inside and still make this be ignored by the shell. In the here-doc +# a couple of "snippet anchors" is used to be able to include the +# snippets in the developer guide. Each snippet begins and ends with +# '--8<-- [start:
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 FV1o24k%#~Q literal 201048 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%0D2{n_=?hq0eO^=Kih) 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 Do1x(KpLt1gl~kf!xMx_LrN7 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

{^v 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%0Dr9~Lrb!%!nB#*qvxtGwF$VjGSiUkxZGMrVffDA( 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