diff --git a/doc/src/Developer_utils.rst b/doc/src/Developer_utils.rst index 54a21e5e44..7e53c61fb8 100644 --- a/doc/src/Developer_utils.rst +++ b/doc/src/Developer_utils.rst @@ -60,6 +60,9 @@ silently returning the result of a partial conversion or zero in cases where the string is not a valid number. This behavior allows to more easily detect typos or issues when processing input files. +Similarly the :cpp:func:`logical() ` function +will convert a string into a boolean and will only accept certain words. + The *do_abort* flag should be set to ``true`` in case this function is called only on a single MPI rank, as that will then trigger the a call to ``Error::one()`` for errors instead of ``Error::all()`` @@ -83,6 +86,9 @@ strings for compliance without conversion. .. doxygenfunction:: tnumeric :project: progguide +.. doxygenfunction:: logical + :project: progguide + String processing ^^^^^^^^^^^^^^^^^ diff --git a/src/utils.cpp b/src/utils.cpp index 17d544bffa..6c288002b9 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -24,6 +24,7 @@ #include "tokenizer.h" #include "update.h" +#include #include #include #include @@ -345,6 +346,48 @@ std::string utils::check_packages_for_style(const std::string &style, const std: return errmsg; } +/* ---------------------------------------------------------------------- + read a boolean value from a string + transform to lower case before checking + generate an error if is not a legitimate boolean + called by various commands to check validity of their arguments +------------------------------------------------------------------------- */ + +bool utils::logical(const char *file, int line, const char *str, bool do_abort, LAMMPS *lmp) +{ + int n = 0; + + if (str) n = strlen(str); + if (n == 0) { + const char msg[] = "Expected boolean parameter instead of NULL or empty string " + "in input script or data file"; + if (do_abort) + lmp->error->one(file, line, msg); + else + lmp->error->all(file, line, msg); + } + + // convert to ascii and lowercase + std::string buf(str); + if (has_utf8(buf)) buf = utf8_subst(buf); + std::transform(buf.begin(), buf.end(), buf.begin(), tolower); + + bool rv = false; + if ((buf == "yes") || (buf == "on") || (buf == "true") || (buf == "1")) { + rv = true; + } else if ((buf == "no") || (buf == "off") || (buf == "false") || (buf == "0")) { + rv = false; + } else { + std::string msg("Expected boolean parameter instead of '"); + msg += buf + "' in input script or data file"; + if (do_abort) + lmp->error->one(file, line, msg); + else + lmp->error->all(file, line, msg); + } + return rv; +} + /* ---------------------------------------------------------------------- read a floating point value from a string generate an error if not a legitimate floating point value diff --git a/src/utils.h b/src/utils.h index f53374144e..65a1462716 100644 --- a/src/utils.h +++ b/src/utils.h @@ -160,6 +160,19 @@ namespace utils { std::string check_packages_for_style(const std::string &style, const std::string &name, LAMMPS *lmp); + /*! Convert a string to a boolean while checking whether it is a valid boolean term. + * Valid terms are 'yes', 'no', 'true', 'false', 'on', 'off', '1', '0'. Capilatization + * will be ignored (thus 'yEs' or 'nO' are valid, 'yay' or 'nay' are not). + * + * \param file name of source file for error message + * \param line line number in source file for error message + * \param str string to be converted to logical + * \param do_abort determines whether to call Error::one() or Error::all() + * \param lmp pointer to top-level LAMMPS class instance + * \return boolean */ + + bool logical(const char *file, int line, const char *str, bool do_abort, LAMMPS *lmp); + /*! Convert a string to a floating point number while checking * if it is a valid floating point or integer number * diff --git a/unittest/formats/CMakeLists.txt b/unittest/formats/CMakeLists.txt index b4c637edfb..93b48ac1b4 100644 --- a/unittest/formats/CMakeLists.txt +++ b/unittest/formats/CMakeLists.txt @@ -7,6 +7,10 @@ add_executable(test_image_flags test_image_flags.cpp) target_link_libraries(test_image_flags PRIVATE lammps GTest::GMock GTest::GTest) add_test(NAME ImageFlags COMMAND test_image_flags WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +add_executable(test_input_convert test_input_convert.cpp) +target_link_libraries(test_input_convert PRIVATE lammps GTest::GMockMain GTest::GMock GTest::GTest) +add_test(NAME InputConvert COMMAND test_input_convert WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_executable(test_molecule_file test_molecule_file.cpp) target_link_libraries(test_molecule_file PRIVATE lammps GTest::GMock GTest::GTest) add_test(NAME MoleculeFile COMMAND test_molecule_file WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/unittest/formats/test_input_convert.cpp b/unittest/formats/test_input_convert.cpp new file mode 100644 index 0000000000..b5ec842e2a --- /dev/null +++ b/unittest/formats/test_input_convert.cpp @@ -0,0 +1,121 @@ +/* ---------------------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + https://www.lammps.org/, Sandia National Laboratories + Steve Plimpton, sjplimp@sandia.gov + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#include "../testing/core.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "library.h" +#include "utils.h" + +// whether to print verbose output (i.e. not capturing LAMMPS screen output). +bool verbose = false; + +using LAMMPS_NS::utils::split_words; + +namespace LAMMPS_NS { +using ::testing::Eq; + +class InputConvertTest : public LAMMPSTest { +protected: + void SetUp() override + { + testbinary = "InputConvertTest"; + LAMMPSTest::SetUp(); + ASSERT_NE(lmp, nullptr); + } + + void TearDown() override { LAMMPSTest::TearDown(); } +}; + +TEST_F(InputConvertTest, logical) +{ + EXPECT_TRUE(utils::logical(FLERR, "yes", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "Yes", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "YES", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "yEs", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "true", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "True", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "TRUE", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "on", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "On", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "ON", false, lmp)); + EXPECT_TRUE(utils::logical(FLERR, "1", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "no", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "No", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "NO", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "nO", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "off", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "Off", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "OFF", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "OfF", false, lmp)); + EXPECT_FALSE(utils::logical(FLERR, "0", false, lmp)); + + TEST_FAILURE(".*ERROR: Expected boolean parameter instead of.*", + utils::logical(FLERR, "yay", false, lmp);); + TEST_FAILURE(".*ERROR: Expected boolean parameter instead of.*", + utils::logical(FLERR, "xxx", false, lmp);); + TEST_FAILURE(".*ERROR: Expected boolean parameter instead of.*", + utils::logical(FLERR, "none", false, lmp);); + TEST_FAILURE(".*ERROR: Expected boolean parameter instead of.*", + utils::logical(FLERR, "5", false, lmp);); +} + +TEST_F(InputConvertTest, numeric) +{ + EXPECT_DOUBLE_EQ(utils::numeric(FLERR, "0", false, lmp), 0); + + TEST_FAILURE(".*ERROR: Expected floating point.*", utils::numeric(FLERR, "yay", false, lmp);); +} + +TEST_F(InputConvertTest, inumeric) +{ + EXPECT_DOUBLE_EQ(utils::inumeric(FLERR, "0", false, lmp), 0); + + TEST_FAILURE(".*ERROR: Expected integer.*", utils::inumeric(FLERR, "yay", false, lmp);); +} + +TEST_F(InputConvertTest, bnumeric) +{ + EXPECT_DOUBLE_EQ(utils::bnumeric(FLERR, "0", false, lmp), 0); + + TEST_FAILURE(".*ERROR: Expected integer.*", utils::bnumeric(FLERR, "yay", false, lmp);); +} +TEST_F(InputConvertTest, tnumeric) +{ + EXPECT_DOUBLE_EQ(utils::tnumeric(FLERR, "0", false, lmp), 0); + + TEST_FAILURE(".*ERROR: Expected integer.*", utils::tnumeric(FLERR, "yay", false, lmp);); +} +} // namespace LAMMPS_NS + +int main(int argc, char **argv) +{ + lammps_mpi_init(); + ::testing::InitGoogleMock(&argc, argv); + + // handle arguments passed via environment variable + if (const char *var = getenv("TEST_ARGS")) { + auto env = split_words(var); + for (auto arg : env) { + if (arg == "-v") { + verbose = true; + } + } + } + if ((argc > 1) && (strcmp(argv[1], "-v") == 0)) verbose = true; + + int rv = RUN_ALL_TESTS(); + lammps_mpi_finalize(); + return rv; +}