From ee5be4202697f77d5e0598afb6e42f012c96bee9 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 24 Jun 2020 09:32:59 -0400 Subject: [PATCH] avoid having to use external test runner script by parsing environment variables directly --- src/utils.cpp | 80 ++++++++++++++++++++++++++ src/utils.h | 15 +++++ unittest/force-styles/CMakeLists.txt | 28 +++------ unittest/force-styles/TestRunner.cmake | 8 --- unittest/force-styles/test_main.cpp | 15 +++++ unittest/utils/test_utils.cpp | 19 ++++++ 6 files changed, 137 insertions(+), 28 deletions(-) delete mode 100644 unittest/force-styles/TestRunner.cmake diff --git a/src/utils.cpp b/src/utils.cpp index 1e1ded3c0b..ae4575980f 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -430,6 +430,86 @@ size_t utils::trim_and_count_words(const std::string & text, const std::string & return utils::count_words(utils::trim_comment(text), separators); } +/* ---------------------------------------------------------------------- + Convert string into words on whitespace while handling single and + double quotes. +------------------------------------------------------------------------- */ +std::vector utils::split_words(const std::string &text) +{ + std::vector list; + const char *buf = text.c_str(); + std::size_t beg = 0; + std::size_t len = 0; + char c = *buf; + + while (c) { + // leading whitespace + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f') { + c = *++buf; + ++beg; + continue; + }; + len = 0; + + // handle escaped/quoted text. + quoted: + + // handle single quote + if (c == '\'') { + c = *++buf; + ++len; + while (((c != '\'') && (c != '\0')) + || ((c == '\\') && (buf[1] == '\''))) { + if ((c == '\\') && (buf[1] == '\'')) { + ++buf; + ++len; + } + c = *++buf; + ++len; + } + c = *++buf; + ++len; + + // handle double quote + } else if (c == '"') { + c = *++buf; + ++len; + while (((c != '"') && (c != '\0')) + || ((c == '\\') && (buf[1] == '"'))) { + if ((c == '\\') && (buf[1] == '"')) { + ++buf; + ++len; + } + c = *++buf; + ++len; + } + c = *++buf; + ++len; + } + + // unquoted + while (1) { + if ((c == '\'') || (c == '"')) goto quoted; + // skip escaped quote + if ((c == '\\') && ((buf[1] == '\'') || (buf[1] == '"'))) { + ++buf; + ++len; + c = *++buf; + ++len; + } + if ((c == ' ') || (c == '\t') || (c == '\r') || (c == '\n') + || (c == '\f') || (c == '\0')) { + list.push_back(text.substr(beg,len)); + beg += len; + break; + } + c = *++buf; + ++len; + } + } + return list; +} + /* ---------------------------------------------------------------------- Return whether string is a valid integer number ------------------------------------------------------------------------- */ diff --git a/src/utils.h b/src/utils.h index 91e5024121..e43be03a87 100644 --- a/src/utils.h +++ b/src/utils.h @@ -18,6 +18,7 @@ #include "lmptype.h" #include +#include #include namespace LAMMPS_NS { @@ -181,6 +182,20 @@ namespace LAMMPS_NS { */ size_t trim_and_count_words(const std::string & text, const std::string & separators = " \t\r\n\f"); + /** + * \brief Take text and split into non-whitespace words. + * + * This can handle single and double quotes, escaped quotes, + * and escaped codes within quotes, but due to using an STL + * container and STL strings is rather slow because of making + * copies. Designed for parsing command lines and similar text + * and not for time critical processing. Use a tokenizer for that. + * + * \param text string that should be split + * \return STL vector with the words + */ + std::vector split_words(const std::string &text); + /** * \brief Check if string can be converted to valid integer * \param text string that should be checked diff --git a/unittest/force-styles/CMakeLists.txt b/unittest/force-styles/CMakeLists.txt index aed715ee65..f68f58cd48 100644 --- a/unittest/force-styles/CMakeLists.txt +++ b/unittest/force-styles/CMakeLists.txt @@ -37,20 +37,15 @@ target_link_libraries(pair_style PRIVATE lammps style_tests) file(GLOB MOL_PAIR_TESTS LIST_DIRECTORIES false ${TEST_INPUT_FOLDER}/mol-pair-*.yaml) foreach(TEST ${MOL_PAIR_TESTS}) string(REGEX REPLACE "^.*mol-pair-(.*)\.yaml" "MolPairStyle:\\1" TNAME ${TEST}) - add_test(NAME ${TNAME} - COMMAND ${CMAKE_COMMAND} -DTEST_EXECUTABLE=$ - -DTEST_INPUT=${TEST} -DTEST_NAME=${TNAME} -P ${CMAKE_CURRENT_SOURCE_DIR}/TestRunner.cmake - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_test(NAME ${TNAME} COMMAND pair_style ${TEST} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}") endforeach() # tests for metal-like atomic systems and related pair styles file(GLOB ATOMIC_PAIR_TESTS LIST_DIRECTORIES false ${TEST_INPUT_FOLDER}/atomic-pair-*.yaml) foreach(TEST ${ATOMIC_PAIR_TESTS}) string(REGEX REPLACE "^.*atomic-pair-(.*)\.yaml" "AtomicPairStyle:\\1" TNAME ${TEST}) - add_test(NAME ${TNAME} - COMMAND ${CMAKE_COMMAND} -DTEST_EXECUTABLE=$ - -DTEST_INPUT=${TEST} -DTEST_NAME=${TNAME} -P ${CMAKE_CURRENT_SOURCE_DIR}/TestRunner.cmake - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_test(NAME ${TNAME} COMMAND pair_style ${TEST} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}") endforeach() @@ -58,10 +53,7 @@ endforeach() file(GLOB MANYBODY_PAIR_TESTS LIST_DIRECTORIES false ${TEST_INPUT_FOLDER}/manybody-pair-*.yaml) foreach(TEST ${MANYBODY_PAIR_TESTS}) string(REGEX REPLACE "^.*manybody-pair-(.*)\.yaml" "ManybodyPairStyle:\\1" TNAME ${TEST}) - add_test(NAME ${TNAME} - COMMAND ${CMAKE_COMMAND} -DTEST_EXECUTABLE=$ - -DTEST_INPUT=${TEST} -DTEST_NAME=${TNAME} -P ${CMAKE_CURRENT_SOURCE_DIR}/TestRunner.cmake - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_test(NAME ${TNAME} COMMAND pair_style ${TEST} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}") endforeach() @@ -72,10 +64,8 @@ target_link_libraries(bond_style PRIVATE lammps style_tests) file(GLOB BOND_TESTS LIST_DIRECTORIES false ${TEST_INPUT_FOLDER}/bond-*.yaml) foreach(TEST ${BOND_TESTS}) string(REGEX REPLACE "^.*bond-(.*)\.yaml" "BondStyle:\\1" TNAME ${TEST}) - add_test(NAME ${TNAME} - COMMAND ${CMAKE_COMMAND} -DTEST_EXECUTABLE=$ - -DTEST_INPUT=${TEST} -DTEST_NAME=${TNAME} -P ${CMAKE_CURRENT_SOURCE_DIR}/TestRunner.cmake - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_test(NAME ${TNAME} COMMAND bond_style ${TEST} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}") endforeach() # angle style tester @@ -85,8 +75,6 @@ target_link_libraries(angle_style PRIVATE lammps style_tests) file(GLOB ANGLE_TESTS LIST_DIRECTORIES false ${TEST_INPUT_FOLDER}/angle-*.yaml) foreach(TEST ${ANGLE_TESTS}) string(REGEX REPLACE "^.*angle-(.*)\.yaml" "AngleStyle:\\1" TNAME ${TEST}) - add_test(NAME ${TNAME} - COMMAND ${CMAKE_COMMAND} -DTEST_EXECUTABLE=$ - -DTEST_INPUT=${TEST} -DTEST_NAME=${TNAME} -P ${CMAKE_CURRENT_SOURCE_DIR}/TestRunner.cmake - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_test(NAME ${TNAME} COMMAND angle_style ${TEST} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set_tests_properties(${TNAME} PROPERTIES ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}") endforeach() diff --git a/unittest/force-styles/TestRunner.cmake b/unittest/force-styles/TestRunner.cmake deleted file mode 100644 index 325704137c..0000000000 --- a/unittest/force-styles/TestRunner.cmake +++ /dev/null @@ -1,8 +0,0 @@ -# workaround to allow passing extra arguments to test runs -# through ctest via a TEST_ARGS environment variable -# This can be used to, e.g. reset reference data for individual -# tests from the build folder with "env TEST_ARGS=-u ctest -R sometest" -execute_process(COMMAND ${TEST_EXECUTABLE} ${TEST_INPUT} $ENV{TEST_ARGS} RESULT_VARIABLE rv) -if(NOT "${rv}" STREQUAL "0") - message(FATAL_ERROR "Test ${TEST_NAME} failed with status ${rv}") -endif() diff --git a/unittest/force-styles/test_main.cpp b/unittest/force-styles/test_main.cpp index 6ff5194076..e9d08b61c4 100644 --- a/unittest/force-styles/test_main.cpp +++ b/unittest/force-styles/test_main.cpp @@ -17,9 +17,11 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include #include #include #include +#include // common read_yaml_file function bool read_yaml_file(const char *infile, TestConfig &config) @@ -76,6 +78,19 @@ int main(int argc, char **argv) return 2; } + // handle arguments passed via environment variable + std::vector env = utils::split_words(getenv("TEST_ARGS")); + for (auto arg : env) { + if (arg == "-u") { + generate_yaml_file(argv[1], test_config); + return 0; + } else if (arg == "-s") { + print_stats = true; + } else if (arg == "-v") { + verbose = true; + } + } + int iarg = 2; while (iarg < argc) { diff --git a/unittest/utils/test_utils.cpp b/unittest/utils/test_utils.cpp index ff73f2de22..c1312e7725 100644 --- a/unittest/utils/test_utils.cpp +++ b/unittest/utils/test_utils.cpp @@ -18,6 +18,7 @@ #include #include #include +#include using namespace LAMMPS_NS; using ::testing::Eq; @@ -48,6 +49,24 @@ TEST(Utils, count_words_with_extra_spaces) ASSERT_EQ(utils::count_words(" some text # comment "), 4); } +TEST(Utils, split_words_simple) +{ + std::vector list = utils::split_words("one two three"); + ASSERT_EQ(list.size(), 3); +} + +TEST(Utils, split_words_quoted) +{ + std::vector list = utils::split_words("one 'two' \"three\""); + ASSERT_EQ(list.size(), 3); +} + +TEST(Utils, split_words_escaped) +{ + std::vector list = utils::split_words("1\\' '\"two\"' 3\\\""); + ASSERT_EQ(list.size(), 3); +} + TEST(Utils, valid_integer1) { ASSERT_TRUE(utils::is_integer("10"));