From d1f75bfc663403e4760c7a53178e0b36e0688184 Mon Sep 17 00:00:00 2001 From: "Dr. Colin Hirsch" Date: Tue, 2 Apr 2024 18:22:23 +0200 Subject: [PATCH] Improve test coverage. --- .codecov.yml | 2 + .../tao/config/internal/phase2_additions.hpp | 2 +- include/tao/config/internal/phase3_remove.hpp | 6 +-- .../tao/config/internal/system_utility.hpp | 6 +-- include/tao/config/key_part.hpp | 9 +--- src/test/config/CMakeLists.txt | 4 +- src/test/config/access.cpp | 1 + src/test/config/assign.cpp | 1 + src/test/config/debug_traits.cpp | 29 ++++++++++++ src/test/config/failure.cpp | 23 +++------- src/test/config/success.cpp | 28 ++++++++++- src/test/config/to_stream.cpp | 46 +++++++++++++++++++ tests/binary.jaxn | 5 ++ tests/binary.success | 3 ++ tests/complex_01.failure | 2 + tests/complex_02.failure | 4 ++ tests/reference_11.failure | 2 + tests/reference_12.failure | 2 + 18 files changed, 143 insertions(+), 32 deletions(-) create mode 100644 .codecov.yml create mode 100644 src/test/config/debug_traits.cpp create mode 100644 src/test/config/to_stream.cpp create mode 100644 tests/binary.jaxn create mode 100644 tests/binary.success create mode 100644 tests/complex_01.failure create mode 100644 tests/complex_02.failure create mode 100644 tests/reference_11.failure create mode 100644 tests/reference_12.failure diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..7f976a2 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,2 @@ +ignore: + - src/example/**/* diff --git a/include/tao/config/internal/phase2_additions.hpp b/include/tao/config/internal/phase2_additions.hpp index f94c316..93fcc7b 100644 --- a/include/tao/config/internal/phase2_additions.hpp +++ b/include/tao/config/internal/phase2_additions.hpp @@ -182,7 +182,7 @@ namespace tao::config::internal [[noreturn]] static void throw_type_error( const entry& l, const entry& r ) { - throw pegtl::parse_error( strcat( "incompatible or invalid type(s) in addition", l.kind(), "@", l.get_position(), " and ", r.kind(), "@", r.get_position() ), pegtl::position( 0, 0, 0, "(todo) location of '+'" ) ); + throw pegtl::parse_error( strcat( "incompatible or invalid type(s) in addition ", l.kind(), "@", l.get_position(), " and ", r.kind(), "@", r.get_position() ), pegtl::position( 0, 0, 0, "(todo) location of '+'" ) ); } static void process_object( object&& l, object& r ) diff --git a/include/tao/config/internal/phase3_remove.hpp b/include/tao/config/internal/phase3_remove.hpp index a10efa6..f896b78 100644 --- a/include/tao/config/internal/phase3_remove.hpp +++ b/include/tao/config/internal/phase3_remove.hpp @@ -84,7 +84,7 @@ namespace tao::config::internal continue; case entry_kind::ARRAY: if( !e.get_array().function.empty() ) { - throw pegtl::parse_error( "unresolved function '" + e.get_array().function + '\'', e.get_array().position ); + throw pegtl::parse_error( "function '" + e.get_array().function + "' could not be called", e.get_array().position ); } phase3_remove( e.get_array() ); continue; @@ -92,9 +92,9 @@ namespace tao::config::internal phase3_remove( e.get_object() ); continue; case entry_kind::ASTERISK: - throw pegtl::parse_error( "unresolved asterisk", e.get_asterisk().position ); // Can happen when there are also unresolved references. + throw pegtl::parse_error( "asterisk could not be expanded", e.get_asterisk().position ); // Can happen when there are also unresolved references. case entry_kind::REFERENCE: - throw pegtl::parse_error( "unresolved reference '" + e.get_reference().to_string() + '\'', e.get_reference().at( 0 ).position ); + throw pegtl::parse_error( "reference '" + e.get_reference().to_string() + "' could not be resolved", e.get_reference().at( 0 ).position ); } throw std::logic_error( "code should be unreachable" ); // LCOV_EXCL_LINE } diff --git a/include/tao/config/internal/system_utility.hpp b/include/tao/config/internal/system_utility.hpp index 8dc05dc..e1eddd0 100644 --- a/include/tao/config/internal/system_utility.hpp +++ b/include/tao/config/internal/system_utility.hpp @@ -62,14 +62,14 @@ namespace tao::config::internal } #if !defined( _MSC_VER ) - [[nodiscard]] inline std::string shell_popen_throws( const pegtl::position& /*pos*/, const std::string& script ) + [[nodiscard]] inline std::string shell_popen_throws( const pegtl::position& pos, const std::string& script ) { errno = 0; std::unique_ptr< FILE, void ( * )( FILE* ) > file( ::popen( script.c_str(), "r" ), []( FILE* f ) { ::pclose( f ); } ); if( !file ) { - throw std::string( "TODO" ); + throw pegtl::parse_error( "TODO: Better error message for shell function error.", pos ); // throw pegtl::parse_error( format( __FILE__, __LINE__, "popen failed", { { "command", script }, { "errno", errno } } ), pos ); } std::string result; @@ -81,7 +81,7 @@ namespace tao::config::internal errno = 0; if( ( errno != 0 ) || ( ::pclose( file.release() ) != 0 ) ) { - throw std::string( "TODO" ); + throw pegtl::parse_error( "TODO: Better error message for shell function error.", pos ); } return result; } diff --git a/include/tao/config/key_part.hpp b/include/tao/config/key_part.hpp index d0fd67f..87122cf 100644 --- a/include/tao/config/key_part.hpp +++ b/include/tao/config/key_part.hpp @@ -4,7 +4,6 @@ #ifndef TAO_CONFIG_KEY_PART_HPP #define TAO_CONFIG_KEY_PART_HPP -#include #include #include #include @@ -43,16 +42,12 @@ namespace tao::config [[nodiscard]] std::size_t get_index() const noexcept { - const auto* s = std::get_if< std::size_t >( &data ); - assert( s != nullptr ); - return *s; + return std::get< std::size_t >( data ); } [[nodiscard]] const std::string& get_name() const noexcept { - const auto* s = std::get_if< std::string >( &data ); - assert( s != nullptr ); - return *s; + return std::get< std::string >( data ); } data_t data; diff --git a/src/test/config/CMakeLists.txt b/src/test/config/CMakeLists.txt index be7c3b8..640d2d3 100644 --- a/src/test/config/CMakeLists.txt +++ b/src/test/config/CMakeLists.txt @@ -4,6 +4,7 @@ set(testsources access.cpp assign.cpp custom.cpp + debug_traits.cpp enumerations.cpp failure.cpp multi_line_string_position.cpp @@ -11,6 +12,7 @@ set(testsources parse_key.cpp parse_reference2.cpp success.cpp + to_stream.cpp ) # file(GLOB ...) is used to validate the above list of test_sources @@ -27,7 +29,7 @@ foreach(testsourcefile ${testsources}) add_executable(${exename} ${testsourcefile}) target_link_libraries(${exename} PRIVATE taocpp::config) set_target_properties(${exename} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF ) diff --git a/src/test/config/access.cpp b/src/test/config/access.cpp index 0055175..93b315d 100644 --- a/src/test/config/access.cpp +++ b/src/test/config/access.cpp @@ -24,6 +24,7 @@ namespace tao::config TAO_CONFIG_TEST_ASSERT( access( v, key( "c.d.e.1" ) ).key == key( "c.d.e.1" ) ); TAO_CONFIG_TEST_THROWS( (void)access( v, key( "0" ) ) ); + TAO_CONFIG_TEST_THROWS( (void)access( v, key( "a.0" ) ) ); TAO_CONFIG_TEST_THROWS( (void)access( v, key( "r" ) ) ); TAO_CONFIG_TEST_THROWS( (void)access( v, key( "c.d.e.f" ) ) ); TAO_CONFIG_TEST_THROWS( (void)access( v, key( "c.d.e.2" ) ) ); diff --git a/src/test/config/assign.cpp b/src/test/config/assign.cpp index 38de534..e887a59 100644 --- a/src/test/config/assign.cpp +++ b/src/test/config/assign.cpp @@ -26,6 +26,7 @@ namespace tao::config TAO_CONFIG_TEST_THROWS( (void)assign( v, key( "0" ) ) ); TAO_CONFIG_TEST_THROWS( (void)assign( v, key( "c.d.e.f" ) ) ); TAO_CONFIG_TEST_THROWS( (void)assign( v, key( "c.d.e.2" ) ) ); + TAO_CONFIG_TEST_THROWS( (void)assign( v, key( "a.0" ) ) ); TAO_CONFIG_TEST_ASSERT( assign( v, key( "r" ) ) == value( json::empty_object ) ); TAO_CONFIG_TEST_ASSERT( assign( v, key( "r.s.t" ) ) == value( json::empty_object ) ); diff --git a/src/test/config/debug_traits.cpp b/src/test/config/debug_traits.cpp new file mode 100644 index 0000000..ceca911 --- /dev/null +++ b/src/test/config/debug_traits.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2021-2024 Dr. Colin Hirsch and Daniel Frey +// Please see LICENSE for license or visit https://github.com/taocpp/config/ + +#include +#include +#include + +#include "test.hpp" + +#include +#include + +namespace tao::config +{ + void unit_test() + { + const std::string input = "a = 1 b { c = 42 d = [ null, true ] }"; + internal::config_parser cp; + cp.parse( std::string_view( input ), __FUNCTION__ ); + std::ostringstream oss; + internal::to_stream( oss, cp.st.root ); + const std::string output = oss.str(); + const std::string reference = "{position:\"(root):1:1\",object_data:{a:{remove:true,implicit:false,temporary:false,position:\"unit_test:1:1\",concat_list:[{type:\"unsigned\",data:1}]},b:{remove:false,implicit:false,temporary:false,position:\"unit_test:1:7\",concat_list:[{type:\"object\",data:{position:\"unit_test:1:9\",object_data:{c:{remove:true,implicit:false,temporary:false,position:\"unit_test:1:11\",concat_list:[{type:\"unsigned\",data:42}]},d:{remove:true,implicit:false,temporary:false,position:\"unit_test:1:18\",concat_list:[{type:\"array\",data:{position:\"unit_test:1:22\",function:\"\",array_data:[{remove:false,implicit:false,temporary:false,position:\"unit_test:1:24\",concat_list:[{type:\"null\"}]},{remove:false,implicit:false,temporary:false,position:\"unit_test:1:24\",concat_list:[{type:\"boolean\",data:true}]}]}}]}}}}]}}}"; + TAO_CONFIG_TEST_ASSERT( output == reference ); + } + +} // namespace tao::config + +#include "main.hpp" diff --git a/src/test/config/failure.cpp b/src/test/config/failure.cpp index 9d69cb3..fad0412 100644 --- a/src/test/config/failure.cpp +++ b/src/test/config/failure.cpp @@ -14,13 +14,15 @@ const char* ansi_text = "\033[33m"; namespace tao { - int failed = 0; + unsigned failed = 0; template< template< typename... > class Traits > void unit_test( const std::filesystem::path& path ) { try { + // For a failure testcase to succeed the next line must throw an error. const auto cc = config::basic_from_file< Traits >( path ); + // LCOV_EXCL_START const auto ccs = json::jaxn::to_string( cc ); ++failed; std::cerr << std::endl @@ -28,6 +30,7 @@ namespace tao std::cerr << "<<< Config parsed as config <<<" << std::endl; std::cerr << ccs << std::endl; std::cerr << ">>> Config parsed as config >>>" << std::endl; + // LCOV_EXCL_STOP } catch( const pegtl::parse_error_base& e ) { std::cout << ansi_text << "pegtl::parse_error: " << ansi_message << e.message() << ": " << ansi_source << e.position_string() << ansi_reset << std::endl; @@ -35,24 +38,12 @@ namespace tao catch( const std::exception& e ) { std::cout << "std::exception: " << e.what() << std::endl; } - catch( const std::string& s ) { - std::cout << "Exception with std::string: " << s << std::endl; - // TODO: Remove catch string when all temporary throw strings have been replaced with proper exceptions. - } } } // namespace tao -int main( int argc, char** argv ) +int main() { - if( argc > 1 ) { - for( int i = 1; i < argc; ++i ) { - std::cout << "Parsing " << argv[ i ] << std::endl; - tao::unit_test< tao::config::traits >( argv[ i ] ); - } - return 0; - } - unsigned count = 0; for( const auto& entry : std::filesystem::directory_iterator( "tests" ) ) { @@ -62,8 +53,8 @@ int main( int argc, char** argv ) ++count; } } - if( !tao::failed ) { + if( tao::failed == 0 ) { std::cerr << "All " << count << " testcases passed." << std::endl; } - return std::min( tao::failed, 127 ); + return std::min( int( tao::failed ), 127 ); } diff --git a/src/test/config/success.cpp b/src/test/config/success.cpp index 7e76e84..25973e4 100644 --- a/src/test/config/success.cpp +++ b/src/test/config/success.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -19,10 +20,12 @@ namespace tao #else errno = 0; if( ::setenv( name.c_str(), value.c_str(), 1 ) != 0 ) { + // LCOV_EXCL_START const auto e = errno; #endif (void)e; - throw std::string( "setenv failed" ); + throw std::runtime_error( "setenv failed" ); + // LCOV_EXCL_STOP } } @@ -31,16 +34,34 @@ namespace tao { try { const auto cc = config::basic_from_file< Traits >( path ); + const std::vector< std::filesystem::path > paths = { "tests/empty.success", path, "tests/empty.success" }; + const auto cf = config::basic_from_files< Traits >( paths ); + std::filesystem::path jaxn = path; jaxn.replace_extension( ".jaxn" ); const auto cj = config::basic_from_file< Traits >( jaxn ); const auto jj = json::jaxn::basic_from_file< Traits >( jaxn ); const auto ccs = json::jaxn::to_string( cc ); + const auto cfs = json::jaxn::to_string( cf ); const auto cjs = json::jaxn::to_string( cj ); const auto jjs = json::jaxn::to_string( jj ); + if( ccs != cfs ) { + // LCOV_EXCL_START + ++failed; + std::cerr << std::endl + << "Testcase '" << path << "' failed config test!" << std::endl; + std::cerr << "<<< Config parsed as config <<<" << std::endl; + std::cerr << ccs << std::endl; + std::cerr << ">>> Config parsed as config >>>" << std::endl; + std::cerr << "<<< Config parsed as config with empty files <<<" << std::endl; + std::cerr << jjs << std::endl; + std::cerr << ">>> Config parsed as config with empty files >>>" << std::endl; + // LCOV_EXCL_STOP + } if( ccs != jjs ) { + // LCOV_EXCL_START ++failed; std::cerr << std::endl << "Testcase '" << path << "' failed config test!" << std::endl; @@ -50,8 +71,10 @@ namespace tao std::cerr << "<<< Reference data parsed as jaxn <<<" << std::endl; std::cerr << jjs << std::endl; std::cerr << ">>> Reference data parsed as jaxn >>>" << std::endl; + // LCOV_EXCL_STOP } if( ccs != cjs ) { + // LCOV_EXCL_START ++failed; std::cerr << std::endl << "Testcase '" << path << "' failed identity test!" << std::endl; @@ -61,8 +84,10 @@ namespace tao std::cerr << "<<< Reference data parsed as config <<<" << std::endl; std::cerr << cjs << std::endl; std::cerr << ">>> Reference data parsed as config >>>" << std::endl; + // LCOV_EXCL_STOP } } + // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "Testcase '" << path << "' failed with exception '" << e.what() << "'" << std::endl; ++failed; @@ -71,6 +96,7 @@ namespace tao std::cerr << "Testcase '" << path << "' failed with error '" << s << "'" << std::endl; ++failed; } + // LCOV_EXCL_STOP } } // namespace tao diff --git a/src/test/config/to_stream.cpp b/src/test/config/to_stream.cpp new file mode 100644 index 0000000..594cb75 --- /dev/null +++ b/src/test/config/to_stream.cpp @@ -0,0 +1,46 @@ +// Copyright (c) 2024 Dr. Colin Hirsch and Daniel Frey +// Please see LICENSE for license or visit https://github.com/taocpp/config/ + +#include +#include +#include + +#include "test.hpp" + +#include + +namespace tao::config +{ + void unit_test() + { + const std::string input = "a = 1 b { c = 42 d = [ null, true ] }"; + const tao::config::value parsed = tao::config::from_string( input, "main" ); + { + std::ostringstream oss; + tao::config::to_stream( oss, parsed ); + const std::string string1 = oss.str(); + const std::string reference1 = "{meta:{key:[],position:\"(root):1:1\"},data:{a:{meta:{key:[{key_kind:\"name\",key_data:{index:0,value:\"a\"}}],position:\"main:1:6\"},data:1},b:{meta:{key:[{key_kind:\"name\",key_data:{index:0,value:\"b\"}}],position:\"main:1:9\"},data:{c:{meta:{key:[{key_kind:\"name\",key_data:{index:0,value:\"b\"}},{key_kind:\"name\",key_data:{index:0,value:\"c\"}}],position:\"main:1:17\"},data:42},d:{meta:{key:[{key_kind:\"name\",key_data:{index:0,value:\"b\"}},{key_kind:\"name\",key_data:{index:0,value:\"d\"}}],position:\"main:1:22\"},data:[{meta:{key:[{key_kind:\"name\",key_data:{index:0,value:\"b\"}},{key_kind:\"name\",key_data:{index:0,value:\"d\"}},{key_kind:\"index\",key_data:{index:1,value:0}}],position:\"main:1:24\"},data:null},{meta:{key:[{key_kind:\"name\",key_data:{index:0,value:\"b\"}},{key_kind:\"name\",key_data:{index:0,value:\"d\"}},{key_kind:\"index\",key_data:{index:1,value:1}}],position:\"main:1:30\"},data:true}]}}}}}"; + if( string1 != reference1 ) { + std::cerr << string1 << std::endl; + std::cerr << reference1 << std::endl; + std::cerr << "Config to_stream test 1 failed!" << std::endl; + ++failed; + } + } { + std::ostringstream oss; + tao::config::to_stream( oss, parsed, 3 ); + const std::string string2 = oss.str(); + const std::string reference2 = "{\n meta: {\n key: [],\n position: \"(root):1:1\"\n },\n data: {\n a: {\n meta: {\n key: [\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"a\"\n }\n }\n ],\n position: \"main:1:6\"\n },\n data: 1\n },\n b: {\n meta: {\n key: [\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"b\"\n }\n }\n ],\n position: \"main:1:9\"\n },\n data: {\n c: {\n meta: {\n key: [\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"b\"\n }\n },\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"c\"\n }\n }\n ],\n position: \"main:1:17\"\n },\n data: 42\n },\n d: {\n meta: {\n key: [\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"b\"\n }\n },\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"d\"\n }\n }\n ],\n position: \"main:1:22\"\n },\n data: [\n {\n meta: {\n key: [\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"b\"\n }\n },\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"d\"\n }\n },\n {\n key_kind: \"index\",\n key_data: {\n index: 1,\n value: 0\n }\n }\n ],\n position: \"main:1:24\"\n },\n data: null\n },\n {\n meta: {\n key: [\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"b\"\n }\n },\n {\n key_kind: \"name\",\n key_data: {\n index: 0,\n value: \"d\"\n }\n },\n {\n key_kind: \"index\",\n key_data: {\n index: 1,\n value: 1\n }\n }\n ],\n position: \"main:1:30\"\n },\n data: true\n }\n ]\n }\n }\n }\n }\n}"; + if( string2 != reference2 ) { + std:: cerr << string2.size() << " " << reference2.size() << std::endl; + std::cerr << string2 << std::endl; + std::cerr << reference2 << std::endl; + std::cerr << "Config to_stream test 2 failed!" << std::endl; + ++failed; + } + } + } + +} // namespace tao::config + +#include "main.hpp" diff --git a/tests/binary.jaxn b/tests/binary.jaxn new file mode 100644 index 0000000..2a4fd44 --- /dev/null +++ b/tests/binary.jaxn @@ -0,0 +1,5 @@ +{ + a : $3031, + b : $3031, + c : $3031 +} diff --git a/tests/binary.success b/tests/binary.success new file mode 100644 index 0000000..52addaa --- /dev/null +++ b/tests/binary.success @@ -0,0 +1,3 @@ +a = $3031 +b = $30 + $31 +c = $"\x30\x31" diff --git a/tests/complex_01.failure b/tests/complex_01.failure new file mode 100644 index 0000000..07da94a --- /dev/null +++ b/tests/complex_01.failure @@ -0,0 +1,2 @@ +a = (default (b) "foo") +b = (default (a) "bar") diff --git a/tests/complex_02.failure b/tests/complex_02.failure new file mode 100644 index 0000000..1725cf7 --- /dev/null +++ b/tests/complex_02.failure @@ -0,0 +1,4 @@ +a = {} +a.* = 42 +a.a.b = (default (a.a.c) "foo") +a.a.c = (default (a.a.b) "bar") diff --git a/tests/reference_11.failure b/tests/reference_11.failure new file mode 100644 index 0000000..f261efc --- /dev/null +++ b/tests/reference_11.failure @@ -0,0 +1,2 @@ +a.b.c = (b.x) +a.b.x = (b.c) diff --git a/tests/reference_12.failure b/tests/reference_12.failure new file mode 100644 index 0000000..4d7ad0e --- /dev/null +++ b/tests/reference_12.failure @@ -0,0 +1,2 @@ +a = ((y)) +y = ((a))