From 6bfbde5c24623ca8fbc02eb47d844bb7d71df191 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 1 Apr 2020 12:22:41 +0200 Subject: [PATCH] Improve test_system Split parent and child code into functions for better output Introduce TEST_EQUAL for better output Compare each entry in env-pointer and argv Don't check env-pointer values against getenv() as they might differ: When launching the program from e.g. bash with `temp=bar`then there is TEMP and temp in the list (env-pointer) but getenv/GetEnvironmentVariable is case insensitive picking up the first matching value. --- test/test.hpp | 18 ++++ test/test_system.cpp | 218 ++++++++++++++++++++++++++++++++----------- 2 files changed, 179 insertions(+), 57 deletions(-) diff --git a/test/test.hpp b/test/test.hpp index 9bfb6df..13eaad3 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -51,6 +51,16 @@ inline void test_failed(const char* expr, const char* file, const int line, cons throw std::runtime_error(ss.str()); } +template +inline void test_equal_impl(const T& lhs, const U& rhs, const char* file, const int line, const char* function) +{ + if(lhs == rhs) + return; + std::ostringstream ss; + ss << "[" << lhs << "!=" << rhs << "]"; + test_failed(ss.str().c_str(), file, line, function); +} + #ifdef _MSC_VER #define DISABLE_CONST_EXPR_DETECTED __pragma(warning(push)) __pragma(warning(disable : 4127)) #define DISABLE_CONST_EXPR_DETECTED_POP __pragma(warning(pop)) @@ -68,5 +78,13 @@ inline void test_failed(const char* expr, const char* file, const int line, cons test_failed(#x, __FILE__, __LINE__, __FUNCTION__); \ DISABLE_CONST_EXPR_DETECTED \ } while(0) DISABLE_CONST_EXPR_DETECTED_POP +#define TEST_EQ(lhs, rhs) \ + do \ + { \ + test_mon(); \ + test_equal_impl((lhs), (rhs), __FILE__, __LINE__, __FUNCTION__); \ + break; \ + DISABLE_CONST_EXPR_DETECTED \ + } while(0) DISABLE_CONST_EXPR_DETECTED_POP #endif // #ifndef BOOST_NOWIDE_LIB_TEST_H_INCLUDED diff --git a/test/test_system.cpp b/test/test_system.cpp index 07f3f23..8156424 100644 --- a/test/test_system.cpp +++ b/test/test_system.cpp @@ -5,76 +5,180 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif #include "test.hpp" #include #include +#include +#include +#include #include #include +#include +#include + +bool is_ascii(const std::string& s) +{ + for(std::string::const_iterator it = s.begin(); it != s.end(); ++it) + { + if(static_cast(*it) > 0x7F) + return false; + } + return true; +} + +std::string replace_non_ascii(const std::string& s) +{ + std::string::const_iterator it = s.begin(); + namespace utf = boost::nowide::detail::utf; + typedef utf::utf_traits utf8; + std::string result; + result.reserve(s.size()); + while(it != s.end()) + { + utf::code_point c = utf8::decode(it, s.end()); + TEST(c != utf::illegal && c != utf::incomplete); + if(c > 0x7F) + c = '?'; // WinAPI seems to do this + result.push_back(static_cast(c)); + } + return result; +} + +void compare_string_arrays(char** main_val, char** utf8_val, bool sort) +{ + std::vector vec_main, vec_utf8; + for(; *main_val; ++main_val) + vec_main.push_back(std::string(*main_val)); + for(; *utf8_val; ++utf8_val) + vec_utf8.push_back(std::string(*utf8_val)); + // Same number of strings + TEST_EQ(vec_main.size(), vec_utf8.size()); + if(sort) + { + // Order doesn't matter + std::sort(vec_main.begin(), vec_main.end()); + std::sort(vec_utf8.begin(), vec_utf8.end()); + } + for(size_t i = 0; i < vec_main.size(); ++i) + { + // Skip strings with non-ascii chars + if(!is_ascii(vec_main[i])) + continue; + if(vec_main[i] != vec_utf8[i]) + TEST_EQ(vec_main[i], replace_non_ascii(vec_utf8[i])); + } +} + +void compare_getenv(char** env) +{ + // For all all variables in env check against getenv + for(char** e = env; *e != 0; e++) + { + const char* key_begin = *e; + const char* key_end = strchr(key_begin, '='); + TEST(key_end); + std::string key = std::string(key_begin, key_end); + const char* std_value = std::getenv(key.c_str()); + const char* bnw_value = boost::nowide::getenv(key.c_str()); + // If std_value is set, bnw value must be too and be equal, else bnw value must be unset too + if(std_value) + { + TEST(bnw_value); + // Compare only if ascii + if(!is_ascii(std_value)) + continue; + if(std::string(std_value) != std::string(bnw_value)) + TEST_EQ(std_value, replace_non_ascii(bnw_value)); + } else + TEST(!bnw_value); + } +} + +const std::string example = "\xd7\xa9-\xd0\xbc-\xce\xbd"; + +void run_child(int argc, char** argv, char** env) +{ + // Test arguments + TEST(argc == 2); + TEST_EQ(argv[1], example); + TEST(argv[2] == 0); + + // Test getenv + TEST(boost::nowide::getenv("BOOST_NOWIDE_TEST")); + TEST_EQ(boost::nowide::getenv("BOOST_NOWIDE_TEST"), example); + TEST(boost::nowide::getenv("BOOST_NOWIDE_TEST_NONE") == 0); + // Empty variables are unreliable on windows, hence skip. E.g. using "set FOO=" unsets FOO +#ifndef BOOST_WINDOWS + TEST(boost::nowide::getenv("BOOST_NOWIDE_EMPTY")); + TEST_EQ(boost::nowide::getenv("BOOST_NOWIDE_EMPTY"), std::string()); +#endif // !_WIN32 + + // This must be contained in env + std::string sample = "BOOST_NOWIDE_TEST=" + example; + bool found = false; + for(char** e = env; *e != 0; e++) + { + if(*e == sample) + found = true; + } + TEST(found); + + std::cout << "Subprocess ok" << std::endl; +} + +void run_parent(const char* exe_path) +{ +#if BOOST_NOWIDE_TEST_USE_NARROW + TEST(boost::nowide::setenv("BOOST_NOWIDE_TEST", example.c_str(), 1) == 0); + TEST(boost::nowide::setenv("BOOST_NOWIDE_TEST_NONE", example.c_str(), 1) == 0); + TEST(boost::nowide::unsetenv("BOOST_NOWIDE_TEST_NONE") == 0); + TEST(boost::nowide::setenv("BOOST_NOWIDE_EMPTY", "", 1) == 0); + TEST(boost::nowide::getenv("BOOST_NOWIDE_EMPTY")); + std::string command = "\""; + command += exe_path; + command += "\" "; + command += example; + TEST(boost::nowide::system(command.c_str()) == 0); + std::cout << "Parent ok" << std::endl; +#else + std::wstring envVar = L"BOOST_NOWIDE_TEST=" + boost::nowide::widen(example); + TEST(_wputenv(envVar.c_str()) == 0); + std::wstring wcommand = boost::nowide::widen(exe_path) + L" " + boost::nowide::widen(example); + TEST(_wsystem(wcommand.c_str()) == 0); + std::cout << "Wide Parent ok" << std::endl; +#endif +} int main(int argc, char** argv, char** env) { try { - std::string example = "\xd7\xa9-\xd0\xbc-\xce\xbd"; - boost::nowide::args a(argc, argv, env); - if(argc == 2 && argv[1][0] != '-') + const int old_argc = argc; + char** old_argv = argv; + char** old_env = env; { - TEST(argv[1] == example); - TEST(argv[2] == 0); - TEST(boost::nowide::getenv("BOOST_NOWIDE_TEST")); - TEST(boost::nowide::getenv("BOOST_NOWIDE_TEST_NONE") == 0); - TEST(boost::nowide::getenv("BOOST_NOWIDE_TEST") == example); - // Empty variables are unreliable on windows, hence skip. E.g. using "set FOO=" unsets FOO -#ifndef BOOST_WINDOWS - TEST(boost::nowide::getenv("BOOST_NOWIDE_EMPTY")); - TEST(boost::nowide::getenv("BOOST_NOWIDE_EMPTY") == std::string()); -#endif // !_WIN32 - - std::string sample = "BOOST_NOWIDE_TEST=" + example; - bool found = false; - for(char** e = env; *e != 0; e++) - { - char* eptr = *e; - std::cout << "Checking " << eptr << std::endl; - char* key_end = strchr(eptr, '='); - TEST(key_end); - std::string key = std::string(eptr, key_end); - std::string value = key_end + 1; -#ifdef BOOST_WINDOWS - if(value.empty()) - continue; -#endif - std::cout << "Key: " << key << " Value: " << value << std::endl; - TEST(boost::nowide::getenv(key.c_str())); - TEST(boost::nowide::getenv(key.c_str()) == value); - if(*e == sample) - found = true; - } - TEST(found); - std::cout << "Subprocess ok" << std::endl; - } else if(argc == 1) - { -#if BOOST_NOWIDE_TEST_USE_NARROW - TEST(boost::nowide::setenv("BOOST_NOWIDE_TEST", example.c_str(), 1) == 0); - TEST(boost::nowide::setenv("BOOST_NOWIDE_TEST_NONE", example.c_str(), 1) == 0); - TEST(boost::nowide::unsetenv("BOOST_NOWIDE_TEST_NONE") == 0); - TEST(boost::nowide::setenv("BOOST_NOWIDE_EMPTY", "", 1) == 0); - TEST(boost::nowide::getenv("BOOST_NOWIDE_EMPTY")); - std::string command = "\""; - command += argv[0]; - command += "\" "; - command += example; - TEST(boost::nowide::system(command.c_str()) == 0); - std::cout << "Parent ok" << std::endl; -#else - std::wstring envVar = L"BOOST_NOWIDE_TEST=" + boost::nowide::widen(example); - TEST(_wputenv(envVar.c_str()) == 0); - std::wstring wcommand = boost::nowide::widen(argv[0]) + L" " + boost::nowide::widen(example); - TEST(_wsystem(wcommand.c_str()) == 0); - std::cout << "Wide Parent ok" << std::endl; -#endif + boost::nowide::args _(argc, argv, env); + TEST(argc == old_argc); + std::cout << "Checking arguments" << std::endl; + compare_string_arrays(old_argv, argv, false); + std::cout << "Checking env" << std::endl; + compare_string_arrays(old_env, env, true); + compare_getenv(env); } + // When `args` is destructed the old values must be restored + TEST(argc == old_argc); + TEST(argv == old_argv); + TEST(env == old_env); + + boost::nowide::args a(argc, argv, env); + if(argc == 1) + run_parent(argv[0]); + else + run_child(argc, argv, env); } catch(const std::exception& e) { std::cerr << "Failed " << e.what() << std::endl;