diff --git a/src/tests/strToNum/CMakeLists.txt b/src/tests/strToNum/CMakeLists.txt new file mode 100644 index 000000000..613bb0cc9 --- /dev/null +++ b/src/tests/strToNum/CMakeLists.txt @@ -0,0 +1,37 @@ +#------------------------------------------------------ +# CMakeLists.txt for strToNum +# +# little stand-alone test driver for the dependency-free +# PStringUtils class (pure C++17, no ROOT needed). +# +# build (stand-alone): +# cmake -S . -B build +# cmake --build build +# ./build/strToNum +# +# Andreas Suter, 2026/06/06 +#------------------------------------------------------ +cmake_minimum_required(VERSION 3.9) + +project(strToNum VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif (NOT CMAKE_BUILD_TYPE) + +#--- the class under test lives in src/classes, its header in src/include ----- +set(MUSRFIT_SRC ${CMAKE_CURRENT_SOURCE_DIR}/../../classes) +set(MUSRFIT_INC ${CMAKE_CURRENT_SOURCE_DIR}/../../include) + +add_executable(strToNum + strToNum.cpp + ${MUSRFIT_SRC}/PStringUtils.cpp +) + +target_include_directories(strToNum + PRIVATE ${MUSRFIT_INC} +) diff --git a/src/tests/strToNum/strToNum.cpp b/src/tests/strToNum/strToNum.cpp new file mode 100644 index 000000000..f06f53ace --- /dev/null +++ b/src/tests/strToNum/strToNum.cpp @@ -0,0 +1,300 @@ +/*************************************************************************** + + strToNum.cpp + + Author: Andreas Suter + e-mail: andreas.suter@psi.ch + + Little stand-alone test driver for the PStringUtils class. It exercises + Split / IsInt / IsFloat / ToInt / ToDouble / IsEqualNoCase / + ContainsNoCase / BeginsWithNoCase and reports a pass/fail summary. + + Usage: + strToNum -> run the built-in test suite + strToNum -> show what PStringUtils makes of the given + string(s) on the command line (ad-hoc checks) + strToNum -i -> interactive mode: type a string at the prompt + and see the result of every PStringUtils method + (empty line or 'quit' to leave) + +***************************************************************************/ + +/*************************************************************************** + * Copyright (C) 2007-2026 by Andreas Suter * + * andreas.suter@psi.ch * + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include +#include +#include +#include + +#include "PStringUtils.h" + +//-------------------------------------------------------------------------- +// tiny test bookkeeping +//-------------------------------------------------------------------------- +static int gPassed = 0; +static int gFailed = 0; + +static void check(const std::string &what, bool ok) +{ + if (ok) { + ++gPassed; + std::cout << " [ ok ] " << what << std::endl; + } else { + ++gFailed; + std::cout << " [FAIL] " << what << std::endl; + } +} + +//-------------------------------------------------------------------------- +static std::string vecToStr(const std::vector &v) +{ + std::string s = "{"; + for (std::vector::size_type i = 0; i < v.size(); ++i) { + s += "'" + v[i] + "'"; + if (i + 1 < v.size()) + s += ", "; + } + s += "}"; + return s; +} + +//-------------------------------------------------------------------------- +static void testSplit() +{ + std::cout << "Split:" << std::endl; + + std::vector r = PStringUtils::Split("alpha beta gamma", " "); + std::cout << " input: 'alpha beta gamma', delim: ' '" << std::endl; + check("3 tokens on single space delimiter", + r.size() == 3 && r[0] == "alpha" && r[1] == "beta" && r[2] == "gamma"); + std::cout << "----" << std::endl; + r = PStringUtils::Split(" , 1 , 2 ,, 3 ,", " ,"); + std::cout << " input: ' , 1 , 2 ,, 3 ,', delim: ' ,'" << std::endl; + std::cout << " -> "; + for (auto i: r) + std::cout << i << ", "; + std::cout << std::endl; + check("mixed/repeated delimiters, empty tokens skipped", + r.size() == 3 && r[0] == "1" && r[1] == "2" && r[2] == "3"); + std::cout << "----" << std::endl; + + r = PStringUtils::Split("", " "); + check("empty input -> no tokens", r.empty()); + + r = PStringUtils::Split(" ", " "); + check("delimiters only -> no tokens", r.empty()); + + r = PStringUtils::Split("nodelim", ",;"); + check("no delimiter present -> single token", + r.size() == 1 && r[0] == "nodelim"); +} + +//-------------------------------------------------------------------------- +static void testIsInt() +{ + std::cout << "IsInt:" << std::endl; + + check("'12345' is int", PStringUtils::IsInt("12345")); + check("' 42 ' (surrounding ws) is int", PStringUtils::IsInt(" 42 ")); + check("'' is not int", !PStringUtils::IsInt("")); + check("' ' (ws only) is not int", !PStringUtils::IsInt(" ")); + check("'-5' is not int (sign not allowed)", !PStringUtils::IsInt("-5")); + check("'3.14' is not int", !PStringUtils::IsInt("3.14")); + check("'12a' is not int", !PStringUtils::IsInt("12a")); +} + +//-------------------------------------------------------------------------- +static void testIsFloat() +{ + std::cout << "IsFloat:" << std::endl; + + check("'3.14' is float", PStringUtils::IsFloat("3.14")); + check("'-1.2e-3' is float", PStringUtils::IsFloat("-1.2e-3")); + check("'+42' is float", PStringUtils::IsFloat("+42")); + check("'.5' is float", PStringUtils::IsFloat(".5")); + check("' 6.022e23 ' (surrounding ws) is float", + PStringUtils::IsFloat(" 6.022e23 ")); + check("'' is not float", !PStringUtils::IsFloat("")); + check("'nan' is not float", !PStringUtils::IsFloat("nan")); + check("'inf' is not float", !PStringUtils::IsFloat("inf")); + check("'1.2.3' is not float", !PStringUtils::IsFloat("1.2.3")); + check("'12abc' is not float", !PStringUtils::IsFloat("12abc")); +} + +//-------------------------------------------------------------------------- +static void testToInt() +{ + std::cout << "ToInt:" << std::endl; + + bool ok = false; + check("'42' -> 42, ok", PStringUtils::ToInt("42", &ok) == 42 && ok); + check("' -7' -> -7, ok", PStringUtils::ToInt(" -7", &ok) == -7 && ok); + check("'123abc' -> 123, ok (trailing ignored)", + PStringUtils::ToInt("123abc", &ok) == 123 && ok); + check("'abc' -> 0, !ok", PStringUtils::ToInt("abc", &ok) == 0 && !ok); + check("'' -> 0, !ok", PStringUtils::ToInt("", &ok) == 0 && !ok); + check("'99999999999999999999' -> out of range, !ok", + (PStringUtils::ToInt("99999999999999999999", &ok), !ok)); + check("null ok pointer is tolerated", PStringUtils::ToInt("17") == 17); +} + +//-------------------------------------------------------------------------- +static void testToDouble() +{ + std::cout << "ToDouble:" << std::endl; + + bool ok = false; + check("'3.14' -> 3.14, ok", + std::fabs(PStringUtils::ToDouble("3.14", &ok) - 3.14) < 1e-12 && ok); + check("' +1.5e2' -> 150, ok", + std::fabs(PStringUtils::ToDouble(" +1.5e2", &ok) - 150.0) < 1e-9 && ok); + check("'2.5xyz' -> 2.5, ok (trailing ignored)", + std::fabs(PStringUtils::ToDouble("2.5xyz", &ok) - 2.5) < 1e-12 && ok); + check("'abc' -> 0.0, !ok", PStringUtils::ToDouble("abc", &ok) == 0.0 && !ok); + check("'' -> 0.0, !ok", PStringUtils::ToDouble("", &ok) == 0.0 && !ok); + check("'1e400' -> out of range, !ok", + (PStringUtils::ToDouble("1e400", &ok), !ok)); + check("null ok pointer is tolerated", + std::fabs(PStringUtils::ToDouble("0.25") - 0.25) < 1e-12); +} + +//-------------------------------------------------------------------------- +static void testCaseHelpers() +{ + std::cout << "IsEqualNoCase / ContainsNoCase / BeginsWithNoCase:" << std::endl; + + check("'Fit' == 'fIT' (no case)", PStringUtils::IsEqualNoCase("Fit", "fIT")); + check("'Fit' != 'Fits'", !PStringUtils::IsEqualNoCase("Fit", "Fits")); + check("'' == ''", PStringUtils::IsEqualNoCase("", "")); + + check("'Hello World' contains 'LO WO' (no case)", + PStringUtils::ContainsNoCase("Hello World", "LO WO")); + check("empty needle is contained", + PStringUtils::ContainsNoCase("abc", "")); + check("needle longer than haystack is not contained", + !PStringUtils::ContainsNoCase("ab", "abc")); + + check("'THEORY' begins with 'the' (no case)", + PStringUtils::BeginsWithNoCase("THEORY", "the")); + check("'THEORY' does not begin with 'ory'", + !PStringUtils::BeginsWithNoCase("THEORY", "ory")); +} + +//-------------------------------------------------------------------------- +static void inspect(const std::string &str) +{ + std::cout << "Inspecting '" << str << "':" << std::endl; + std::cout << " Split(' \\t,;') = " + << vecToStr(PStringUtils::Split(str, " \t,;")) << std::endl; + std::cout << " IsInt = " << (PStringUtils::IsInt(str) ? "true" : "false") << std::endl; + std::cout << " IsFloat = " << (PStringUtils::IsFloat(str) ? "true" : "false") << std::endl; + + bool ok = false; + int i = PStringUtils::ToInt(str, &ok); + std::cout << " ToInt = " << i << " (ok=" << (ok ? "true" : "false") << ")" << std::endl; + double d = PStringUtils::ToDouble(str, &ok); + std::cout << " ToDouble = " << d << " (ok=" << (ok ? "true" : "false") << ")" << std::endl; +} + +//-------------------------------------------------------------------------- +static void usage(const char *prog) +{ + std::cout + << "usage: " << prog << " [options] [string ...]\n" + << "\n" + << "Little stand-alone test driver for the PStringUtils class. It exercises\n" + << "Split / IsInt / IsFloat / ToInt / ToDouble / IsEqualNoCase /\n" + << "ContainsNoCase / BeginsWithNoCase.\n" + << "\n" + << "options:\n" + << " -h, --help show this help and exit\n" + << " -i, --interactive interactive mode: type a string at the prompt and\n" + << " see the result of every PStringUtils method\n" + << " (empty line, 'quit'/'exit' or Ctrl-D leaves)\n" + << "\n" + << "arguments:\n" + << " string ... one or more strings to inspect; the result of every\n" + << " PStringUtils method is printed for each of them\n" + << "\n" + << "with no options and no arguments the built-in test suite is run; the exit\n" + << "code is 0 if all checks pass and 1 otherwise.\n" + << "\n" + << "examples:\n" + << " " << prog << " run the built-in test suite\n" + << " " << prog << " \" -42xyz\" 1.5e-3 inspect the given strings\n" + << " " << prog << " -i interactive mode\n"; +} + +//-------------------------------------------------------------------------- +static void interactive() +{ + std::cout << "==== PStringUtils interactive mode ====" << std::endl; + std::cout << "Enter a string to test the methods on it." << std::endl; + std::cout << "An empty line or 'quit' leaves." << std::endl << std::endl; + + std::string line; + while (true) { + std::cout << "strToNum> " << std::flush; + if (!std::getline(std::cin, line)) // EOF (e.g. Ctrl-D) + break; + if (line.empty() || line == "quit" || line == "exit") + break; + inspect(line); + std::cout << std::endl; + } +} + +//-------------------------------------------------------------------------- +int main(int argc, char *argv[]) +{ + if (argc > 1) { + const std::string arg1 = argv[1]; + if (arg1 == "-h" || arg1 == "--help") { + usage(argv[0]); + return 0; + } + if (arg1 == "-i" || arg1 == "--interactive") { + interactive(); + return 0; + } + // ad-hoc mode: inspect the strings given on the command line + for (int i = 1; i < argc; ++i) { + inspect(argv[i]); + std::cout << std::endl; + } + return 0; + } + + std::cout << "==== PStringUtils test suite ====" << std::endl << std::endl; + + testSplit(); + testIsInt(); + testIsFloat(); + testToInt(); + testToDouble(); + testCaseHelpers(); + + std::cout << std::endl + << "==== summary: " << gPassed << " passed, " + << gFailed << " failed ====" << std::endl; + + return (gFailed == 0) ? 0 : 1; +}