From 5ead9d6b72738d7dd00413633a5e651684d03355 Mon Sep 17 00:00:00 2001 From: Dan Notestein Date: Wed, 7 Jan 2026 17:53:00 -0500 Subject: [PATCH 1/2] Add GitLab CI for standalone FC builds and tests - Add .gitlab-ci.yml with parallel test jobs and Emscripten build - Add hive_targets.cmake for standalone builds (copied from Hive) - Add BOOST_COMPONENTS to CMakeLists.txt for standalone compilation - Migrate variant_test.cpp from Hive (pure FC tests) Pipeline structure: - build:linux - full build with test artifacts - build:emscripten - WASM compilation check (minimal FC) - 6 parallel test jobs (variant, sha, hmac, saturation, ecdsa, ecc) Estimated pipeline time: ~2-3 minutes --- .gitlab-ci.yml | 74 ++++ CMakeLists.txt | 14 + CMakeModules/hive_targets.cmake | 183 +++++++++ tests/CMakeLists.txt | 3 + tests/variant_test.cpp | 671 ++++++++++++++++++++++++++++++++ 5 files changed, 945 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 CMakeModules/hive_targets.cmake create mode 100644 tests/variant_test.cpp diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..97145af --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,74 @@ +stages: + - build + - test + +variables: + GIT_SUBMODULE_STRATEGY: recursive + GIT_SUBMODULE_DEPTH: 1 + CMAKE_BUILD_TYPE: Release + +default: + image: registry.gitlab.syncad.com/hive/common-ci-configuration/ci-base-image:ubuntu24.04-py3.14-2 + tags: + - public-runner-docker + +build:linux: + stage: build + script: + - mkdir -p build && cd build + - echo "=== Configure ===" && time cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + - echo "=== Build ===" && time make -j$(nproc) + artifacts: + paths: + - build/ + expire_in: 2 hours + +build:emscripten: + stage: build + image: registry.gitlab.syncad.com/hive/common-ci-configuration/emsdk:4.0.18-1 + script: + - mkdir -p build-wasm && cd build-wasm + - echo "=== Configure (Emscripten) ===" + - time cmake .. + -DCMAKE_TOOLCHAIN_FILE=/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake + -DCMAKE_BUILD_TYPE=Release + -DHIVE_BUILD_ON_MINIMAL_FC=ON + -DBoost_NO_WARN_NEW_VERSIONS=1 + -G Ninja + - echo "=== Build (Emscripten) ===" && time ninja -j$(nproc) + +test:variant: + stage: test + needs: ["build:linux"] + script: + - cd build && time ./tests/variant_test --log_level=test_suite + +test:sha: + stage: test + needs: ["build:linux"] + script: + - cd build && time ./tests/sha_test + +test:hmac: + stage: test + needs: ["build:linux"] + script: + - cd build && time ./tests/hmac_test + +test:saturation: + stage: test + needs: ["build:linux"] + script: + - cd build && time ./tests/saturation_test + +test:ecdsa: + stage: test + needs: ["build:linux"] + script: + - cd build && time ./tests/ecdsa_canon_test + +test:ecc: + stage: test + needs: ["build:linux"] + script: + - cd build && time ./tests/ecc_test testpassword /tmp/ecc_interop.dat diff --git a/CMakeLists.txt b/CMakeLists.txt index 188a177..95e65e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ INCLUDE( VersionMacros ) INCLUDE( SetupTargetMacros ) INCLUDE(GetGitRevisionDescription) INCLUDE(FindSSE) +INCLUDE(hive_targets) get_git_head_revision("${CMAKE_CURRENT_SOURCE_DIR}" GIT_REFSPEC FC_GIT_REVISION_SHA) get_git_unix_timestamp("${CMAKE_CURRENT_SOURCE_DIR}" FC_GIT_REVISION_UNIX_TIMESTAMP) @@ -482,6 +483,19 @@ TARGET_LINK_OPTIONS( CommonBuildOptions INTERFACE $<$:${HIVE_ASAN_L TARGET_LINK_LIBRARIES( fc_core PUBLIC CommonBuildOptions ) +# Define Boost components needed by FC +SET( BOOST_COMPONENTS ) +LIST( APPEND BOOST_COMPONENTS + chrono + context + coroutine + date_time + filesystem + iostreams + system + thread + unit_test_framework ) + ADD_LIBRARY( STATIC_BOOST INTERFACE ) SET_TARGET_PROPERTIES( STATIC_BOOST PROPERTIES POSITION_INDEPENDENT_CODE OFF ) ADD_TARGET_BOOST_LIBRARIES( STATIC_BOOST "${BOOST_COMPONENTS}" ) diff --git a/CMakeModules/hive_targets.cmake b/CMakeModules/hive_targets.cmake new file mode 100644 index 0000000..462824a --- /dev/null +++ b/CMakeModules/hive_targets.cmake @@ -0,0 +1,183 @@ +MACRO( ADD_TARGET_BOOST_LIBRARIES target_name components ) + SET( BOOST_COMPONENTS ${components} ) + + LIST ( TRANSFORM BOOST_COMPONENTS PREPEND "Boost::" OUTPUT_VARIABLE _boost_implicit_targets ) + + MESSAGE ( STATUS ${_boost_implicit_targets} ) + + SET( Boost_NO_BOOST_CMAKE ON CACHE STRING "ON or OFF" FORCE ) + + SET ( ORIGINAL_LIB_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES} ) + SET ( ORIGINAL_Boost_USE_STATIC_LIBS ${Boost_USE_STATIC_LIBS} ) + + GET_TARGET_PROPERTY(_target_type ${target_name} TYPE) + + MESSAGE( STATUS "Setting up Boost libraries for target: ${target_name} defined as: ${_target_type}" ) + + IF (_target_type STREQUAL "EXECUTABLE") + # Executable can always link against static Boost libraries, to reduce binary dependencies + SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" FORCE ) + SET( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX} ) + + ELSEIF (_target_type STREQUAL "SHARED_LIBRARY") + # Shared library shall always link against shared Boost libraries + SET( Boost_USE_STATIC_LIBS OFF CACHE STRING "ON or OFF" FORCE ) + SET( CMAKE_FIND_LIBRARY_SUFFIXES ".so") + + ELSE() + # Nothing to do for static libraries - they does not need to know dependencies at library level, since all symbols must be finally solved at .so or exec level + #FIND_PACKAGE( Boost 1.74 REQUIRED COMPONENTS ${BOOST_COMPONENTS} ) + GET_TARGET_PROPERTY(_target_PIC ${target_name} POSITION_INDEPENDENT_CODE) + + IF ( ${_target_PIC} ) + MESSAGE( STATUS "Target: ${target_name} requires boost dynamic linkage" ) + SET( Boost_USE_STATIC_LIBS OFF CACHE STRING "ON or OFF" FORCE ) + SET( CMAKE_FIND_LIBRARY_SUFFIXES ".so") + ELSE() + MESSAGE( STATUS "Target: ${target_name} can use boost static linkage" ) + SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" FORCE ) + SET( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX} ) + ENDIF() + + ENDIF () + + FIND_PACKAGE( Boost 1.74 REQUIRED COMPONENTS ${BOOST_COMPONENTS} ) + + TARGET_LINK_LIBRARIES( ${target_name} INTERFACE ${Boost_LIBRARIES} ) + + MESSAGE ( STATUS "Detected boost libs: ${Boost_LIBRARIES}" ) + + SET( CMAKE_FIND_LIBRARY_SUFFIXES ${ORIGINAL_LIB_SUFFIXES} ) + SET( Boost_USE_STATIC_LIBS ${ORIGINAL_Boost_USE_STATIC_LIBS} ) + + TARGET_INCLUDE_DIRECTORIES( ${target_name} INTERFACE ${Boost_INCLUDE_DIR} ) + +ENDMACRO() + +MACRO( ADD_TARGET_PACKAGE_LIBRARIES target_name _package_name use_static_libs ) + + SET( ORIGINAL_LIB_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES} ) + + STRING(TOUPPER ${_package_name} package_name) + + IF ( ${use_static_libs} ) + MESSAGE( STATUS "Setting up ${_package_name} STATIC libraries for target: ${target_name}" ) + SET(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) + SET( ${package_name}_USE_STATIC_LIBS TRUE ) + ELSE() + MESSAGE( STATUS "Setting up ${_package_name} SHARED libraries for target: ${target_name}" ) + SET(CMAKE_FIND_LIBRARY_SUFFIXES ".so") + SET( ${package_name}_USE_STATIC_LIBS FALSE ) + ENDIF () + + FIND_PACKAGE( ${_package_name} REQUIRED ) + + IF (${${package_name}_FOUND}) + target_compile_definitions( ${target_name} INTERFACE HAS_${package_name} ) + ENDIF() + + TARGET_INCLUDE_DIRECTORIES( ${target_name} INTERFACE ${${package_name}_INCLUDE_DIRS} ) + TARGET_LINK_LIBRARIES( ${target_name} INTERFACE ${${package_name}_LIBRARIES} ) + + MESSAGE ( STATUS "Detected ${_package_name} libs: ${${package_name}_LIBRARIES}" ) + + UNSET (${package_name}_USE_STATIC_LIBS CACHE) + UNSET (${package_name}_LIBRARY CACHE) + UNSET (${package_name}_LIBRARY_RELEASE CACHE) + UNSET (${package_name}_LIBRARIES CACHE) + + UNSET (${package_name}_USE_STATIC_LIBS ) + UNSET (${package_name}_LIBRARIES ) + UNSET (${package_name}_LIBRARY ) + UNSET (${package_name}_LIBRARY_RELEASE ) + + SET( CMAKE_FIND_LIBRARY_SUFFIXES ${ORIGINAL_LIB_SUFFIXES} ) + +ENDMACRO() + + +MACRO( ADD_TARGET_OPENSSL_LIBRARIES target_name use_static_libs ) + + IF ( ${use_static_libs} ) + MESSAGE( STATUS "Setting up OpenSSL STATIC libraries for target: ${target_name}" ) + SET( OPENSSL_USE_STATIC_LIBS TRUE ) + ELSE() + MESSAGE( STATUS "Setting up OpenSSL SHARED libraries for target: ${target_name}" ) + ENDIF () + + FIND_PACKAGE( OpenSSL REQUIRED ) + + TARGET_LINK_LIBRARIES( ${target_name} INTERFACE ${OPENSSL_LIBRARIES} ) + + MESSAGE ( STATUS "Detected OpenSSL libs: ${OPENSSL_LIBRARIES}" ) + + UNSET ( OPENSSL_USE_STATIC_LIBS ) + UNSET (OPENSSL_DIR CACHE ) + UNSET (OPENSSL_LIBRARIES CACHE) + UNSET (OPENSSL_VERSION CACHE) + UNSET (OPENSSL_CRYPTO_LIBRARY CACHE ) + UNSET (OPENSSL_SSL_LIBRARY CACHE ) + UNSET (OPENSSL_FOUND CACHE ) + +ENDMACRO() + +# Allows to define Hive project specific executable target. +# By default, this target depends on Boost libraries defined in ADD_TARGET_BOOST_LIBRARIES. +# In very rare cases when this is not needed, linker will skip such libraries... + +MACRO( ADD_HIVE_EXECUTABLE) + SET(multiValueArgs SOURCES LIBRARIES) + SET(OPTIONS "") + SET(oneValueArgs NAME ) + + CMAKE_PARSE_ARGUMENTS( HIVE_EXE "${OPTIONS}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + + ADD_EXECUTABLE( ${HIVE_EXE_NAME} ${HIVE_EXE_SOURCES} ) + + FIND_PACKAGE( Gperftools QUIET ) + IF( GPERFTOOLS_FOUND ) + MESSAGE( STATUS "Found gperftools; compiling ${HIVE_EXE_NAME} with TCMalloc") + LIST( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) + endif() + + TARGET_LINK_LIBRARIES( ${HIVE_EXE_NAME} PUBLIC + "-static-libstdc++ -static-libgcc" + + ${HIVE_EXE_LIBRARIES} + +# ${CMAKE_DL_LIBS} +# ${PLATFORM_SPECIFIC_LIBS} + ) + + set(_libs ) + + GET_TARGET_PROPERTY(_libs ${HIVE_EXE_NAME} LINK_LIBRARIES) + + MESSAGE ( STATUS "Target libraries before adding boost: ${_libs} " ) + + + + #ADD_TARGET_BOOST_LIBRARIES( ${HIVE_EXE_NAME} ) + + + + set(_libs ) + + GET_TARGET_PROPERTY(_libs ${HIVE_EXE_NAME} LINK_LIBRARIES) + + MESSAGE ( STATUS "Target libraries after adding boost: ${_libs} " ) + + # needed to correctly print crash stacktrace + SET_TARGET_PROPERTIES( ${HIVE_EXE_NAME} PROPERTIES ENABLE_EXPORTS true) + + IF( CLANG_TIDY_EXE ) + SET_TARGET_PROPERTIES( ${HIVE_EXE_NAME} PROPERTIES CXX_CLANG_TIDY "${DO_CLANG_TIDY}" ) + ENDIF() + + INSTALL( TARGETS ${HIVE_EXE_NAME} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) + +ENDMACRO() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 745f130..2b3614a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -53,6 +53,9 @@ target_link_libraries( ecdsa_canon_test fc ) add_executable( sha_test sha_test.cpp ) target_link_libraries( sha_test fc ) +add_executable( variant_test all_tests.cpp variant_test.cpp ) +target_link_libraries( variant_test fc ) + add_executable( all_tests all_tests.cpp compress/compress.cpp crypto/aes_test.cpp diff --git a/tests/variant_test.cpp b/tests/variant_test.cpp new file mode 100644 index 0000000..343fc32 --- /dev/null +++ b/tests/variant_test.cpp @@ -0,0 +1,671 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// Used later in `reflected` tests +namespace variant_tests +{ + +struct A +{ + int a = 0; +}; + +struct B +{ + A a; + int b = 0; +}; + +struct C +{ + B b; +}; + +} + +// This should auto-generate from_variant and to_variant functions +FC_REFLECT( variant_tests::A, (a) ) +FC_REFLECT( variant_tests::B, (a)(b) ) + +// Custom functions for C struct +namespace fc { + +void from_variant( const variant& var, variant_tests::C& c ) +{ from_variant( var, c.b ); } + +void to_variant( const variant_tests::C& c, variant& var ) +{ to_variant( c.b, var ); } + +} + +BOOST_AUTO_TEST_SUITE( variant_tests ) + +// Eliminates code redundancy wrapping the actual tests in error handling and logging +#define FC_AUTO_TEST_CASE( name, ... ) \ + BOOST_AUTO_TEST_CASE( name ) \ + { \ + try \ + { \ + BOOST_TEST_MESSAGE( "--- Testing: " #name ); \ + __VA_ARGS__ \ + } \ + FC_LOG_AND_RETHROW() \ + } + +// Pre-defined types + +FC_AUTO_TEST_CASE( as_int64, + int64_t initial = -1; + + fc::variant v = initial; + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::int64_type ); + + BOOST_REQUIRE_EQUAL( v.as_int64(), initial ); + + BOOST_REQUIRE( v.is_int64() ); + BOOST_REQUIRE( v.is_numeric() ); + BOOST_REQUIRE( v.is_integer() ); +) + +FC_AUTO_TEST_CASE( as_uint64, + uint64_t initial = -1; + + fc::variant v = initial; + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::uint64_type ); + + BOOST_REQUIRE_EQUAL( v.as_uint64(), initial ); + + BOOST_REQUIRE( v.is_uint64() ); + BOOST_REQUIRE( v.is_numeric() ); + BOOST_REQUIRE( v.is_integer() ); +) + +FC_AUTO_TEST_CASE( as_bool, + fc::variant v = true; + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::bool_type ); + BOOST_REQUIRE_EQUAL( v.as_bool(), true ); + + // Make sure that true is not a false-positive result + v = false; + BOOST_REQUIRE_EQUAL( v.as_bool(), false ); + + BOOST_REQUIRE( v.is_bool() ); + BOOST_REQUIRE( v.is_numeric() ); + BOOST_REQUIRE( v.is_integer() ); +) + +FC_AUTO_TEST_CASE( as_double, + double initial = 3.141592; + + fc::variant v = initial; + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::double_type ); + + BOOST_REQUIRE_EQUAL( v.as_double(), initial ); + + BOOST_REQUIRE( v.is_double() ); + BOOST_REQUIRE( v.is_numeric() ); + BOOST_REQUIRE( !v.is_integer() ); +) + +FC_AUTO_TEST_CASE( as_blob, + fc::blob initial; + initial.data = { 'a', 'l', 'i', 'c', 'e' }; + + fc::variant v = initial; + fc::blob out = v.as_blob(); + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::blob_type ); + + BOOST_REQUIRE_EQUAL( out.data.size(), initial.data.size() ); + + for( size_t i = 0; i < out.data.size(); ++i ) + BOOST_REQUIRE_EQUAL( out.data.at(i), initial.data.at(i) ); + + BOOST_REQUIRE( v.is_blob() ); + BOOST_REQUIRE( !v.is_numeric() ); + BOOST_REQUIRE( !v.is_integer() ); +) + +FC_AUTO_TEST_CASE( as_string, + std::string initial = "alice"; + + fc::variant v = initial; + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::string_type ); + + BOOST_REQUIRE_EQUAL( v.as_string(), initial ); + + BOOST_REQUIRE( v.is_string() ); + BOOST_REQUIRE( !v.is_numeric() ); + BOOST_REQUIRE( !v.is_integer() ); +) + +FC_AUTO_TEST_CASE( null_type, + fc::variant v; + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::null_type ); + + v = 3.141592; + v.clear(); + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::null_type ); + + BOOST_REQUIRE( v.is_null() ); + BOOST_REQUIRE( !v.is_numeric() ); + BOOST_REQUIRE( !v.is_integer() ); +) + +FC_AUTO_TEST_CASE( mutable_variant_object, + fc::variant_object vo; + BOOST_REQUIRE_EQUAL( vo.size(), 0 ); + + fc::mutable_variant_object mvo; + vo = mvo( "a", 1 )( "b", true ).set( "c", 3.14 ); + BOOST_REQUIRE_EQUAL( mvo[ "a" ].get_type(), fc::variant::int64_type ); + BOOST_REQUIRE_EQUAL( mvo[ "b" ].get_type(), fc::variant::bool_type ); + BOOST_REQUIRE_EQUAL( mvo[ "c" ].get_type(), fc::variant::double_type ); + + BOOST_REQUIRE_EQUAL( vo.size(), 3 ); + + // Throws on const object as it cannot add another element to the entries storage + BOOST_REQUIRE_THROW( static_cast< const fc::mutable_variant_object >( mvo )[ "d" ], fc::key_not_found_exception ); + // But inserts variant to the storage if used on non-const object + BOOST_REQUIRE_NO_THROW( mvo[ "d" ] ); + + BOOST_REQUIRE_EQUAL( mvo.size(), 4 ); + + mvo.erase( "d" ); + BOOST_REQUIRE_EQUAL( mvo.size(), 3 ); + + BOOST_REQUIRE_EQUAL( mvo[ "a" ].as_int64(), vo[ "a" ].as_int64() ); + BOOST_REQUIRE_EQUAL( mvo[ "b" ].as_bool(), vo[ "b" ].as_bool() ); + BOOST_REQUIRE_EQUAL( mvo[ "c" ].as_double(), vo[ "c" ].as_double() ); + BOOST_REQUIRE_EQUAL( mvo[ "d" ].get_type(), fc::variant::null_type ); + + // Random value test + BOOST_REQUIRE_EQUAL( mvo[ "c" ].as_double(), 3.14 ); +) + +FC_AUTO_TEST_CASE( variant_object, + fc::variant_object vo; + BOOST_REQUIRE_EQUAL( vo.size(), 0 ); + + fc::mutable_variant_object mvo; + vo = mvo( "a", 1 )( "b", true ).set( "c", 3.14 ); + + BOOST_REQUIRE_EQUAL( vo.size(), 3 ); + + BOOST_REQUIRE( vo.contains( "c" ) ); + + BOOST_REQUIRE_EQUAL( vo[ "a" ].get_type(), fc::variant::int64_type ); + BOOST_REQUIRE_EQUAL( vo[ "b" ].get_type(), fc::variant::bool_type ); + BOOST_REQUIRE_EQUAL( vo[ "c" ].get_type(), fc::variant::double_type ); + + BOOST_REQUIRE_THROW( vo[ "d" ], fc::key_not_found_exception ); + + // Random value test + BOOST_REQUIRE_EQUAL( vo[ "c" ].as_double(), 3.14 ); +) + +FC_AUTO_TEST_CASE( object_type, + fc::variant v = fc::variant_object{ "a", 3.14 }; + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::object_type ); + + const auto vo = v.get_object(); + BOOST_REQUIRE_EQUAL( vo.size(), 1 ); + + BOOST_REQUIRE_EQUAL( vo.find( "a" )->value().get_type(), fc::variant::double_type ); + + BOOST_REQUIRE_EQUAL( vo.find( "a" )->value().as_double(), vo.begin()->value().as_double() ); + + BOOST_REQUIRE_EQUAL( vo[ "a" ].get_type(), fc::variant::double_type ); + + // Test variant [] operator + BOOST_REQUIRE_EQUAL( v[ "a" ].get_type(), fc::variant::double_type ); + + BOOST_REQUIRE_EQUAL( v[ "a" ].as_double(), 3.14 ); + + BOOST_REQUIRE( v.is_object() ); + BOOST_REQUIRE( !v.is_numeric() ); + BOOST_REQUIRE( !v.is_integer() ); +) + +FC_AUTO_TEST_CASE( array_type, + fc::variants vs = { + { 1 }, + { true }, + { 3.14 } + }; + + fc::variant v = vs; + BOOST_REQUIRE_EQUAL( v.get_type(), fc::variant::array_type ); + BOOST_REQUIRE_EQUAL( v.size(), 3 ); + + const auto variants_out = v.get_array(); + BOOST_REQUIRE_EQUAL( variants_out.size(), 3 ); + + BOOST_REQUIRE_EQUAL( variants_out[ 0 ].get_type(), fc::variant::int64_type ); + BOOST_REQUIRE_EQUAL( variants_out[ 1 ].get_type(), fc::variant::bool_type ); + BOOST_REQUIRE_EQUAL( variants_out[ 2 ].get_type(), fc::variant::double_type ); + + // Test variant [] operator + BOOST_REQUIRE_EQUAL( v[ 1 ].get_type(), fc::variant::bool_type ); + + // Random value test + BOOST_REQUIRE_EQUAL( v[ 2 ].as_double(), 3.14 ); + + BOOST_REQUIRE( v.is_array() ); + BOOST_REQUIRE( !v.is_numeric() ); + BOOST_REQUIRE( !v.is_integer() ); +) + +// Type conversions + +namespace +{ + +template< typename TypeTested > +using variant_retrieve_member_fn_t = TypeTested (fc::variant::*)() const; + +#define FC_TEST_VARIANT_FUNCTION_INVOKE(object, member_ptr) ((object).*(member_ptr))() + +template< typename TypeTested > +void test_type_conversion( variant_retrieve_member_fn_t< TypeTested > variant_retrieve_fn ) +{ + fc::variant v; + TypeTested expected = std::numeric_limits< unsigned >::max(); + + const auto type_conversion_helper = [&]( auto&& input ) { + v = input; + BOOST_REQUIRE_EQUAL( (FC_TEST_VARIANT_FUNCTION_INVOKE( v, variant_retrieve_fn )), expected ); + }; + + type_conversion_helper( std::to_string( expected ) ); + type_conversion_helper( double( expected ) ); + type_conversion_helper( uint64_t( expected ) ); + type_conversion_helper( int64_t( expected ) ); + + expected = true; + type_conversion_helper( bool( expected ) ); + expected = false; + type_conversion_helper( bool( expected ) ); + + v.clear(); + BOOST_REQUIRE_EQUAL( (FC_TEST_VARIANT_FUNCTION_INVOKE( v, variant_retrieve_fn )), 0 ); + + // Objects: + v = fc::variant_object{}; + BOOST_REQUIRE_THROW( (FC_TEST_VARIANT_FUNCTION_INVOKE( v, variant_retrieve_fn )), fc::bad_cast_exception ); + v = fc::mutable_variant_object{}; + BOOST_REQUIRE_THROW( (FC_TEST_VARIANT_FUNCTION_INVOKE( v, variant_retrieve_fn )), fc::bad_cast_exception ); + + // Arrays: + v = fc::variants{}; + BOOST_REQUIRE_THROW( (FC_TEST_VARIANT_FUNCTION_INVOKE( v, variant_retrieve_fn )), fc::bad_cast_exception ); + + // Blob: + v = fc::blob{}; + BOOST_REQUIRE_THROW( (FC_TEST_VARIANT_FUNCTION_INVOKE( v, variant_retrieve_fn )), fc::bad_cast_exception ); +} + +} + +FC_AUTO_TEST_CASE( as_int64_conversions, + test_type_conversion< int64_t >( &fc::variant::as_int64 ); +) + +FC_AUTO_TEST_CASE( as_uint64_conversions, + test_type_conversion< uint64_t >( &fc::variant::as_uint64 ); +) + +FC_AUTO_TEST_CASE( as_double_conversions, + test_type_conversion< double >( &fc::variant::as_double ); +) + +FC_AUTO_TEST_CASE( as_bool_conversions, + bool expected = true; + fc::variant v; + + const auto type_conversion_helper = [&]( auto&& input ) { + v = input; + BOOST_REQUIRE_EQUAL( v.as_bool(), expected ); + }; + + type_conversion_helper( uint64_t( expected ) ); + type_conversion_helper( int64_t( expected ) ); + type_conversion_helper( double( expected ) ); + + type_conversion_helper( std::string{ "true" } ); + + v.clear(); + BOOST_REQUIRE_EQUAL( v.as_bool(), false ); + + expected = false; + type_conversion_helper( uint64_t( expected ) ); + type_conversion_helper( int64_t( expected ) ); + type_conversion_helper( double( expected ) ); + + type_conversion_helper( std::string{ "false" } ); + + BOOST_REQUIRE_THROW( type_conversion_helper( std::string{ "alice" } ), fc::bad_cast_exception ); + + // Objects: + v = fc::variant_object{}; + BOOST_REQUIRE_THROW( v.as_bool(), fc::bad_cast_exception ); + v = fc::mutable_variant_object{}; + BOOST_REQUIRE_THROW( v.as_bool(), fc::bad_cast_exception ); + + // Arrays: + v = fc::variants{}; + BOOST_REQUIRE_THROW( v.as_bool(), fc::bad_cast_exception ); + + // Blob: + v = fc::blob{}; + BOOST_REQUIRE_THROW( v.as_bool(), fc::bad_cast_exception ); +) + +FC_AUTO_TEST_CASE( as_string_conversions, + const uint64_t expected_integer = 123; + std::string expected = std::to_string( expected_integer ); + fc::variant v; + + const auto type_conversion_helper = [&]( auto&& input ) { + v = input; + BOOST_REQUIRE_EQUAL( v.as_string(), expected ); + }; + + type_conversion_helper( uint64_t( expected_integer ) ); + type_conversion_helper( int64_t( expected_integer ) ); + + expected = std::to_string( double( expected_integer ) ); + v = double( expected_integer ); + BOOST_REQUIRE_EQUAL( v.as_string().substr( 0, expected.size() ), expected ); + + expected = "true"; + type_conversion_helper( true ); + expected = "false"; + type_conversion_helper( false ); + + v.clear(); + BOOST_REQUIRE( v.as_string().empty() ); + + // Objects: + v = fc::variant_object{}; + BOOST_REQUIRE_THROW( v.as_string(), fc::bad_cast_exception ); + v = fc::mutable_variant_object{}; + BOOST_REQUIRE_THROW( v.as_string(), fc::bad_cast_exception ); + + // Arrays: + v = fc::variants{}; + BOOST_REQUIRE_THROW( v.as_string(), fc::bad_cast_exception ); + + // Blob: + fc::blob blob; + blob.data = { 'a', 'l', 'i', 'c', 'e' }; + v = blob; + BOOST_REQUIRE_EQUAL( v.as_string(), "YWxpY2U==" ); +) + +FC_AUTO_TEST_CASE( as_blob_conversions, + fc::blob blob; + blob.data = { 'a', 'l', 'i', 'c', 'e' }; + fc::variant v = "YWxpY2U=="; + + const auto compare_blob = []( const auto& lhs, const auto& rhs ) { + BOOST_REQUIRE_EQUAL( lhs.data.size(), rhs.data.size() ); + + for( size_t i = 0; i < lhs.data.size(); ++i ) + BOOST_REQUIRE_EQUAL( lhs.data.at(i), rhs.data.at(i) ); + }; + + compare_blob( v.as_blob(), blob ); + + v = std::string{ "alice", 5 }; + compare_blob( v.as_blob(), blob ); + + v.clear(); + BOOST_REQUIRE_EQUAL( v.as_blob().data.size(), 0 ); + + const auto create_vector = []( auto&& input ) { + return std::vector( (char*)&input, (char*)&input + sizeof(input) ); + }; + + const auto create_and_compare = [&]( auto&& input ) { + v = input; + blob.data = create_vector( input ); + compare_blob( v.as_blob(), blob ); + }; + + create_and_compare( int64_t( -1 ) ); + create_and_compare( uint64_t( -1 ) ); + create_and_compare( double( -1 ) ); + + // Objects: + v = fc::variant_object{}; + BOOST_REQUIRE_THROW( v.as_string(), fc::bad_cast_exception ); + v = fc::mutable_variant_object{}; + BOOST_REQUIRE_THROW( v.as_string(), fc::bad_cast_exception ); + + // Arrays: + v = fc::variants{}; + BOOST_REQUIRE_THROW( v.as_string(), fc::bad_cast_exception ); +) + +// Test variant construction from non-standard types +FC_AUTO_TEST_CASE( extended_variant_construction, + using fc::variant; + variant v; + + const auto test_type = [&]( auto&& value, variant::type_id type ) { + v = { value }; + BOOST_REQUIRE_EQUAL( v.get_type(), type ); + }; + + test_type( nullptr_t{}, variant::null_type ); + + char c_arr[6] = { 'a', 'l', 'i', 'c', 'e', '\0' }; + test_type( c_arr, variant::string_type ); + test_type( static_cast< const char* >( c_arr ), variant::string_type ); + + wchar_t wc_arr[6] = { 'a', 'l', 'i', 'c', 'e', '\0' }; + test_type( wc_arr, variant::string_type ); + test_type( static_cast< const wchar_t* >( wc_arr ), variant::string_type ); + + test_type( fc::string{ c_arr }, variant::string_type ); + + test_type( float{}, variant::double_type ); + + test_type( int8_t{}, variant::int64_type ); + test_type( int16_t{}, variant::int64_type ); + test_type( int32_t{}, variant::int64_type ); + + test_type( uint8_t{}, variant::uint64_type ); + test_type( uint16_t{}, variant::uint64_type ); + test_type( uint32_t{}, variant::uint64_type ); +) + +namespace +{ + +class variant_visitor : public fc::variant::visitor +{ +private: + fc::variant::type_id accept_type; + +public: + variant_visitor( fc::variant::type_id accept_type = fc::variant::null_type ); + + variant_visitor& operator[]( fc::variant::type_id accept_type ); + + virtual ~variant_visitor() = default; + + virtual void handle()const override; + virtual void handle( const int64_t& )const override; + virtual void handle( const uint64_t& )const override; + virtual void handle( const double& )const override; + virtual void handle( const bool& )const override; + virtual void handle( const std::string& )const override; + virtual void handle( const fc::variant_object& )const override; + virtual void handle( const fc::variants& )const override; +}; + +variant_visitor::variant_visitor( fc::variant::type_id accept_type ) + : accept_type( accept_type ) +{} + +variant_visitor& variant_visitor::operator[]( fc::variant::type_id accept_type ) +{ + this->accept_type = accept_type; + return *this; +} + +void variant_visitor::handle()const +{ BOOST_REQUIRE_EQUAL( accept_type, fc::variant::null_type ); } + +void variant_visitor::handle( const int64_t& )const +{ BOOST_REQUIRE_EQUAL( accept_type, fc::variant::int64_type ); } + +void variant_visitor::handle( const uint64_t& )const +{ BOOST_REQUIRE_EQUAL( accept_type, fc::variant::uint64_type ); } + +void variant_visitor::handle( const double& )const +{ BOOST_REQUIRE_EQUAL( accept_type, fc::variant::double_type ); } + +void variant_visitor::handle( const bool& )const +{ BOOST_REQUIRE_EQUAL( accept_type, fc::variant::bool_type ); } + +void variant_visitor::handle( const std::string& )const +{ BOOST_REQUIRE_EQUAL( accept_type, fc::variant::string_type ); } + +void variant_visitor::handle( const fc::variant_object& )const +{ BOOST_REQUIRE_EQUAL( accept_type, fc::variant::object_type ); } + +void variant_visitor::handle( const fc::variants& )const +{ BOOST_REQUIRE_EQUAL( accept_type, fc::variant::array_type ); } + +} + +FC_AUTO_TEST_CASE( visitor, + using fc::variant; + variant v; + + variant_visitor hv; + + const auto test_visitor = [&]( auto&& value, variant::type_id required ) { + v = value; + v.visit( hv[ required ] ); + }; + + test_visitor( nullptr_t{}, variant::null_type ); + test_visitor( std::string{}, variant::string_type ); + test_visitor( bool{}, variant::bool_type ); + test_visitor( int64_t{}, variant::int64_type ); + test_visitor( uint64_t{}, variant::uint64_type ); + test_visitor( double{}, variant::double_type ); + test_visitor( fc::variant_object{}, variant::object_type ); + test_visitor( fc::variants{}, variant::array_type ); +) + +// From variant, to variant + +namespace +{ + +template< typename T > +void test_from_variant( const T& expected ) +{ + fc::variant v; + T result = T{}; + v = expected; + fc::from_variant( v, result ); + BOOST_REQUIRE_EQUAL( expected, result ); +} + +template< typename T > +void test_to_variant( const T& expected ) +{ + fc::variant v; + fc::to_variant( expected, v ); + T result = T{}; + fc::from_variant( v, result ); + BOOST_REQUIRE_EQUAL( expected, result ); +} + +} + +FC_AUTO_TEST_CASE( from_variant_base, + test_from_variant( true ); + + test_from_variant( uint8_t( -1 ) ); + test_from_variant( int8_t( -1 ) ); + test_from_variant( uint16_t( -1 ) ); + test_from_variant( int16_t( -1 ) ); + test_from_variant( uint32_t( -1 ) ); + test_from_variant( int32_t( -1 ) ); + test_from_variant( uint64_t( -1 ) ); + test_from_variant( int64_t( -1 ) ); + + test_from_variant( double( 3.14 ) ); + + test_from_variant( std::string{ "alice" } ); +) + +FC_AUTO_TEST_CASE( to_variant_base, + test_to_variant( true ); + + test_to_variant( uint8_t( -1 ) ); + test_to_variant( int8_t( -1 ) ); + test_to_variant( uint16_t( -1 ) ); + test_to_variant( int16_t( -1 ) ); + test_to_variant( uint32_t( -1 ) ); + test_to_variant( int32_t( -1 ) ); + test_to_variant( uint64_t( -1 ) ); + test_to_variant( int64_t( -1 ) ); + + test_to_variant( double( 3.14 ) ); + + test_to_variant( std::string{ "alice" } ); +) + +FC_AUTO_TEST_CASE( reflected, + C c = { { { 1 }, 2 } }; + + fc::variant v; + fc::to_variant( c, v ); + BOOST_REQUIRE( v.is_object() ); + + C c_out; + fc::from_variant( v, c_out ); + + BOOST_REQUIRE_EQUAL( c.b.a.a, c_out.b.a.a ); + BOOST_REQUIRE_EQUAL( c.b.b, c_out.b.b ); +) + +FC_AUTO_TEST_CASE( variant_negation, + BOOST_REQUIRE( !fc::variant{ false } ); + BOOST_REQUIRE( !fc::variant{ 0 } ); +) + +FC_AUTO_TEST_CASE( variant_compare_eq, + BOOST_REQUIRE( (fc::variant{ 1 } == fc::variant{ 1 }) ); + BOOST_REQUIRE( (fc::variant{ "alice" } == fc::variant{ "alice" }) ); + BOOST_REQUIRE( !(fc::variant{ 2 } == fc::variant{ 1 }) ); + BOOST_REQUIRE( !(fc::variant{ true } == fc::variant{ 1 }) ); + BOOST_REQUIRE( !(fc::variant{ "alice" } == fc::variant{ "bob" }) ); +) + +FC_AUTO_TEST_CASE( variant_compare_ne, + BOOST_REQUIRE( !(fc::variant{ 1 } != fc::variant{ 1 }) ); + BOOST_REQUIRE( !(fc::variant{ "alice" } != fc::variant{ "alice" }) ); + BOOST_REQUIRE( (fc::variant{ 2 } != fc::variant{ 1 }) ); + BOOST_REQUIRE( (fc::variant{ true } != fc::variant{ 1 }) ); + BOOST_REQUIRE( (fc::variant{ "alice" } != fc::variant{ "bob" }) ); +) + +BOOST_AUTO_TEST_SUITE_END() -- GitLab From e2640edfb435ec32d0e1c950af765ed4220f31ef Mon Sep 17 00:00:00 2001 From: Dan Notestein Date: Wed, 7 Jan 2026 18:00:33 -0500 Subject: [PATCH 2/2] Fix Emscripten build: use minimal Boost components for WASM --- CMakeLists.txt | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95e65e2..92e6273 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -485,16 +485,26 @@ TARGET_LINK_LIBRARIES( fc_core PUBLIC CommonBuildOptions ) # Define Boost components needed by FC SET( BOOST_COMPONENTS ) -LIST( APPEND BOOST_COMPONENTS - chrono - context - coroutine - date_time - filesystem - iostreams - system - thread - unit_test_framework ) +if(HIVE_BUILD_ON_MINIMAL_FC) + # Minimal build (used for WASM/Emscripten) needs fewer components + LIST( APPEND BOOST_COMPONENTS + chrono + date_time + filesystem + system ) +else() + # Full build needs all components + LIST( APPEND BOOST_COMPONENTS + chrono + context + coroutine + date_time + filesystem + iostreams + system + thread + unit_test_framework ) +endif() ADD_LIBRARY( STATIC_BOOST INTERFACE ) SET_TARGET_PROPERTIES( STATIC_BOOST PROPERTIES POSITION_INDEPENDENT_CODE OFF ) -- GitLab