diff --git a/bin/default.yml b/bin/default.yml index d26c93ef..b3c18d55 100644 --- a/bin/default.yml +++ b/bin/default.yml @@ -3023,15 +3023,6 @@ SIM_verif_eclipse_calculator: compare: - models/environment/eclipse_calculator/verif/SIM_verif/RUN_moon_shadow/log_test_data.csv vs. models/environment/eclipse_calculator/verif/SIM_verif/verif_data/RUN_moon_shadow/log_test_data.csv -SIM_verif_env_utils: - model_dir: models/utilities/env_utils - path: models/utilities/env_utils/verif/SIM_verif - build_args: CML_OFFLINE_BUILD=1 - runs: - RUN_verif/input.py: - compare: - - models/utilities/env_utils/verif/SIM_verif/RUN_verif/log_test_data.csv vs. - models/utilities/env_utils/verif/SIM_verif/verif_data/RUN_verif/log_test_data.csv SIM_verif_events_manager: model_dir: models/vehicle_management/events_manager path: models/vehicle_management/events_manager/verif/SIM_verif diff --git a/docs/sphinx/CMakeLists.txt b/docs/sphinx/CMakeLists.txt index 906408a2..e663585a 100644 --- a/docs/sphinx/CMakeLists.txt +++ b/docs/sphinx/CMakeLists.txt @@ -28,6 +28,7 @@ if (TARGET Doxygen::doxygen AND Sphinx_FOUND) models/fhw/index.rst models/interactions/index.rst models/tools/index.rst + models/utilities/env-utils.rst models/utilities/index.rst models/utilities/subscriptions.rst models/vehicle_management/index.rst diff --git a/docs/sphinx/models/utilities/env-utils.rst b/docs/sphinx/models/utilities/env-utils.rst new file mode 100644 index 00000000..bf085c23 --- /dev/null +++ b/docs/sphinx/models/utilities/env-utils.rst @@ -0,0 +1,403 @@ +Environment Variable Utilities +++++++++++++++++++++++++++++++ + +.. list-table:: Revision History + :widths: 15 30 30 50 + :header-rows: 1 + + * - Version + - Date + - Author + - Purpose + * - 1 + - September 2026 + - Nino Tarantino + - Initial version + +.. contents:: Table of Contents + :local: + :class: this-will-duplicate-information-and-it-is-still-useful-here + +________________________________________________________ + +Introduction +============ + +The Environment Variable Utilities model provides C++ interfaces to retrieve environment variables +with a variety of selectable fallback behaviors of the requested variable is not set. + +________________________________________________________ + +Requirements +============ + +- **CML-ENV-UTILS-1**: The model shall provide an option to terminate the program if a requested environment variable is + not set. +- **CML-ENV-UTILS-2**: The model shall provide an option to return a default value if a requested environment variable is + not set. +- **CML-ENV-UTILS-3**: The model shall provide an option to throw an error if a requested environment variable is not set. +- **CML-ENV-UTILS-4**: The model shall provide a method for expanding environment variables in a user-defined string. + +________________________________________________________ + +Model Specifications +==================== + +Architectural Considerations +---------------------------- + +Existing External Capabilities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The C++ standard library has the ``std::getenv`` function, which exposes a C-based API. This model extends +that standard library function to perform error handling and environment variable expansion. + +Model Structure +~~~~~~~~~~~~~~~ + +.. doxygenfile:: env_utils.hh + +Mathematical Formulation +------------------------ + +No mathematical formulation. + +________________________________________________________ + +User's Guide +============ + +Retrieving an Environment Variable +---------------------------------- + +Exit if the Variable is Not Set +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Retrieve an environment variable's value, or else call ``std::exit(EXIT_FAILURE)`` if the +requested value is not set: + +.. code-block:: cpp + + const std::string var = getenv_or_exit("MY_ENV_VAR"); + +Alternatively, a user-defined exit function may be provided. This function will be called +with ``EXIT_FAILURE`` as its argument. + +.. code-block:: cpp + + auto my_exit_function = [](int return_value) -> void { + // Some user-defined logic goes here. + }; + const std::string var = getenv_or_exit("MY_ENV_VAR", my_exit_function); + + +Use a Default Value if the Variable is Not Set +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Define a default value to use if the requested environment variable is not set: + +.. code-block:: cpp + + const std::string var = getenv_or_default("MY_ENV_VAR", "fallback-value"); + + +Throw an Error if the Variable is Not Set +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Retrieve an environment variable's value, or else throw a runtime error: + +.. code-block:: cpp + + try { + const std::string var = getenv_or_throw("MY_ENV_VAR"); + } catch (const std::runtime_error& err) { + // Handle the missing variable. + } + + +Expanding Environment Variables in a String +------------------------------------------- + +Expand all environment variables in a string: + +.. code-block:: cpp + + const std::filesystem::path data_path = expand_env_variables("${DATA_DIR}/${SCENARIO_NAME}/data.csv"); + +Note that an ``std::runtime_error`` is always thrown if any environment variable in the +provided string is not set. + +Extension +--------- + +As the model is simply a collection of functions, users may augment it with their own +environment variable handling functions. + +________________________________________________________ + +Verification +============ + +Each model requirement has associated unit tests which verify it. The unit tests are described in the +:ref:`Unit-Test Cases ` section. + +Code Coverage +------------- + +.. code-block:: text + + ------------------------------------------------------------------------------ + GCC Code Coverage Report + Directory: . + ------------------------------------------------------------------------------ + File Lines Exec Cover Missing + ------------------------------------------------------------------------------ + include/env_utils.hh + 17 17 100% + src/env_utils.cc + 20 20 100% + ------------------------------------------------------------------------------ + TOTAL 37 37 100% + ------------------------------------------------------------------------------ + +See detailed coverage information `here `_. + +Exceptions +---------- + +N/A + + +.. _test-cases: + +Unit-Test Cases +--------------- + +Each requirement is verified by one or more unit tests constructed using the GoogleTest framework. + +.. table:: Requirements Traceability + :widths: 35 100 + + +-------------------------+-----------------------------------------------------------------------------------+ + | Requirement | Test(s) Which Verify It | + +=========================+===================================================================================+ + | CML-ENV-UTILS-1 | :ref:`EnvUtils.Exit ` | + +-------------------------+-----------------------------------------------------------------------------------+ + | CML-ENV-UTILS-2 | :ref:`EnvUtils.DefaultValue ` | + +-------------------------+-----------------------------------------------------------------------------------+ + | CML-ENV-UTILS-3 | :ref:`EnvUtils.Throw ` | + +-------------------------+-----------------------------------------------------------------------------------+ + | CML-ENV-UTILS-4 | :ref:`EnvUtils.ExpandBracedEnvironmentVariables ` | + | | :ref:`EnvUtils.ExpandUnBracedEnvironmentVariables ` | + | | :ref:`EnvUtils.ExpandEnvironmentVariablesEdgeCases ` | + +-------------------------+-----------------------------------------------------------------------------------+ + + +.. _exit-test: + +EnvUtils.Exit +~~~~~~~~~~~~~ + +*Purpose*: Demonstrate that the correct environment variable data is returned if the variable exists, or else calls an +exit-handling function. + +*Requirement*: Satisfactory conclusion of the test satisfies the verification of requirement **CML-ENV-UTILS-1**. + +*Procedure*: + +1. Set the contents of an environment variable, ``CML_ENVUTILS_TEST_VAR_EXIT_TEST``, equal to ``"test-value"``. +2. Query the contents of ``CML_ENVUTILS_TEST_VAR_EXIT_TEST`` using the :cpp:func:`getenv_or_exit` function. +3. Clear the contents of an environment variable, ``DOES_NOT_EXIST_EXIT_TEST``. +4. Query the contents of ``DOES_NOT_EXIST_EXIT_TEST`` using the :cpp:func:`getenv_or_exit` function. + +*Success Criteria*: The contents retrieved from the ``CML_ENVUTILS_TEST_VAR_EXIT_TEST`` environment variable +should match what they were set to in the first step of the test. Attempting to query the contents of the +``DOES_NOT_EXIST_EXIT_TEST`` environment variable should result in an exit-handling function being called. + +*Results*: + ++----------------------------------------------------------------------------+---------------------------+--------+ +| Test Step | Expectation | Result | ++============================================================================+===========================+========+ +| :cpp:func:`getenv_or_exit` called with ``CML_ENVUTILS_TEST_VAR_EXIT_TEST`` | ``"test-value"`` returned | Pass | ++----------------------------------------------------------------------------+---------------------------+--------+ +| :cpp:func:`getenv_or_exit` called with ``DOES_NOT_EXIST_EXIT_TEST`` | Exit handler called | Pass | ++----------------------------------------------------------------------------+---------------------------+--------+ + +By showing that: + +- Attempting to retrieve the value of an environment variable previously set in the test returns that same value +- Attempting to retrieve the value of an environment variable which was previously un-set in the test results in an + exit-handler function being called + +the test ``EnvUtils.Exit`` verifies that the model satisfies the requirement **CML-ENV-UTILS-1**. + +.. _default-value-test: + +EnvUtils.DefaultValue +~~~~~~~~~~~~~~~~~~~~~ + +*Purpose*: Demonstrate that the correct environment variable data is returned if the variable exists, or else returns a +user-defined default value. + +*Requirement*: Satisfactory conclusion of the test satisfies the verification of requirement **CML-ENV-UTILS-2**. + +*Procedure*: + +1. Set the contents of an environment variable, ``CML_ENVUTILS_TEST_VAR_DEFAULT_VALUE_TEST``, equal to ``"test-value"``. +2. Query the contents of ``CML_ENVUTILS_TEST_VAR_DEFAULT_VALUE_TEST`` using the :cpp:func:`getenv_or_default` function + with a default value of ``"DEFAULT"`` +3. Clear the contents of an environment variable, ``DOES_NOT_EXIST_DEFAULT_VALUE_TEST``. +4. Query the contents of ``DOES_NOT_EXIST_DEFAULT_VALUE_TEST`` using the :cpp:func:`getenv_or_default` function + with a default value of ``"DEFAULT"`` + +*Success Criteria*: The contents retrieved from the ``CML_ENVUTILS_TEST_VAR_DEFAULT_VALUE_TEST`` environment variable +should match what they were set to in the first step of the test. Attempting to query the contents of the +``DOES_NOT_EXIST_DEFAULT_VALUE_TEST`` environment variable should result in the default value of ``"DEFAULT"`` being +returned instead. + +*Results*: + ++----------------------------------------------------------------------------------------+---------------------------+--------+ +| Test Step | Expectation | Result | ++========================================================================================+===========================+========+ +| :cpp:func:`getenv_or_default` called with ``CML_ENVUTILS_TEST_VAR_DEFAULT_VALUE_TEST`` | ``"test-value"`` returned | Pass | ++----------------------------------------------------------------------------------------+---------------------------+--------+ +| :cpp:func:`getenv_or_default` called with ``DOES_NOT_EXIST_DEFAULT_VALUE_TEST`` | ``"DEFAULT"`` returned | Pass | ++----------------------------------------------------------------------------------------+---------------------------+--------+ + +By showing that: + +- Attempting to retrieve the value of an environment variable previously set in the test returns that same value +- Attempting to retrieve the value of an environment variable which was previously un-set in the test results in a + specified default value being returned instead + +the test ``EnvUtils.DefaultValue`` verifies that the model satisfies the requirement **CML-ENV-UTILS-2**. + +.. _throw-test: + +EnvUtils.Throw +~~~~~~~~~~~~~~ + +*Purpose*: Demonstrate that the correct environment variable data is returned if the variable exists, or else throws an +error. + +*Requirement*: Satisfactory conclusion of the test satisfies the verification of requirement **CML-ENV-UTILS-3**. + +*Procedure*: + +1. Set the contents of an environment variable, ``CML_ENVUTILS_TEST_VAR_THROW_TEST``, equal to ``"test-value"``. +2. Query the contents of ``CML_ENVUTILS_TEST_VAR_THROW_TEST`` using the :cpp:func:`getenv_or_throw` function. +3. Clear the contents of an environment variable, ``DOES_NOT_EXIST_THROW_TEST``. +4. Query the contents of ``DOES_NOT_EXIST_THROW_TEST`` using the :cpp:func:`getenv_or_throw` function. + +*Success Criteria*: The contents retrieved from the ``CML_ENVUTILS_TEST_VAR_THROW_TEST`` environment variable +should match what they were set to in the first step of the test. Attempting to query the contents of the +``DOES_NOT_EXIST_THROW_TEST`` environment variable should result in an error being thrown. + +*Results*: + ++-------------------------------------------------------------------------------+-------------------------------+--------+ +| Test Step | Expectation | Result | ++===============================================================================+===============================+========+ +| :cpp:func:`getenv_or_throw` called with ``CML_ENVUTILS_TEST_VAR_THROW_TEST`` | ``"test-value"`` returned | Pass | ++-------------------------------------------------------------------------------+-------------------------------+--------+ +| :cpp:func:`getenv_or_throw` called with ``DOES_NOT_EXIST_THROW_TEST`` | ``std::runtime_error`` thrown | Pass | ++-------------------------------------------------------------------------------+-------------------------------+--------+ + +By showing that: + +- Attempting to retrieve the value of an environment variable previously set in the test returns that same value +- Attempting to retrieve the value of an environment variable which was previously un-set in the test results in an + error being throw + +the test ``EnvUtils.Throw`` verifies that the model satisfies the requirement **CML-ENV-UTILS-3**. + +.. _braced-env-vars-test: + +EnvUtils.ExpandBracedEnvironmentVariables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +*Purpose*: Demonstrate that environment variables are correctly expanded when specified in braced form. + +*Requirement*: Satisfactory conclusion of the test partially satisfies the verification of requirement +**CML-ENV-UTILS-4**. + +*Procedure*: + +1. Set the contents of an environment variable, ``CML_ENVUTILS_TEST_VAR_BRACED1``, equal to ``"blue"``. +2. Set the contents of an environment variable, ``CML_ENVUTILS_TEST_VAR_BRACED2``, equal to ``"mouse"``. +3. Expand the environment variables in the string ``"I have a pet ${CML_ENVUTILS_TEST_VAR_BRACED1} ${CML_ENVUTILS_TEST_VAR_BRACED2}"``. +4. Attempt to expand the string when one of the environment variables is not set. +5. Attempt to expand the string when all of the environment variables are not set. + +*Success Criteria*: When both environment variables are defined, the string should expand to ``"I have a pet blue mouse"``. +If any of the variables are not set, an error is thrown. + +*Results*: + ++-------------------------------------------------------------------------------+----------------------------------------+--------+ +| Test Step | Expectation | Result | ++===============================================================================+========================================+========+ +| :cpp:func:`expand_env_variables` called with both variables set | ``"I have a pet blue mouse"`` returned | Pass | ++-------------------------------------------------------------------------------+----------------------------------------+--------+ +| :cpp:func:`expand_env_variables` called with one variable not set | ``std::runtime_error`` thrown | Pass | ++-------------------------------------------------------------------------------+----------------------------------------+--------+ +| :cpp:func:`expand_env_variables` called with neither variable set | ``std::runtime_error`` thrown | Pass | ++-------------------------------------------------------------------------------+----------------------------------------+--------+ + +By showing that: + +- Both environment variables are expanded correctly when set +- An error is thrown if any environment variable in the string is not set + +the test ``EnvUtils.ExpandBracedEnvironmentVariables`` partially verifies that the model satisfies the requirement +**CML-ENV-UTILS-4**. + +.. _unbraced-env-vars-test: + +EnvUtils.ExpandUnbracedEnvironmentVariables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This test is identical to to :ref:`braced environment variable expansion test `, except it uses the +``$UNBRACED_VAR`` form instead of the ``${BRACED_VAR_FORM}``. The test procedure, inputs, and expected outputs are +otherwise identical. + +the test ``EnvUtils.ExpandUnbracedEnvironmentVariables`` partially verifies that the model satisfies the requirement +**CML-ENV-UTILS-4**. + +.. _edge-cases-test: + +EnvUtils.ExpandEnvironmentVariablesEdgeCases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +*Purpose*: Demonstrate that environment variables are correctly expanded under certain edge conditions. + +*Requirement*: Satisfactory conclusion of the test partially satisfies the verification of requirement +**CML-ENV-UTILS-4**. + +*Procedure*: + +1. Attempt to expand environment variables in an input which has no environment variables to expand. +2. Attempt to expand unbraced environment variables with so spaces between them. + +*Success Criteria*: + +1. A string with no environment variables to expand is returned unmodified. +2. Two unbraced environment variables which are not separated by a space are correctly expanded. + ++----------------------------------------------------------------------------------+-----------------------------------------+--------+ +| Test Step | Expectation | Result | ++==================================================================================+=========================================+========+ +| :cpp:func:`expand_env_variables` called with ``"No environment variables"`` | ``"No environment variables"`` returned | Pass | ++----------------------------------------------------------------------------------+-----------------------------------------+--------+ +| :cpp:func:`expand_env_variables` called with two consecutive, unbraced variables | Variables are both expanded | Pass | ++----------------------------------------------------------------------------------+-----------------------------------------+--------+ + +By showing that: + +- A string with no environment variables to expand is returned unmodified +- Two unbraced environment variables which are not separated by a space are correctly expanded + +the test ``EnvUtils.ExpandEnvironmentVariablesEdgeCases`` partially verifies that the model satisfies the requirement +**CML-ENV-UTILS-4**. diff --git a/docs/sphinx/models/utilities/index.rst b/docs/sphinx/models/utilities/index.rst index d584182b..ee298c56 100644 --- a/docs/sphinx/models/utilities/index.rst +++ b/docs/sphinx/models/utilities/index.rst @@ -10,4 +10,5 @@ really fit under other model categories. :name: utilities-models :titlesonly: - subscriptions.rst \ No newline at end of file + env-utils.rst + subscriptions.rst diff --git a/models/tools/unit_test/models/include/unit_test.hh b/models/tools/unit_test/models/include/unit_test.hh index 2c60ee8b..d87be386 100644 --- a/models/tools/unit_test/models/include/unit_test.hh +++ b/models/tools/unit_test/models/include/unit_test.hh @@ -123,9 +123,6 @@ class UnitTestFramework { void update_sweeps(); void update_file(); - private: - friend class UnitTestFrameworkTest; - std::string expand_env_variables(const std::string& input); }; // Interface to make this look like the C-style unit-test framework: @@ -135,4 +132,4 @@ inline void unit_test_init( UNIT_TEST * data) {data->initialize();} inline void unit_test( UNIT_TEST * data) {data->update();} inline void unit_test_dd( UNIT_TEST * data) {(void) data;} inline void unit_test_shutdown( UNIT_TEST * data) {(void) data;} -#endif \ No newline at end of file +#endif diff --git a/models/tools/unit_test/models/src/unit_test.cc b/models/tools/unit_test/models/src/unit_test.cc index 6b0e5a18..cb0341df 100644 --- a/models/tools/unit_test/models/src/unit_test.cc +++ b/models/tools/unit_test/models/src/unit_test.cc @@ -3,7 +3,8 @@ PURPOSE: (To provide a Trick-friendly unit-test framework) REFERENCES: (../models-C by Jason Arnold) LIBRARY DEPENDENCIES: - ((cml/models/utilities/cml_message/src/cml_message.cc)) + ((cml/models/utilities/env_utils/src/env_utils.cc) + (cml/models/utilities/cml_message/src/cml_message.cc)) PROGRAMMERS: (((Jason Arnold) (Titan) (Jul 2005)) @@ -15,27 +16,20 @@ LIBRARY DEPENDENCIES: #include #include #include -#include #include #include #include #include #include -#include -#include #include #include "../include/unit_test.hh" +#include "cml/models/utilities/env_utils/include/env_utils.hh" #include "cml/models/utilities/math_utils/include/math_utils.hh" - #include "cml/models/utilities/cml_message/include/cml_message.hh" #include "trick/input_processor_proto.h" -#include "trick/IPPython.hh" - -extern Trick::IPPython* the_pip; - /***************************************************************************** Constructor @@ -252,51 +246,6 @@ UnitTestFramework::configure_file_combinations() process_linked_variables(); } -/***************************************************************************** -expand_env_variables -Purpose: (Replaces environment variable placeholders in the form `${VAR_NAME}` - within the input string with their corresponding values from the - process environment. The search pattern matches variable names - beginning with a letter or underscore, followed by letters, digits, - or underscores. If an environment variable is found, its value is - inserted into the output string. If a variable is not set, the - placeholder is left unchanged, a warning is printed via `CMLMessage::error`, - and a runtime exception is thrown. The method preserves any text - outside of `${}` sequences unchanged.) -*****************************************************************************/ -std::string UnitTestFramework::expand_env_variables(const std::string& input) { - static const std::regex pattern(R"(\$\{([A-Za-z_][A-Za-z0-9_]*)\})"); - - std::string result; - const std::sregex_iterator begin(input.begin(), input.end(), pattern); - const std::sregex_iterator end; - - std::size_t last_pos = 0; - - for (auto it = begin; it != end; ++it) { - const std::smatch& match = *it; - const std::size_t substr_len = static_cast(match.position()) - last_pos; - result.append(input.substr(last_pos, substr_len)); // text before match - - const std::string var_name = match[1].str(); - const char* env_val = std::getenv(var_name.c_str()); - if (env_val != nullptr) - { - result.append(env_val); - } else - { - CMLMessage::error(__FILE__, __LINE__, - "Warning: Environment variable '", var_name, "' is not set. Leaving placeholder unchanged.\n"); - result.append(match[0].str()); // Keep the original "${VAR}" - throw std::runtime_error("Missing environment variable: " + var_name); - } - last_pos = static_cast(match.position() + match.length()); - } - - result.append(input.substr(last_pos)); // remaining text - return result; -} - /***************************************************************************** configure_from_definition_file Purpose:(Used when the tests are specified in a single file with each row of @@ -676,4 +625,4 @@ UnitTestFramework::update_file() commands.push_back(commands.front()); } commands.pop_front(); -} \ No newline at end of file +} diff --git a/models/tools/unit_test/models/tests/test_unit_framework.cc b/models/tools/unit_test/models/tests/test_unit_framework.cc index 82f6d38b..764757dd 100644 --- a/models/tools/unit_test/models/tests/test_unit_framework.cc +++ b/models/tools/unit_test/models/tests/test_unit_framework.cc @@ -1,17 +1,7 @@ -#include -#include -#include #include -#include "../include/unit_test.hh" -#include "mocks/cml/cml_message_mock.hh" +#include "../include/unit_test.hh" -// Create a test subclass to access protected methods (if needed) -class UnitTestFrameworkTest : public UnitTestFramework { -public: - using UnitTestFramework::expand_env_variables; -}; - -TEST(UnitTestFrameworkTest, DefaultConstructorInitializesState) { +TEST(UnitTestFramework, DefaultConstructorInitializesState) { UnitTestFramework utf; EXPECT_TRUE(utf.enabled); @@ -21,29 +11,3 @@ TEST(UnitTestFrameworkTest, DefaultConstructorInitializesState) { EXPECT_EQ(utf.linked_vars_file_name, ""); EXPECT_EQ(utf.cycle_overruns_limit, 2u); } - -TEST(UnitTestFrameworkTest, ExpandEnvVariableKnown) { - setenv("MY_TEST_PATH", "/tmp/testdir", 1); - UnitTestFrameworkTest utf; - - std::string input = "${MY_TEST_PATH}/file.txt"; - std::string expected = "/tmp/testdir/file.txt"; - - EXPECT_EQ(utf.expand_env_variables(input), expected); -} - -TEST(UnitTestFrameworkTest, ExpandEnvVariableUnknownThrows) { - using testing::_; - using testing::HasSubstr; - - CMLMessage::Mock cml_message_mock; - - unsetenv("NON_EXISTENT_VAR"); - UnitTestFrameworkTest utf; - - EXPECT_CALL( - cml_message_mock, - publish(CMLMessage::Error, _, _, HasSubstr("'NON_EXISTENT_VAR' is not set"))); - std::string input = "${NON_EXISTENT_VAR}/file.txt"; - EXPECT_THROW(utf.expand_env_variables(input), std::runtime_error); -} diff --git a/models/utilities/CMakeLists.txt b/models/utilities/CMakeLists.txt index f946e73e..cf22eef0 100644 --- a/models/utilities/CMakeLists.txt +++ b/models/utilities/CMakeLists.txt @@ -22,7 +22,7 @@ add_cml_library( constraint_check/include/valset_constraint.hh convert_string/include/convert_string.hh double_to_words/include/convert_double_to_words.hh - env_utils/include/env_utils.h + env_utils/include/env_utils.hh fault_arch/include/sSensorFaults.hh fault_management/include/fault.hh fault_management/include/fault_bias.hh @@ -74,6 +74,7 @@ add_cml_library( constraint_check/src/constraint_test_templates.cc constraint_check/src/constraint_test_timed_templates.cc double_to_words/src/convert_double_to_uint_words.cc + env_utils/src/env_utils.cc fault_arch/src/sSensorFaults.cc fault_management/src/fault.cc fault_management/src/fault_function.cc @@ -109,5 +110,6 @@ add_cml_tests( SOURCES cml_message/test/cml_message_test.cc + env_utils/test/env_utils_test.cc table_interp_cpp/test/table_independent_variable_test.cc ) diff --git a/models/utilities/env_utils/docs/README.md b/models/utilities/env_utils/docs/README.md new file mode 100644 index 00000000..17872ee8 --- /dev/null +++ b/models/utilities/env_utils/docs/README.md @@ -0,0 +1 @@ +Read the documentation for the env utils model [here](https://nasa.github.io/cml/models/utilities/env-utils.html). diff --git a/models/utilities/env_utils/docs/env_utils.pdf b/models/utilities/env_utils/docs/env_utils.pdf deleted file mode 100644 index 7344d6c4..00000000 Binary files a/models/utilities/env_utils/docs/env_utils.pdf and /dev/null differ diff --git a/models/utilities/env_utils/docs/env_utils.tex b/models/utilities/env_utils/docs/env_utils.tex deleted file mode 100644 index 605f86ce..00000000 --- a/models/utilities/env_utils/docs/env_utils.tex +++ /dev/null @@ -1,91 +0,0 @@ -\documentclass{article} -\usepackage{amsmath} -\usepackage{listings} -\usepackage{xcolor} -\usepackage{hyperref} - -\title{Utility Functions for Environment Variable Access in C++} -\author{} -\date{} - -\lstset{ - language=C++, - basicstyle=\ttfamily\small, - keywordstyle=\color{blue}, - commentstyle=\color{gray}, - stringstyle=\color{teal}, - showstringspaces=false, - breaklines=true, - frame=single -} - -\begin{document} - -\maketitle - -\section*{Overview} - -These C++ utility functions provide safer and more expressive alternatives to the standard \lstinline|getenv()| function for retrieving environment variables. Each function handles the case where the environment variable is not set in a distinct way: exiting the program, returning a default value, or throwing an exception. - -\section*{Function Descriptions} - -\subsection*{1. \lstinline|const char* getenv_or_exit(const char* var_name, void (*exit_fn)(int) = std::exit)|} - -This function attempts to retrieve the environment variable \lstinline|var_name| using \lstinline|getenv()|. If the variable is not set (i.e., \lstinline|getenv()| returns \lstinline|nullptr), it calls the specified \lstinline|exit\_fn| function, which defaults to \lstinline|std::exit|. - -\textbf{Example Usage:} -\begin{lstlisting} -const char* path = getenv_or_exit("MY_ENV_VAR"); -\end{lstlisting} - -\textbf{Notes:} -\begin{itemize} - \item Useful for critical variables that must be present for the program to continue. - \item Allows custom exit behavior by passing a different exit function. -\end{itemize} - -\subsection*{2. \lstinline|const char* getenv_or_default(const char* var_name, const char* default_value)|} - -Returns the value of the environment variable \lstinline|var_name| if it exists. Otherwise, returns \lstinline|default_value|. - -\textbf{Example Usage:} -\begin{lstlisting} -const char* mode = getenv_or_default("RUN_MODE", "debug"); -\end{lstlisting} - -\textbf{Notes:} -\begin{itemize} - \item Useful when a fallback or default behavior is acceptable. - \item Avoids the need for manual null checks. -\end{itemize} - -\subsection*{3. \lstinline|const char* getenv_or_throw(const char* var_name)|} - -Returns the environment variable value if it exists. If not, throws a \lstinline|std::runtime_error| with a descriptive message. - -\textbf{Example Usage:} -\begin{lstlisting} -try { - const char* value = getenv_or_throw("API_KEY"); -} catch (const std::runtime_error& e) { - std::cerr << "Missing env var: " << e.what() << std::endl; -} -\end{lstlisting} - -\textbf{Notes:} -\begin{itemize} - \item Best used in exception-enabled code where you want to handle missing variables as recoverable errors. -\end{itemize} - -\section*{Conclusion} - -These utility functions offer three different idiomatic patterns for environment variable access in C++: -\begin{itemize} - \item \textbf{Fail-fast:} \lstinline|getenv_or_exit| - \item \textbf{Fallback:} \lstinline|getenv_or_default| - \item \textbf{Exception-based:} \lstinline|getenv_or_throw| -\end{itemize} - -They help enforce clear, consistent handling of environment variables and avoid scattered manual null checks throughout your codebase. - -\end{document} diff --git a/models/utilities/env_utils/include/env_utils.h b/models/utilities/env_utils/include/env_utils.h deleted file mode 100644 index 58557b46..00000000 --- a/models/utilities/env_utils/include/env_utils.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CML_ENV_UTILS_H -#define CML_ENV_UTILS_H - -#include -#include -#include -#include - -// Function that exits if getenv returns NULL -inline const char* getenv_or_exit(const char* var_name, void (*exit_fn)(int) = std::exit) -{ - const char* value = std::getenv(var_name); - if (value == nullptr) - { - std::cerr << "Error: Environment variable " << var_name << " is not set.\n"; - exit_fn(EXIT_FAILURE); - } - return value; -} - -inline const char* getenv_or_default(const char* var_name, const char* default_value) -{ - const char* value = std::getenv(var_name); - return value != nullptr ? value : default_value; -} - -inline const char* getenv_or_throw(const char* var_name) -{ - if (var_name == nullptr) { - throw std::runtime_error("Invalid parameter: var_name is nullptr, 0 or NULL"); - } - const char* value = std::getenv(var_name); - if (value == nullptr) - { - throw std::runtime_error("Error: Environment variable \"" + std::string(var_name) + "\" is not set."); - } - return value; -} - -#endif diff --git a/models/utilities/env_utils/include/env_utils.hh b/models/utilities/env_utils/include/env_utils.hh new file mode 100644 index 00000000..a72aaa7d --- /dev/null +++ b/models/utilities/env_utils/include/env_utils.hh @@ -0,0 +1,95 @@ +/******************************************************************************* +PURPOSE: (Tools for reading environment variables.) + +LIBRARY DEPENDENCIES: + ( + (../src/env_utils.cc) + ) + +PROGRAMMERS: + ( + ((Nino Tarantino) (NASA) (09/16/26) (Use C++ interfaces)) + ) +*******************************************************************************/ +#ifndef CML_ENV_UTILS_HH +#define CML_ENV_UTILS_HH + +#include +#include +#include +#include +#include + +/** + * Retrieve the environment variable if it exists, otherwise terminate the program + * + * @param var_name Environment variable to retrieve + * @param exit_fn Function to be called if the variable is not set + * @return The contents of the environment variable + */ +inline std::string getenv_or_exit( + const std::string& var_name, + const std::function& exit_fn = std::exit) +{ + const char* value = std::getenv(var_name.c_str()); + if (value == nullptr) { + std::cerr << "Error: Environment variable \"" << var_name << "\" is not set.\n"; + exit_fn(EXIT_FAILURE); + return ""; + } + return value; +} + +/** + * Retrieve the environment variable if it exists, otherwise return a user-defined default value + * + * @param var_name Environment variable to retrieve + * @param default_value Value to return if the variable is not set + * @return The contents of the environment variable if set, otherwise the default value + */ +inline std::string getenv_or_default(const std::string& var_name, const std::string& default_value) +{ + const char* value = std::getenv(var_name.c_str()); + if (value == nullptr) { + return default_value; + } + return value; +} + +/** + * Retrieve the environment variable if it exists, otherwise throw an error + * + * @throws std::runtime_error If the environment variable is not set + * @param var_name Environment variable to retrieve + * @return The contents of the environment variable + */ +inline std::string getenv_or_throw(const std::string& var_name) noexcept(false) +{ + const char* value = std::getenv(var_name.c_str()); + if (value == nullptr) { + throw std::runtime_error("Error: Environment variable \"" + var_name + "\" is not set."); + } + return value; +} + +/** + * Expand all environment variables in a given string + * + * Recognizes both `${VAR}` and `$VAR` forms. For the braced form, the name is whatever + * appears between the braces. For the bare form, the name runs until the next delimiter + * (space, '/', etc.) or the end of the string. Environment variables are assumed to + * begin with a letter and contain only letters, numbers, and underscores. + * + * If the string has neither of the aforementioned forms, it is considered to not contain + * any environment variables. In this case, calling this function returns exactly what + * was passed to it. + * + * @note Nested environment variables are not supported. Variables of the form + * `${SOME${VAR}}` will not expand correctly. + * + * @param input String containing environment variables + * @return The original string with environment variables expanded + */ +std::string expand_env_variables(const std::string& input) noexcept(false); + +#endif diff --git a/models/utilities/env_utils/src/env_utils.cc b/models/utilities/env_utils/src/env_utils.cc new file mode 100644 index 00000000..4beb7378 --- /dev/null +++ b/models/utilities/env_utils/src/env_utils.cc @@ -0,0 +1,53 @@ +/******************************************************************************* +PURPOSE: (Tools for reading environment variables.) + +LIBRARY DEPENDENCIES: () + +PROGRAMMERS: + ( + ((Nino Tarantino) (NASA) (09/16/26) (Relocate from Unit Test model)) + ) +*******************************************************************************/ + +#include "../include/env_utils.hh" + +#include +#include +#include + +// Expand all environment variables in a given string +std::string expand_env_variables(const std::string& input) noexcept(false) +{ + std::string result; + result.reserve(input.size()); + + static const std::regex pattern( + R"(\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*))"); + const auto begin = std::sregex_iterator(input.begin(), input.end(), pattern); + const auto end = std::sregex_iterator(); + + std::size_t last_position = 0; + for (auto it = begin; it != end; ++it) { + const std::smatch& match = *it; + + // Copy text between the previous match and this one. + const auto match_position = static_cast(match.position()); + result.append(input, last_position, match_position - last_position); + + // Grab the value stored in the environment variable. + std::string var_name; + if (match[1].matched) { + var_name = match[1].str(); + } else { + var_name = match[2].str(); + } + const auto env_var_contents = getenv_or_throw(var_name); + + // Add environment variable contents to result. + result += env_var_contents; + last_position = match_position + static_cast(match.length()); + } + + result.append(input, last_position, input.size() - last_position); + return result; +} diff --git a/models/utilities/env_utils/test/env_utils_test.cc b/models/utilities/env_utils/test/env_utils_test.cc new file mode 100644 index 00000000..0cd6d3ab --- /dev/null +++ b/models/utilities/env_utils/test/env_utils_test.cc @@ -0,0 +1,105 @@ +#include "../include/env_utils.hh" + +#include +#include +#include +#include + +namespace { + +// Test usage of the function which exits when the environment variable is not set. +TEST(EnvUtils, Exit) { + // Variable exists. + setenv("CML_ENVUTILS_TEST_VAR_EXIT_TEST", "test-value", 1); + EXPECT_EQ(getenv_or_exit("CML_ENVUTILS_TEST_VAR_EXIT_TEST"), "test-value"); + + // Variable doesn't exist. + unsetenv("DOES_NOT_EXIST_EXIT_TEST"); + bool exit_called = false; + auto dummy_exit_fn = [&exit_called] ([[maybe_unused]] int ret) -> void {exit_called = true;}; + getenv_or_exit("DOES_NOT_EXIST_EXIT_TEST", dummy_exit_fn); + EXPECT_TRUE(exit_called); +} + +// Test usage of the function which returns a default value when the environment variable +// is not set. +TEST(EnvUtils, DefaultValue) { + // Variable exists. + setenv("CML_ENVUTILS_TEST_VAR_DEFAULT_VALUE_TEST", "test-value", 1); + EXPECT_EQ(getenv_or_default("CML_ENVUTILS_TEST_VAR_DEFAULT_VALUE_TEST", "DEFAULT"), "test-value"); + + // Variable doesn't exist. + unsetenv("DOES_NOT_EXIST_DEFAULT_VALUE_TEST"); + EXPECT_EQ(getenv_or_default("DOES_NOT_EXIST_DEFAULT_VALUE_TEST", "DEFAULT"), "DEFAULT"); +} + +// Test usage of the function which throws when the environment variable is not set. +TEST(EnvUtils, Throw) { + // Variable exists. + setenv("CML_ENVUTILS_TEST_VAR_THROW_TEST", "test-value", 1); + EXPECT_EQ(getenv_or_throw("CML_ENVUTILS_TEST_VAR_THROW_TEST"), "test-value"); + + // Variable doesn't exist. + unsetenv("DOES_NOT_EXIST_THROW_TEST"); + EXPECT_THROW(getenv_or_throw("DOES_NOT_EXIST_THROW_TEST"), std::runtime_error); +} + +// Test the environment variable expansion function for braced variables. +TEST(EnvUtils, ExpandBracedEnvironmentVariables) { + // Both variables exist. + setenv("CML_ENVUTILS_TEST_VAR_BRACED1", "blue", 1); + setenv("CML_ENVUTILS_TEST_VAR_BRACED2", "mouse", 1); + EXPECT_EQ( + expand_env_variables("I have a pet ${CML_ENVUTILS_TEST_VAR_BRACED1} ${CML_ENVUTILS_TEST_VAR_BRACED2}"), + "I have a pet blue mouse"); + + // One variable doesn't exist. + unsetenv("CML_ENVUTILS_TEST_VAR_BRACED1"); + EXPECT_THROW( + expand_env_variables("I have a pet ${CML_ENVUTILS_TEST_VAR_BRACED1} ${CML_ENVUTILS_TEST_VAR_BRACED2}"), + std::runtime_error); + + // None of the environment variables exist. + unsetenv("CML_ENVUTILS_TEST_VAR_BRACED2"); + EXPECT_THROW( + expand_env_variables("I have a pet ${CML_ENVUTILS_TEST_VAR_BRACED1} ${CML_ENVUTILS_TEST_VAR_BRACED2}"), + std::runtime_error); +} + +// Test the environment variable expansion function for unbraced variables. +TEST(EnvUtils, ExpandUnbracedEnvironmentVariables) { + // Both variables exist. + setenv("CML_ENVUTILS_TEST_VAR_BRACED1", "blue", 1); + setenv("CML_ENVUTILS_TEST_VAR_BRACED2", "mouse", 1); + EXPECT_EQ( + expand_env_variables("I have a pet $CML_ENVUTILS_TEST_VAR_BRACED1 $CML_ENVUTILS_TEST_VAR_BRACED2"), + "I have a pet blue mouse"); + + // One variable doesn't exist. + unsetenv("CML_ENVUTILS_TEST_VAR_BRACED1"); + EXPECT_THROW( + expand_env_variables("I have a pet $CML_ENVUTILS_TEST_VAR_BRACED1 $CML_ENVUTILS_TEST_VAR_BRACED2"), + std::runtime_error); + + // None of the environment variables exist. + unsetenv("CML_ENVUTILS_TEST_VAR_BRACED2"); + EXPECT_THROW( + expand_env_variables("I have a pet $CML_ENVUTILS_TEST_VAR_BRACED1 $CML_ENVUTILS_TEST_VAR_BRACED2"), + std::runtime_error); +} + +// Test edge cases of the environment variables expansion function. +TEST(EnvUtils, ExpandEnvironmentVariablesEdgeCases) { + // A string containing no environment variables is returned as-is. + const std::string no_env_vars = "No environment variables"; + EXPECT_EQ(no_env_vars, expand_env_variables(no_env_vars)); + + // Test the regex which determines variable delimiters. + setenv("CML_ENVUTILS_TEST_VAR_EDGE1", "John", 1); + setenv("CML_ENVUTILS_TEST_VAR_EDGE2", "son", 2); + EXPECT_EQ( + expand_env_variables("$CML_ENVUTILS_TEST_VAR_EDGE1$CML_ENVUTILS_TEST_VAR_EDGE2 Space Center"), + "Johnson Space Center"); +} + +} // namespace diff --git a/models/utilities/env_utils/verif/SIM_verif/Log_data/log_data.py b/models/utilities/env_utils/verif/SIM_verif/Log_data/log_data.py deleted file mode 100644 index 3da41371..00000000 --- a/models/utilities/env_utils/verif/SIM_verif/Log_data/log_data.py +++ /dev/null @@ -1,10 +0,0 @@ -dr_group = trick.sim_services.DRAscii("test_data") -dr_group.set_cycle(1.0) -dr_group.freq = trick.sim_services.DR_Always -trick.add_data_record_group(dr_group, trick.DR_Buffer) - - -dr_group.add_variable("test.test1") -dr_group.add_variable("test.test2") -dr_group.add_variable("test.test3") -dr_group.add_variable("test.test4") diff --git a/models/utilities/env_utils/verif/SIM_verif/RUN_verif/input.py b/models/utilities/env_utils/verif/SIM_verif/RUN_verif/input.py deleted file mode 100644 index f97f4d4e..00000000 --- a/models/utilities/env_utils/verif/SIM_verif/RUN_verif/input.py +++ /dev/null @@ -1,8 +0,0 @@ -exec(open("Log_data/log_data.py").read()) -print(""" - *********************************************************************** - Testing obtaining variables from environment. - *********************************************************************** - """) - -trick.stop(0) diff --git a/models/utilities/env_utils/verif/SIM_verif/S_define b/models/utilities/env_utils/verif/SIM_verif/S_define deleted file mode 100644 index 5ec1db6a..00000000 --- a/models/utilities/env_utils/verif/SIM_verif/S_define +++ /dev/null @@ -1,64 +0,0 @@ -#include "sim_objects/default_trick_sys.sm" - -/***************************************************************************** -EnvUtilsObject -Purpose:(The sim) -*****************************************************************************/ - -#define bad_env_variable "fhglkjdehglk" - -// Include headers for classes that this class contains: -##include "cml/models/utilities/env_utils/include/env_utils.h" -##include -##include - -class EnvUtilsObject: public Trick::SimObject -{ - public: - - bool test1=false; - bool test2=false; - bool test3=false; - bool test4=false; - - static void fake_exit(int code) - { - (void)code; // Mark code as intentionally unused - throw std::runtime_error("exit called"); - } - - EnvUtilsObject() - { - ("initialization") run_tests(); - } - - void run_tests() - { - test1 = std::string(getenv_or_default("PWD", "nowhere")) != "nowhere"; - test2 = std::string(getenv_or_default(bad_env_variable, "nobody")) == "nobody"; - - try - { - getenv_or_exit(bad_env_variable, EnvUtilsObject::fake_exit); - } - catch(std::runtime_error const &ex) - { - test3 = (std::string(ex.what()) == "exit called"); - } - - try - { - getenv_or_throw(bad_env_variable); - } - catch(std::runtime_error const &ex) - { - test4 = (std::string(ex.what()) == "Error: Environment variable \"fhglkjdehglk\" is not set."); - } - } - - private: - EnvUtilsObject (const EnvUtilsObject&); - EnvUtilsObject & operator = (const EnvUtilsObject&); -}; - -EnvUtilsObject test; diff --git a/models/utilities/env_utils/verif/SIM_verif/S_overrides.mk b/models/utilities/env_utils/verif/SIM_verif/S_overrides.mk deleted file mode 100644 index c769606d..00000000 --- a/models/utilities/env_utils/verif/SIM_verif/S_overrides.mk +++ /dev/null @@ -1 +0,0 @@ -include ${CML_HOME}/mkspecs/internal/cml_unit_sim.mk diff --git a/models/utilities/env_utils/verif/SIM_verif/verif_data/RUN_verif/log_test_data.csv b/models/utilities/env_utils/verif/SIM_verif/verif_data/RUN_verif/log_test_data.csv deleted file mode 100644 index 5eab2ce4..00000000 --- a/models/utilities/env_utils/verif/SIM_verif/verif_data/RUN_verif/log_test_data.csv +++ /dev/null @@ -1,2 +0,0 @@ -sys.exec.out.time {s},test.test1 {1},test.test2 {1},test.test3 {1},test.test4 {1} - 0,1,1,1,1 diff --git a/trickified/S_source.hh b/trickified/S_source.hh index 519bd30b..dc1abad6 100644 --- a/trickified/S_source.hh +++ b/trickified/S_source.hh @@ -130,7 +130,7 @@ PROGRAMMERS: #include "cml/models/utilities/constraint_check/include/valset_constraint.hh" #include "cml/models/utilities/convert_string/include/convert_string.hh" #include "cml/models/utilities/double_to_words/include/convert_double_to_words.hh" -#include "cml/models/utilities/env_utils/include/env_utils.h" +#include "cml/models/utilities/env_utils/include/env_utils.hh" #include "cml/models/utilities/fault_arch/include/sSensorFaults.hh" #include "cml/models/utilities/fault_management/include/fault_bias.hh" #include "cml/models/utilities/fault_management/include/fault_function.hh"