From e09373998d81b58b2385f253d0627bfd56026a48 Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Wed, 21 Feb 2024 17:08:50 +0200 Subject: [PATCH 01/17] Basic error support for free functions Signed-off-by: Martynas Gurskas --- .../cpp/templates/cpp_scaffolding.cpp | 49 +++++++++++-------- .../src/bindings/cpp/templates/err_tmpl.cpp | 2 +- .../cpp/templates/scaffolding/err.cpp | 38 ++++++++++++++ .../cpp/templates/scaffolding/err.hpp | 9 ++++ .../cpp/templates/scaffolding/macros.cpp | 28 +++++++++++ .../arithmetic/lib_arithmetic.cpp | 8 +++ .../arithmetic/lib_arithmetic.hpp | 10 +++- .../scaffolding_tests/arithmetic/main.cpp | 7 ++- .../chronological/lib_chronological.hpp | 13 +++++ .../lib_custom_fixture_callbacks.hpp | 2 +- cpp-tests/tests/arithmetic/main.cpp | 3 +- 11 files changed, 140 insertions(+), 29 deletions(-) create mode 100644 bindgen/src/bindings/cpp/templates/scaffolding/err.cpp create mode 100644 bindgen/src/bindings/cpp/templates/scaffolding/err.hpp create mode 100644 bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp diff --git a/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp b/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp index 54af1fb..38a10d4 100644 --- a/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp +++ b/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp @@ -1,4 +1,4 @@ -{%- import "macros.cpp" as macros %} +{%- import "scaffolding/macros.cpp" as macros %} {% let namespace = ci.namespace() %} {% match config.namespace %} {% when Some with (ns) %} @@ -22,9 +22,14 @@ #include #include #include +#include using namespace {{ namespace }}; +constexpr int8_t CALL_STATUS_OK = 0; +constexpr int8_t CALL_STATUS_ERROR = 1; +constexpr int8_t CALL_STATUS_PANIC = 2; + struct ForeignBytes { int32_t len; uint8_t *data; @@ -137,6 +142,9 @@ struct RustStream: std::basic_iostream { {%- when Type::Enum { module_path, name } %} {%- let e = ci|get_enum_definition(name) %} {%- if ci.is_name_used_as_error(name) %} +{% for variant in e.variants() %} +{% include "scaffolding/err.hpp" %} +{% endfor %} {%- else %} {%- if e.is_flat() %} {% include "enum_conv.hpp" %} @@ -155,7 +163,7 @@ extern "C" { #endif UNIFFI_EXPORT RustBuffer {{ ci.ffi_rustbuffer_alloc().name() }}(int32_t size, RustCallStatus *out_status) { - out_status->code = 0; + out_status->code = CALL_STATUS_OK; RustBuffer buf = { .capacity = size, @@ -167,7 +175,7 @@ UNIFFI_EXPORT RustBuffer {{ ci.ffi_rustbuffer_alloc().name() }}(int32_t size, Ru } UNIFFI_EXPORT RustBuffer {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes bytes, RustCallStatus *out_status) { - out_status->code = 0; + out_status->code = CALL_STATUS_OK; RustBuffer buf = { .capacity = bytes.len, @@ -181,13 +189,13 @@ UNIFFI_EXPORT RustBuffer {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignByte } UNIFFI_EXPORT void {{ ci.ffi_rustbuffer_free().name() }}(RustBuffer buf, RustCallStatus *out_status) { - out_status->code = 0; + out_status->code = CALL_STATUS_OK; delete[] buf.data; } UNIFFI_EXPORT RustBuffer {{ ci.ffi_rustbuffer_reserve().name() }}(RustBuffer buffer, int32_t additional, RustCallStatus *out_status) { - out_status->code = 0; + out_status->code = CALL_STATUS_OK; RustBuffer buf = { .capacity = buffer.capacity + additional, @@ -208,12 +216,9 @@ UNIFFI_EXPORT {% endfor %} {%- if ffi_func.has_rust_call_status_arg() %}RustCallStatus *out_status{% endif -%} ) { - {%- if ffi_func.has_rust_call_status_arg() %} - out_status->code = 0; - {% endif -%} - - {% match func.return_type() %} - {% when Some with (return_type) %} + {%- call macros::fn_prologue(ci, func, ffi_func) %} + {%- match func.return_type() %} + {%- when Some with (return_type) %} auto ret = {{ namespace }}::{{ func.name() }}( {%- for arg in func.arguments() %} {{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} @@ -223,15 +228,16 @@ UNIFFI_EXPORT {{ namespace }}::{{ func.name() }}( {%- for arg in func.arguments() %} {{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} - {% endfor %}); - {% endmatch %} + {%- endfor %}); + {%- endmatch %} + {%- call macros::fn_epilogue(ci, func, ffi_func) %} } {% endfor %} {% for func in ci.callback_interface_definitions() %} {% let ffi_func = func.ffi_init_callback() %} UNIFFI_EXPORT void {{ ffi_func.name() }}(ForeignCallback callback_stub, RustCallStatus *out_status) { - out_status->code = 0; + out_status->code = CALL_STATUS_OK; {{ func|ffi_converter_name }}::fn_handle.store(reinterpret_cast(callback_stub)); } @@ -250,7 +256,7 @@ UNIFFI_EXPORT {%- if ffi_ctor.has_rust_call_status_arg() %}RustCallStatus *out_status{% endif -%} ) { {%- if ffi_ctor.has_rust_call_status_arg() %} - out_status->code = 0; + out_status->code = CALL_STATUS_OK; {% endif -%} std::shared_ptr<{{ obj.name() }}> obj = std::make_shared<{{ obj.name() }}>( @@ -272,7 +278,7 @@ UNIFFI_EXPORT {%- if ffi_dtor.has_rust_call_status_arg() %}RustCallStatus *out_status{% endif -%} ) { {%- if ffi_dtor.has_rust_call_status_arg() %} - out_status->code = 0; + out_status->code = CALL_STATUS_OK; {% endif -%} {{ obj.name() }}_map.erase((uint64_t)ptr); @@ -289,7 +295,7 @@ UNIFFI_EXPORT {%- if ffi_func.has_rust_call_status_arg() %}RustCallStatus *out_status{% endif -%} ) { {%- if ffi_func.has_rust_call_status_arg() %} - out_status->code = 0; + out_status->code = CALL_STATUS_OK; {% endif -%} auto obj = {{ obj.name() }}_map.at((uint64_t)ptr); @@ -343,7 +349,7 @@ UNIFFI_EXPORT uint32_t {{ contract_fn.name() }}() { return {{ ci.uniffi_contract #endif RustBuffer rustbuffer_alloc(int32_t size) { - RustCallStatus status = { 0 }; + RustCallStatus status = { CALL_STATUS_OK }; return {{ ci.ffi_rustbuffer_alloc().name() }}( size, @@ -352,7 +358,7 @@ RustBuffer rustbuffer_alloc(int32_t size) { } RustBuffer rustbuffer_from_bytes(const ForeignBytes& bytes) { - RustCallStatus status = { 0 }; + RustCallStatus status = { CALL_STATUS_OK }; return {{ ci.ffi_rustbuffer_from_bytes().name() }}( bytes, @@ -361,7 +367,7 @@ RustBuffer rustbuffer_from_bytes(const ForeignBytes& bytes) { } void rustbuffer_free(RustBuffer& buf) { - RustCallStatus status = { 0 }; + RustCallStatus status = { CALL_STATUS_OK }; {{ ci.ffi_rustbuffer_free().name() }}( buf, @@ -417,6 +423,9 @@ void rustbuffer_free(RustBuffer& buf) { {%- when Type::Enum { module_path, name } %} {%- let e = ci|get_enum_definition(name) %} {%- if ci.is_name_used_as_error(name) %} +{% for variant in e.variants() %} +{% include "scaffolding/err.cpp" %} +{% endfor %} {%- else %} {%- if e.is_flat() %} {% include "enum_tmpl.cpp" %} diff --git a/bindgen/src/bindings/cpp/templates/err_tmpl.cpp b/bindgen/src/bindings/cpp/templates/err_tmpl.cpp index 296d723..3ab0619 100644 --- a/bindgen/src/bindings/cpp/templates/err_tmpl.cpp +++ b/bindgen/src/bindings/cpp/templates/err_tmpl.cpp @@ -83,4 +83,4 @@ int32_t {{ ffi_converter_name }}::allocation_size(const {{ class_name }} &val) { throw std::runtime_error("Unexpected error variant"); } {%- endif %} -} \ No newline at end of file +} diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp new file mode 100644 index 0000000..9cc29ee --- /dev/null +++ b/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp @@ -0,0 +1,38 @@ +{%- let type_name = e|type_name %} +{%- let class_name = type_name|class_name %} +{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let namespace = type_name|to_lower_snake_case %} + +RustBuffer {{ ffi_converter_name }}{{ variant.name() }}::lower(const {{ namespace }}::{{ variant.name() }} &val) { + auto buf = rustbuffer_alloc(allocation_size(val)); + auto stream = RustStream(&buf); + + {{ ffi_converter_name }}{{ variant.name() }}::write(stream, val); + + return std::move(buf); +} + +void {{ ffi_converter_name }}{{ variant.name() }}::write(RustStream &stream, const {{ namespace }}::{{ variant.name() }} &val) { + stream << uint32_t({{ loop.index }}); + + {%- if e.is_flat() %} + {{ Type::String.borrow()|write_fn }}(stream, val.what()); + {%- else %} + {%- for field in variant.fields() %} + {{ field|write_fn }}(stream, val.{{ field.name()|var_name }}); + {%- endfor %} + {%- endif %} +} + +int32_t {{ ffi_converter_name }}{{ variant.name() }}::allocation_size(const {{ namespace }}::{{ variant.name() }} &val) { + auto size = sizeof(uint32_t); + {%- if e.is_flat() %} + size += {{ Type::String.borrow()|allocation_size_fn }}(val.what()); + {%- else %} + {%- for field in variant.fields() %} + size ++ {{ field|allocation_size_fn }}() + {%- endfor %} + {%- endif %} + + return size; +} diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp b/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp new file mode 100644 index 0000000..c6d390e --- /dev/null +++ b/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp @@ -0,0 +1,9 @@ +{%- let type_name = e|type_name %} +{%- let class_name = type_name|class_name %} +{%- let ffi_converter_name = typ|ffi_converter_name %} +{%- let namespace = type_name|to_lower_snake_case %} +struct {{ ffi_converter_name }}{{ variant.name() }} { + static RustBuffer lower(const {{ namespace }}::{{ variant.name() }} &); + static void write(RustStream &stream, const {{ namespace }}::{{ variant.name() }} &); + static int32_t allocation_size(const {{ namespace }}::{{ variant.name() }} &); +}; diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp new file mode 100644 index 0000000..bf5d002 --- /dev/null +++ b/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp @@ -0,0 +1,28 @@ +{% macro fn_prologue(ci, func, ffi_func) -%} +{%- if ffi_func.has_rust_call_status_arg() %} + out_status->code = CALL_STATUS_OK; +{%- endif %} + try { +{% endmacro %} + +{% macro fn_epilogue(ci, func, ffi_func) -%} +{%- if ffi_func.has_rust_call_status_arg() %} +{%- if func.throws() %} +{%- let err_type = func.throws_type().unwrap()|type_name %} +{%- let err_enum = ci.get_enum_definition(err_type).unwrap() %} +{%- for variant in err_enum.variants() %} +{%- let converter_name = err_enum|ffi_converter_name %} + } catch (const {{ err_type|to_lower_snake_case }}::{{ variant.name() }} &e) { + out_status->code = CALL_STATUS_ERROR; + out_status->error_buf = {{ converter_name }}{{ variant.name() }}::lower(e); +{%- endfor %} +{%- endif %} + } catch (const std::exception &e) { + out_status->code = CALL_STATUS_PANIC; + out_status->error_buf = {{ Type::String.borrow()|ffi_converter_name }}::lower(e.what()); + } catch (...) { + out_status->code = CALL_STATUS_PANIC; + } + return {}; +{%- endif %} +{% endmacro %} diff --git a/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.cpp b/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.cpp index 44bdf2d..ac65a44 100644 --- a/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.cpp +++ b/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.cpp @@ -5,10 +5,18 @@ uint64_t arithmetic::add(uint64_t a, uint64_t b) { } uint64_t arithmetic::sub(uint64_t a, uint64_t b) { + if (a < b) { + throw arithmetic_error::IntegerOverflow(); + } + return a - b; } uint64_t arithmetic::div(uint64_t a, uint64_t b) { + if (b == 0) { + throw std::runtime_error("division by zero"); + } + return a / b; } diff --git a/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp b/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp index 7155f23..4898173 100644 --- a/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp +++ b/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp @@ -1,10 +1,18 @@ #include +#include namespace { namespace arithmetic { + namespace arithmetic_error { + class IntegerOverflow: public std::runtime_error { + public: + IntegerOverflow(): std::runtime_error("integer overflow") { } + }; + }; + uint64_t add(uint64_t a, uint64_t b); uint64_t sub(uint64_t a, uint64_t b); uint64_t div(uint64_t a, uint64_t b); int8_t equal(uint64_t a, uint64_t b); } -} \ No newline at end of file +} diff --git a/cpp-tests/scaffolding_tests/arithmetic/main.cpp b/cpp-tests/scaffolding_tests/arithmetic/main.cpp index 3746e74..f89aa39 100644 --- a/cpp-tests/scaffolding_tests/arithmetic/main.cpp +++ b/cpp-tests/scaffolding_tests/arithmetic/main.cpp @@ -9,18 +9,17 @@ int main() { ASSERT_EQ(2ul, arithmetic::sub(4, 2)); ASSERT_EQ(4ul, arithmetic::sub(8, 4)); - // EXPECT_EXCEPTION(arithmetic::sub(2, 4), arithmetic::arithmetic_error::IntegerOverflow); + EXPECT_EXCEPTION(arithmetic::sub(2, 4), arithmetic::arithmetic_error::IntegerOverflow); ASSERT_EQ(4ul, arithmetic::div(8, 2)); - // EXPECT_EXCEPTION(arithmetic::div(8, 0), std::runtime_error); + EXPECT_EXCEPTION(arithmetic::div(8, 0), std::runtime_error); ASSERT_TRUE(arithmetic::equal(2, 2)); ASSERT_TRUE(arithmetic::equal(4, 4)); ASSERT_FALSE(arithmetic::equal(2, 4)); ASSERT_FALSE(arithmetic::equal(4, 8)); - return 0; -} \ No newline at end of file +} diff --git a/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp b/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp index f7f029f..9b83874 100644 --- a/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp +++ b/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp @@ -1,12 +1,25 @@ #include #include #include +#include namespace { namespace chronological { typedef std::chrono::time_point timestamp; typedef std::chrono::duration duration; + namespace chronological_error { + class TimeOverflow: public std::runtime_error { + public: + TimeOverflow(): std::runtime_error("time overflow") { } + }; + + class TimeDiffError: public std::runtime_error { + public: + TimeDiffError(): std::runtime_error("time diff error") { } + }; + }; + timestamp return_timestamp(timestamp a); duration return_duration(duration a); diff --git a/cpp-tests/scaffolding_tests/custom_fixture_callbacks/lib_custom_fixture_callbacks.hpp b/cpp-tests/scaffolding_tests/custom_fixture_callbacks/lib_custom_fixture_callbacks.hpp index e62e1c7..d47dfe2 100644 --- a/cpp-tests/scaffolding_tests/custom_fixture_callbacks/lib_custom_fixture_callbacks.hpp +++ b/cpp-tests/scaffolding_tests/custom_fixture_callbacks/lib_custom_fixture_callbacks.hpp @@ -10,7 +10,7 @@ namespace { typedef std::optional>> ComplexType; enum class Enumeration { - A = 1, + A, B, C, UNKNOWN, diff --git a/cpp-tests/tests/arithmetic/main.cpp b/cpp-tests/tests/arithmetic/main.cpp index ffaaefc..f89aa39 100644 --- a/cpp-tests/tests/arithmetic/main.cpp +++ b/cpp-tests/tests/arithmetic/main.cpp @@ -20,7 +20,6 @@ int main() { ASSERT_FALSE(arithmetic::equal(2, 4)); ASSERT_FALSE(arithmetic::equal(4, 8)); - return 0; -} \ No newline at end of file +} From e28f62ca735d844891be4462f1b78e3cd5b70606 Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Fri, 23 Feb 2024 11:49:32 +0200 Subject: [PATCH 02/17] Don't add a return statement in function prologue macro if the function does not have a return value Signed-off-by: Martynas Gurskas --- bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp index bf5d002..67dbfef 100644 --- a/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp +++ b/bindgen/src/bindings/cpp/templates/scaffolding/macros.cpp @@ -23,6 +23,10 @@ } catch (...) { out_status->code = CALL_STATUS_PANIC; } +{% match func.return_type() %} +{% when Some with (return_type) %} return {}; +{% else %} +{% endmatch %} {%- endif %} {% endmacro %} From c8d573ec2d3cc6396164b55318360cba2a98ba5c Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Fri, 23 Feb 2024 11:54:40 +0200 Subject: [PATCH 03/17] Clean up scaffolding lib errors Signed-off-by: Martynas Gurskas --- cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp | 2 +- .../scaffolding_tests/chronological/lib_chronological.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp b/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp index 4898173..7c6abac 100644 --- a/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp +++ b/cpp-tests/scaffolding_tests/arithmetic/lib_arithmetic.hpp @@ -6,7 +6,7 @@ namespace { namespace arithmetic_error { class IntegerOverflow: public std::runtime_error { public: - IntegerOverflow(): std::runtime_error("integer overflow") { } + IntegerOverflow(): std::runtime_error("Integer overflow") { } }; }; diff --git a/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp b/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp index 9b83874..4bb8ea5 100644 --- a/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp +++ b/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp @@ -11,12 +11,12 @@ namespace { namespace chronological_error { class TimeOverflow: public std::runtime_error { public: - TimeOverflow(): std::runtime_error("time overflow") { } + TimeOverflow(): std::runtime_error("Time overflow") { } }; class TimeDiffError: public std::runtime_error { public: - TimeDiffError(): std::runtime_error("time diff error") { } + TimeDiffError(): std::runtime_error("Time Diff error") { } }; }; From 32e0032e7f0f4d160294819ae4d4e4517a0502bb Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Fri, 23 Feb 2024 11:56:22 +0200 Subject: [PATCH 04/17] Add scaffolding error converter read function, implement error propogation in callbacks Signed-off-by: Martynas Gurskas --- .../cpp/templates/cpp_scaffolding.cpp | 2 + .../cpp/templates/scaffolding/callback.cpp | 58 +++++++++++++++---- .../cpp/templates/scaffolding/err.cpp | 25 ++++++++ .../cpp/templates/scaffolding/err.hpp | 2 + 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp b/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp index 38a10d4..76a0255 100644 --- a/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp +++ b/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp @@ -300,6 +300,7 @@ UNIFFI_EXPORT auto obj = {{ obj.name() }}_map.at((uint64_t)ptr); + {%- call macros::fn_prologue(ci, func, ffi_func) %} {% match func.return_type() %} {% when Some with (return_type) %} auto ret = obj->{{ func.name() }}( @@ -313,6 +314,7 @@ UNIFFI_EXPORT {{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} {% endfor %}); {% endmatch %} + {%- call macros::fn_epilogue(ci, func, ffi_func) %} } {% endfor %} {% endfor %} diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/callback.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/callback.cpp index 228ea4a..b1b1bc7 100644 --- a/bindgen/src/bindings/cpp/templates/scaffolding/callback.cpp +++ b/bindgen/src/bindings/cpp/templates/scaffolding/callback.cpp @@ -43,19 +43,57 @@ class {{ iface.name() }}Proxy: public {{ iface.name() }} { {% endfor %} auto ret = callback_stub(this->handle, {{ loop.index }}, in_buf.data, size, &out_buf); - rustbuffer_free(in_buf); - {% match m.return_type() %} - {% when Some with (return_type) %} - RustStream out_stream(&out_buf); - auto result = {{ return_type|read_fn }}(out_stream); - rustbuffer_free(out_buf); + if (ret == CALL_STATUS_OK) { + {% match m.return_type() %} + {% when Some with (return_type) %} + RustStream out_stream(&out_buf); + auto result = {{ return_type|read_fn }}(out_stream); + rustbuffer_free(out_buf); + + return result; + {% else %} + rustbuffer_free(out_buf); + {% endmatch %} + } + else if (ret == CALL_STATUS_ERROR) { + RustStream out_stream(&out_buf); + uint32_t v; + out_stream >> v; + {%- if m.throws() %} + switch (v) { + {%- let err_type = m.throws_type().unwrap()|type_name %} + {%- let err_enum = ci.get_enum_definition(err_type).unwrap() %} + {%- for variant in err_enum.variants() %} + {%- let converter_name = err_enum|ffi_converter_name %} + case {{ loop.index }}: + { + auto result = {{ converter_name }}{{ variant.name() }}::read(out_stream, v); + rustbuffer_free(out_buf); + + throw result; + } + {%- endfor %} + default: + rustbuffer_free(out_buf); + throw std::runtime_error("Unexpected error variant"); + } + {%- endif %} + rustbuffer_free(out_buf); + throw std::runtime_error("Callback reuturned unexpected error code"); + } + else if (ret == CALL_STATUS_PANIC) { + RustStream out_stream(&out_buf); + auto result = FfiConverterString::read(out_stream); + rustbuffer_free(out_buf); - return result; - {% else %} - rustbuffer_free(out_buf); - {% endmatch %} + throw std::runtime_error(result); + } + else { + rustbuffer_free(out_buf); + throw std::runtime_error("Unknown error code"); + } } {% endfor %} private: diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp index 9cc29ee..ddbfcef 100644 --- a/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp +++ b/bindgen/src/bindings/cpp/templates/scaffolding/err.cpp @@ -3,6 +3,15 @@ {%- let ffi_converter_name = typ|ffi_converter_name %} {%- let namespace = type_name|to_lower_snake_case %} +{{ namespace }}::{{ variant.name() }} {{ ffi_converter_name }}{{ variant.name() }}::lift(RustBuffer buf, int32_t v) { + auto stream = RustStream(&buf); + auto ret = {{ ffi_converter_name }}{{ variant.name() }}::read(stream, v); + + rustbuffer_free(buf); + + return ret; +} + RustBuffer {{ ffi_converter_name }}{{ variant.name() }}::lower(const {{ namespace }}::{{ variant.name() }} &val) { auto buf = rustbuffer_alloc(allocation_size(val)); auto stream = RustStream(&buf); @@ -12,6 +21,22 @@ RustBuffer {{ ffi_converter_name }}{{ variant.name() }}::lower(const {{ namespac return std::move(buf); } +{{ namespace }}::{{ variant.name() }} {{ ffi_converter_name }}{{ variant.name() }}::read(RustStream &stream, int32_t v) { + if (v != {{ loop.index }}) { + throw std::runtime_error("Unexpected error variant"); + } + + {%- if e.is_flat() %} + return {{ namespace }}::{{ variant.name() }}(); + {%- else %} + {{ namespace }}::{{ variant.name() }} var; + {%- for field in variant.fields() %} + var.{{ field.name()|var_name }} = {{ field|read_fn }}(stream); + {%- endfor %} + return var; + {%- endif %} +} + void {{ ffi_converter_name }}{{ variant.name() }}::write(RustStream &stream, const {{ namespace }}::{{ variant.name() }} &val) { stream << uint32_t({{ loop.index }}); diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp b/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp index c6d390e..46efb2f 100644 --- a/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp +++ b/bindgen/src/bindings/cpp/templates/scaffolding/err.hpp @@ -3,7 +3,9 @@ {%- let ffi_converter_name = typ|ffi_converter_name %} {%- let namespace = type_name|to_lower_snake_case %} struct {{ ffi_converter_name }}{{ variant.name() }} { + static {{ namespace }}::{{ variant.name() }} lift(RustBuffer buf, int32_t v); static RustBuffer lower(const {{ namespace }}::{{ variant.name() }} &); + static {{ namespace }}::{{ variant.name() }} read(RustStream &stream, int32_t v); static void write(RustStream &stream, const {{ namespace }}::{{ variant.name() }} &); static int32_t allocation_size(const {{ namespace }}::{{ variant.name() }} &); }; From 6d13829b26a741ce6e17eb7d9a1a9fc15bb20b8b Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Fri, 23 Feb 2024 11:58:50 +0200 Subject: [PATCH 05/17] Add scaffolding callback test Signed-off-by: Martynas Gurskas --- cpp-tests/CMakeLists.txt | 2 +- .../callbacks/lib_callbacks.cpp | 3 ++ .../callbacks/lib_callbacks.hpp | 41 +++++++++++++++++++ .../scaffolding_tests/callbacks/main.cpp | 29 +++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 cpp-tests/scaffolding_tests/callbacks/lib_callbacks.cpp create mode 100644 cpp-tests/scaffolding_tests/callbacks/lib_callbacks.hpp create mode 100644 cpp-tests/scaffolding_tests/callbacks/main.cpp diff --git a/cpp-tests/CMakeLists.txt b/cpp-tests/CMakeLists.txt index 14476d2..4aa464b 100644 --- a/cpp-tests/CMakeLists.txt +++ b/cpp-tests/CMakeLists.txt @@ -76,7 +76,7 @@ test_case(trait_methods) test_case(custom_types_builtin) scaffolding_test_case(arithmetic) -# scaffolding_test_case(callbacks) +scaffolding_test_case(callbacks) scaffolding_test_case(custom_fixture_callbacks) scaffolding_test_case(chronological) # scaffolding_test_case(custom_types) diff --git a/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.cpp b/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.cpp new file mode 100644 index 0000000..46c7069 --- /dev/null +++ b/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.cpp @@ -0,0 +1,3 @@ +#include "lib_callbacks.hpp" + +#include diff --git a/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.hpp b/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.hpp new file mode 100644 index 0000000..2400e9f --- /dev/null +++ b/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.hpp @@ -0,0 +1,41 @@ +#include +#include +#include + +namespace { + namespace callbacks { + namespace telephone_error { + class Busy : public std::runtime_error { + public: + Busy() : std::runtime_error("I'm busy") {} + }; + + class InternalTelephoneError : public std::runtime_error { + public: + InternalTelephoneError() : std::runtime_error("Internal telephone error") {} + }; + } + + class CallAnswerer { + public: + virtual std::string answer() = 0; + + virtual ~CallAnswerer() = default; + }; + + class Telephone { + public: + Telephone() = default; + + std::string call(std::shared_ptr answerer) { + try { + return answerer->answer(); + } catch (telephone_error::Busy& e) { + throw e; + } catch (std::runtime_error& e) { + throw telephone_error::InternalTelephoneError(); + } + } + }; + } +} diff --git a/cpp-tests/scaffolding_tests/callbacks/main.cpp b/cpp-tests/scaffolding_tests/callbacks/main.cpp new file mode 100644 index 0000000..236fbe9 --- /dev/null +++ b/cpp-tests/scaffolding_tests/callbacks/main.cpp @@ -0,0 +1,29 @@ +#include + +#include + +class CallAnswererImpl: public callbacks::CallAnswerer { +public: + std::string mode; + + CallAnswererImpl(const std::string& mode) : mode(std::move(mode)) {} + + std::string answer() override { + if (mode.compare("normal") == 0) { + return "Hello"; + } else if (mode.compare("busy") == 0) { + throw callbacks::telephone_error::Busy("I'm busy"); + } else { + throw std::runtime_error("Unknown mode"); + } + } +}; + +int main() { + auto telephone = callbacks::Telephone::init(); + ASSERT_EQ(telephone->call(std::make_shared("normal")), "Hello"); + EXPECT_EXCEPTION(telephone->call(std::make_shared("busy")), callbacks::telephone_error::Busy); + EXPECT_EXCEPTION(telephone->call(std::make_shared("unknown")), callbacks::telephone_error::InternalTelephoneError); + + return 0; +} From a0be31eb939c88d8b9f3d29100252407f828b23a Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Fri, 23 Feb 2024 12:00:57 +0200 Subject: [PATCH 06/17] Build system: move memcheck test to a macro Signed-off-by: Martynas Gurskas --- cpp-tests/CMakeLists.txt | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/cpp-tests/CMakeLists.txt b/cpp-tests/CMakeLists.txt index 4aa464b..3041534 100644 --- a/cpp-tests/CMakeLists.txt +++ b/cpp-tests/CMakeLists.txt @@ -10,6 +10,16 @@ find_package(Threads REQUIRED) set(BINDINGS_BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../target/debug) set(BINDINGS_SRC_DIR ${BINDINGS_BUILD_DIR}/bindings) +macro(memcheck_test TEST_NAME) +add_test(NAME ${TEST_NAME}-test-memcheck + COMMAND valgrind + --error-exitcode=1 + --tool=memcheck + --leak-check=full + --errors-for-leak-kinds=definite + --show-leak-kinds=definite $) +endmacro(memcheck_test) + macro(test_case TEST_NAME) add_executable(${TEST_NAME}-test tests/${TEST_NAME}/main.cpp ${BINDINGS_SRC_DIR}/${TEST_NAME}.cpp) @@ -19,14 +29,7 @@ target_link_libraries(${TEST_NAME}-test uniffi_bindgen_cpp_fixtures Threads::Thr target_compile_definitions(${TEST_NAME}-test PRIVATE UNIFFI_BINDING_DIR="${BINDINGS_SRC_DIR}") add_test(NAME ${TEST_NAME}-test COMMAND ${TEST_NAME}-test) - -add_test(NAME ${TEST_NAME}-test-memcheck - COMMAND valgrind - --error-exitcode=1 - --tool=memcheck - --leak-check=full - --errors-for-leak-kinds=definite - --show-leak-kinds=definite $) +memcheck_test(${TEST_NAME}-test) add_dependencies(${TEST_NAME}-test bindings) @@ -44,14 +47,7 @@ target_include_directories(${TEST_NAME}-scaffolding-test PRIVATE ${BINDINGS_SRC_ target_link_libraries(${TEST_NAME}-scaffolding-test uniffi_${TEST_NAME} Threads::Threads) add_test(NAME ${TEST_NAME}-scaffolding-test COMMAND ${TEST_NAME}-scaffolding-test) - -add_test(NAME ${TEST_NAME}-scaffolding-test-memcheck - COMMAND valgrind - --error-exitcode=1 - --tool=memcheck - --leak-check=full - --errors-for-leak-kinds=definite - --show-leak-kinds=definite $) +memcheck_test(${TEST_NAME}-scaffolding-test) add_dependencies(uniffi_${TEST_NAME} bindings scaffolding custom_scaffolding uniffi_${TEST_NAME}) From 7af274b81470af52ecd96728a5aeaf687bb9d432 Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Mon, 26 Feb 2024 09:06:33 +0200 Subject: [PATCH 07/17] Update chronological test to work with scaffolding It's a bit of a pain do deal with pre unix epoch times in C++, so this was added as a hopefully temporary workaround Signed-off-by: Martynas Gurskas --- cpp-tests/CMakeLists.txt | 7 +- .../chronological/lib_chronological.cpp | 5 +- cpp-tests/tests/chronological/main.cpp | 85 +++++++++++++------ 3 files changed, 68 insertions(+), 29 deletions(-) diff --git a/cpp-tests/CMakeLists.txt b/cpp-tests/CMakeLists.txt index 3041534..5c72f02 100644 --- a/cpp-tests/CMakeLists.txt +++ b/cpp-tests/CMakeLists.txt @@ -20,6 +20,7 @@ add_test(NAME ${TEST_NAME}-test-memcheck --show-leak-kinds=definite $) endmacro(memcheck_test) +# Add a bindings test case macro(test_case TEST_NAME) add_executable(${TEST_NAME}-test tests/${TEST_NAME}/main.cpp ${BINDINGS_SRC_DIR}/${TEST_NAME}.cpp) @@ -37,14 +38,16 @@ list(APPEND BINDING_FILES ${BINDINGS_SRC_DIR}/${TEST_NAME}.cpp) endmacro(test_case) +# Add a scaffolding test case macro(scaffolding_test_case TEST_NAME) add_library(uniffi_${TEST_NAME} SHARED scaffolding_tests/${TEST_NAME}/lib_${TEST_NAME}.cpp) target_include_directories(uniffi_${TEST_NAME} PRIVATE scaffolding_tests/${TEST_NAME} ${BINDINGS_SRC_DIR}) -add_executable(${TEST_NAME}-scaffolding-test scaffolding_tests/${TEST_NAME}/main.cpp ${BINDINGS_SRC_DIR}/${TEST_NAME}.cpp) +add_executable(${TEST_NAME}-scaffolding-test tests/${TEST_NAME}/main.cpp ${BINDINGS_SRC_DIR}/${TEST_NAME}.cpp) target_include_directories(${TEST_NAME}-scaffolding-test PRIVATE ${BINDINGS_SRC_DIR} include) target_link_libraries(${TEST_NAME}-scaffolding-test uniffi_${TEST_NAME} Threads::Threads) +target_compile_definitions(${TEST_NAME}-scaffolding-test PRIVATE SCAFFOLDING_TEST=1) add_test(NAME ${TEST_NAME}-scaffolding-test COMMAND ${TEST_NAME}-scaffolding-test) memcheck_test(${TEST_NAME}-scaffolding-test) @@ -73,7 +76,7 @@ test_case(custom_types_builtin) scaffolding_test_case(arithmetic) scaffolding_test_case(callbacks) -scaffolding_test_case(custom_fixture_callbacks) +# scaffolding_test_case(custom_fixture_callbacks) scaffolding_test_case(chronological) # scaffolding_test_case(custom_types) scaffolding_test_case(geometry) diff --git a/cpp-tests/scaffolding_tests/chronological/lib_chronological.cpp b/cpp-tests/scaffolding_tests/chronological/lib_chronological.cpp index 81e6b6c..489ed7e 100644 --- a/cpp-tests/scaffolding_tests/chronological/lib_chronological.cpp +++ b/cpp-tests/scaffolding_tests/chronological/lib_chronological.cpp @@ -16,7 +16,7 @@ std::string chronological::to_string_timestamp(chronological::timestamp a) { std::time_t time = std::chrono::system_clock::to_time_t(a); std::tm tm = *std::gmtime(&time); std::stringstream ss; - ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S.000000000Z"); + ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S"); return ss.str(); } @@ -29,6 +29,9 @@ chronological::timestamp chronological::add(chronological::timestamp a, chronolo } chronological::duration chronological::diff(chronological::timestamp a, chronological::timestamp b) { + if (a < b) { + throw chronological_error::TimeDiffError(); + } return a - b; } diff --git a/cpp-tests/tests/chronological/main.cpp b/cpp-tests/tests/chronological/main.cpp index 1833ae6..d227809 100644 --- a/cpp-tests/tests/chronological/main.cpp +++ b/cpp-tests/tests/chronological/main.cpp @@ -15,7 +15,6 @@ std::chrono::time_point epo std::chrono::duration time_span_seconds(int seconds, int nanoseconds) { return std::chrono::nanoseconds(std::chrono::seconds(seconds) + std::chrono::nanoseconds(nanoseconds)); - } std::chrono::time_point time_from_string(const std::string& time) { @@ -26,6 +25,60 @@ std::chrono::time_point time_from_string(const std::s return std::chrono::system_clock::from_time_t(timegm(&tm)); } +void test_string_timestamps() { + { + auto time_str = "1969-12-12T00:00:00.000000000Z"; + auto time = time_from_string(time_str); + + ASSERT_EQ(time_str, chronological::to_string_timestamp(time)); + } + + { + // get_time doesn't support nanoseconds, so we have to add them manually + auto time_str = "1969-12-31T23:59:58.999999900Z"; + auto time = time_from_string(time_str) + 999999900ns; + + ASSERT_EQ(time_str, chronological::to_string_timestamp(time)); + } + + { + // get_time doesn't support nanoseconds, so we have to add them manually + auto time = time_from_string("1955-11-05T00:06:01.283000200Z") + 283000200ns; + auto time2 = time_from_string("1955-11-05T00:06:00.283000100Z") + 283000100ns; + + ASSERT_EQ( + time, + chronological::add(time2, time_span_seconds(1, 100)) + ); + } +} + +void test_scaffolding_string_timestamps() { + { + auto time_str = "1970-01-01T00:00:00"; + auto time = time_from_string(time_str); + + ASSERT_EQ(time_str, chronological::to_string_timestamp(time)); + } + + { + auto time_str = "1970-12-31T23:59:58"; + auto time = time_from_string(time_str); + + ASSERT_EQ(time_str, chronological::to_string_timestamp(time)); + } + + { + auto time = time_from_string("1979-11-05T00:06:01") + 283000200ns; + auto time2 = time_from_string("1979-11-05T00:06:00") + 283000100ns; + + ASSERT_EQ( + time, + chronological::add(time2, time_span_seconds(1, 100)) + ); + } +} + int main() { ASSERT_EQ( epoch_second(101, 110), @@ -57,31 +110,11 @@ int main() { chronological::return_duration(std::chrono::nanoseconds::max()) ); - { - auto time_str = "1969-12-12T00:00:00.000000000Z"; - auto time = time_from_string(time_str); - - ASSERT_EQ(time_str, chronological::to_string_timestamp(time)); - } - - { - // get_time doesn't support nanoseconds, so we have to add them manually - auto time_str = "1969-12-31T23:59:58.999999900Z"; - auto time = time_from_string(time_str) + 999999900ns; - - ASSERT_EQ(time_str, chronological::to_string_timestamp(time)); - } - - { - // get_time doesn't support nanoseconds, so we have to add them manually - auto time = time_from_string("1955-11-05T00:06:01.283000200Z") + 283000200ns; - auto time2 = time_from_string("1955-11-05T00:06:00.283000100Z") + 283000100ns; - - ASSERT_EQ( - time, - chronological::add(time2, time_span_seconds(1, 100)) - ); - } + #ifdef SCAFFOLDING_TEST + test_scaffolding_string_timestamps(); + #else + test_string_timestamps(); + #endif auto before = std::chrono::system_clock::now(); std::this_thread::sleep_for(1s); From 72bdb6b1ab8bba33798248ea9eff612bb5686348 Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Mon, 26 Feb 2024 09:17:06 +0200 Subject: [PATCH 08/17] Remove scaffolding test consumer implementations As at this point we can reuse the code from the original tests Signed-off-by: Martynas Gurskas --- .../scaffolding_tests/arithmetic/main.cpp | 25 ----- .../scaffolding_tests/callbacks/main.cpp | 29 ------ .../scaffolding_tests/chronological/main.cpp | 99 ------------------- cpp-tests/scaffolding_tests/geometry/main.cpp | 29 ------ cpp-tests/scaffolding_tests/sprites/main.cpp | 27 ----- 5 files changed, 209 deletions(-) delete mode 100644 cpp-tests/scaffolding_tests/arithmetic/main.cpp delete mode 100644 cpp-tests/scaffolding_tests/callbacks/main.cpp delete mode 100644 cpp-tests/scaffolding_tests/chronological/main.cpp delete mode 100644 cpp-tests/scaffolding_tests/geometry/main.cpp delete mode 100644 cpp-tests/scaffolding_tests/sprites/main.cpp diff --git a/cpp-tests/scaffolding_tests/arithmetic/main.cpp b/cpp-tests/scaffolding_tests/arithmetic/main.cpp deleted file mode 100644 index f89aa39..0000000 --- a/cpp-tests/scaffolding_tests/arithmetic/main.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include - -#include - -int main() { - ASSERT_EQ(arithmetic::add(2, 4), 6ul); - ASSERT_EQ(arithmetic::add(4, 8), 12ul); - - ASSERT_EQ(2ul, arithmetic::sub(4, 2)); - ASSERT_EQ(4ul, arithmetic::sub(8, 4)); - - EXPECT_EXCEPTION(arithmetic::sub(2, 4), arithmetic::arithmetic_error::IntegerOverflow); - - ASSERT_EQ(4ul, arithmetic::div(8, 2)); - - EXPECT_EXCEPTION(arithmetic::div(8, 0), std::runtime_error); - - ASSERT_TRUE(arithmetic::equal(2, 2)); - ASSERT_TRUE(arithmetic::equal(4, 4)); - - ASSERT_FALSE(arithmetic::equal(2, 4)); - ASSERT_FALSE(arithmetic::equal(4, 8)); - - return 0; -} diff --git a/cpp-tests/scaffolding_tests/callbacks/main.cpp b/cpp-tests/scaffolding_tests/callbacks/main.cpp deleted file mode 100644 index 236fbe9..0000000 --- a/cpp-tests/scaffolding_tests/callbacks/main.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include - -#include - -class CallAnswererImpl: public callbacks::CallAnswerer { -public: - std::string mode; - - CallAnswererImpl(const std::string& mode) : mode(std::move(mode)) {} - - std::string answer() override { - if (mode.compare("normal") == 0) { - return "Hello"; - } else if (mode.compare("busy") == 0) { - throw callbacks::telephone_error::Busy("I'm busy"); - } else { - throw std::runtime_error("Unknown mode"); - } - } -}; - -int main() { - auto telephone = callbacks::Telephone::init(); - ASSERT_EQ(telephone->call(std::make_shared("normal")), "Hello"); - EXPECT_EXCEPTION(telephone->call(std::make_shared("busy")), callbacks::telephone_error::Busy); - EXPECT_EXCEPTION(telephone->call(std::make_shared("unknown")), callbacks::telephone_error::InternalTelephoneError); - - return 0; -} diff --git a/cpp-tests/scaffolding_tests/chronological/main.cpp b/cpp-tests/scaffolding_tests/chronological/main.cpp deleted file mode 100644 index adc5b45..0000000 --- a/cpp-tests/scaffolding_tests/chronological/main.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -std::chrono::time_point epoch_second(int seconds, int nanoseconds) { - return std::chrono::time_point(std::chrono::seconds(seconds)) + std::chrono::nanoseconds(nanoseconds); -} - - -std::chrono::duration time_span_seconds(int seconds, int nanoseconds) { - return std::chrono::nanoseconds(std::chrono::seconds(seconds) + std::chrono::nanoseconds(nanoseconds)); - -} - -std::chrono::time_point time_from_string(const std::string& time) { - std::tm tm = {}; - auto ss = std::stringstream(time); - ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S"); - - return std::chrono::system_clock::from_time_t(timegm(&tm)); -} - -int main() { - ASSERT_EQ( - epoch_second(101, 110), - chronological::add(epoch_second(100, 100), time_span_seconds(1, 10)) - ); - - ASSERT_EQ( - time_span_seconds(1, 100), - chronological::diff(epoch_second(101, 200), epoch_second(100, 100)) - ); - - // EXPECT_EXCEPTION( - // chronological::diff(epoch_second(100, 0), epoch_second(101, 0)), - // chronological::ChronologicalError - // ); - - ASSERT_EQ( - std::chrono::time_point::min(), - chronological::return_timestamp(std::chrono::time_point::min()) - ); - - ASSERT_EQ( - std::chrono::time_point::max(), - chronological::return_timestamp(std::chrono::time_point::max()) - ); - - ASSERT_EQ( - std::chrono::nanoseconds::max(), - chronological::return_duration(std::chrono::nanoseconds::max()) - ); - - // { - // auto time_str = "1969-12-12T00:00:00.000000000Z"; - // auto time = time_from_string(time_str); - - // ASSERT_EQ(time_str, chronological::to_string_timestamp(time)); - // } - - // { - // // get_time doesn't support nanoseconds, so we have to add them manually - // auto time_str = "1969-12-31T23:59:58.999999900Z"; - // auto time = time_from_string(time_str) + 999999900ns; - - // ASSERT_EQ(time_str, chronological::to_string_timestamp(time)); - // } - - // { - // // get_time doesn't support nanoseconds, so we have to add them manually - // auto time = time_from_string("1955-11-05T00:06:01.283000200Z") + 283000200ns; - // auto time2 = time_from_string("1955-11-05T00:06:00.283000100Z") + 283000100ns; - - // ASSERT_EQ( - // time, - // chronological::add(time2, time_span_seconds(1, 100)) - // ); - // } - - auto before = std::chrono::system_clock::now(); - std::this_thread::sleep_for(1s); - auto after = chronological::now(); - std::this_thread::sleep_for(1s); - auto after2 = std::chrono::system_clock::now(); - ASSERT_EQ(-1, std::chrono::duration_cast(before - after).count()); - ASSERT_EQ(1, std::chrono::duration_cast(after2 - after).count()); - - ASSERT_TRUE(chronological::optional(std::chrono::time_point::max(), time_span_seconds(0, 0))); - ASSERT_FALSE(chronological::optional(std::nullopt, time_span_seconds(0, 0))); - ASSERT_FALSE(chronological::optional(std::chrono::time_point::min(), std::nullopt)); - - return 0; -} diff --git a/cpp-tests/scaffolding_tests/geometry/main.cpp b/cpp-tests/scaffolding_tests/geometry/main.cpp deleted file mode 100644 index 26570f4..0000000 --- a/cpp-tests/scaffolding_tests/geometry/main.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include - -#include - -int main() { - auto line1 = geometry::Line { - geometry::Point { 0.0, 0.0 }, - geometry::Point { 1.0, 2.0 } - }; - - auto line2 = geometry::Line { - geometry::Point { 1.0, 1.0 }, - geometry::Point { 2.0, 2.0 } - }; - - ASSERT_EQ(geometry::gradient(line1), 2.0); - ASSERT_EQ(geometry::gradient(line2), 1.0); - - auto point = geometry::Point { 0.0, 0.0 }; - auto intersection = geometry::intersection(line1, line2); - ASSERT_TRUE(intersection.has_value()); - auto intersection_value = intersection.value(); - ASSERT_EQ(intersection_value.coord_x, 0); - ASSERT_EQ(intersection_value.coord_y, 0); - - ASSERT_EQ(std::nullopt, geometry::intersection(line1, line1)); - - return 0; -} \ No newline at end of file diff --git a/cpp-tests/scaffolding_tests/sprites/main.cpp b/cpp-tests/scaffolding_tests/sprites/main.cpp deleted file mode 100644 index 20454ec..0000000 --- a/cpp-tests/scaffolding_tests/sprites/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#include - -int main() { - auto sprite = sprites::Sprite::init(std::nullopt); - ASSERT_EQ(sprite->get_position().x, 0.0); - ASSERT_EQ(sprite->get_position().y, 0.0); - - auto s = sprites::Sprite::init(sprites::Point { 1.0, 2.0 }); - ASSERT_EQ(s->get_position().x, 1.0); - ASSERT_EQ(s->get_position().y, 2.0); - - s->move_to(sprites::Point { 3.0, 4.0 }); - ASSERT_EQ(s->get_position().x, 3.0); - ASSERT_EQ(s->get_position().y, 4.0); - - s->move_by(sprites::Vector { -4.0, 2.0 }); - ASSERT_EQ(s->get_position().x, -1.0); - ASSERT_EQ(s->get_position().y, 6.0); - - auto rel = sprites::Sprite::new_relative_to(sprites::Point {0.0, 1.0}, sprites::Vector { 1.0, 1.5}); - ASSERT_EQ(rel->get_position().x, 1.0); - ASSERT_EQ(rel->get_position().y, 2.5); - - return 0; -} From 461ac923e6903e263ee05cd947cb5e8a339ca9eb Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Mon, 26 Feb 2024 09:24:26 +0200 Subject: [PATCH 09/17] Clean up test code Signed-off-by: Martynas Gurskas --- .../scaffolding_tests/callbacks/lib_callbacks.cpp | 10 ++++++++++ .../scaffolding_tests/callbacks/lib_callbacks.hpp | 10 +--------- .../chronological/lib_chronological.hpp | 10 ---------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.cpp b/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.cpp index 46c7069..dc0f8bb 100644 --- a/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.cpp +++ b/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.cpp @@ -1,3 +1,13 @@ #include "lib_callbacks.hpp" +std::string callbacks::Telephone::call(std::shared_ptr answerer) { + try { + return answerer->answer(); + } catch (telephone_error::Busy& e) { + throw e; + } catch (std::runtime_error& e) { + throw telephone_error::InternalTelephoneError(); + } +} + #include diff --git a/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.hpp b/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.hpp index 2400e9f..3e2cdc3 100644 --- a/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.hpp +++ b/cpp-tests/scaffolding_tests/callbacks/lib_callbacks.hpp @@ -27,15 +27,7 @@ namespace { public: Telephone() = default; - std::string call(std::shared_ptr answerer) { - try { - return answerer->answer(); - } catch (telephone_error::Busy& e) { - throw e; - } catch (std::runtime_error& e) { - throw telephone_error::InternalTelephoneError(); - } - } + std::string call(std::shared_ptr answerer); }; } } diff --git a/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp b/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp index 4bb8ea5..db30bf7 100644 --- a/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp +++ b/cpp-tests/scaffolding_tests/chronological/lib_chronological.hpp @@ -21,25 +21,15 @@ namespace { }; timestamp return_timestamp(timestamp a); - duration return_duration(duration a); - std::string to_string_timestamp(timestamp a); - timestamp get_pre_epoch_timestamp(); - timestamp add(timestamp a, duration b); - duration diff(timestamp a, timestamp b); - timestamp now(); - bool equal(timestamp a, timestamp b); - bool optional(std::optional a, std::optional b); - uint64_t get_seconds_before_unix_epoch(timestamp a); - timestamp set_seconds_before_unix_epoch(uint64_t seconds); } } From 5321ea34ff8a73dd79ec6fb4b7812732e32845d6 Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Mon, 26 Feb 2024 15:13:28 +0200 Subject: [PATCH 10/17] Add dedicated `ObjectMap` and dedicated object converters to scaffolding As reusing the `HandleMap` and default converters was not a feasible solution Signed-off-by: Martynas Gurskas --- .../cpp/templates/cpp_scaffolding.cpp | 7 ++-- .../src/bindings/cpp/templates/opt_tmpl.cpp | 2 +- .../cpp/templates/scaffolding/obj.cpp | 23 +++++++++++++ .../cpp/templates/scaffolding/obj.hpp | 8 +++++ .../cpp/templates/scaffolding/object_map.cpp | 34 +++++++++++++++++++ 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 bindgen/src/bindings/cpp/templates/scaffolding/obj.cpp create mode 100644 bindgen/src/bindings/cpp/templates/scaffolding/obj.hpp create mode 100644 bindgen/src/bindings/cpp/templates/scaffolding/object_map.cpp diff --git a/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp b/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp index 76a0255..da9ba6e 100644 --- a/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp +++ b/bindgen/src/bindings/cpp/templates/cpp_scaffolding.cpp @@ -97,7 +97,7 @@ struct RustStream: std::basic_iostream { RustStreamBuffer streambuf; }; -{%- include "handle_map.cpp" %} +{%- include "scaffolding/object_map.cpp" %} {%- for typ in ci.iter_types() %} {%- match typ %} @@ -151,7 +151,8 @@ struct RustStream: std::basic_iostream { {% endif %} {%- endif %} {%- when Type::Object { module_path, name, imp } %} -HandleMap<{{ typ|canonical_name }}> {{ name }}_map; +{% include "scaffolding/obj.hpp" %} +ObjectMap<{{ typ|canonical_name }}> {{ name }}_map; {%- when Type::CallbackInterface { module_path, name } %} {% include "scaffolding/callback.hpp" %} {% else %} @@ -263,7 +264,6 @@ UNIFFI_EXPORT {%- for arg in ctor.arguments() %} {{- arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %}, {% endif -%} {% endfor %}); - return (void*){{ obj.name() }}_map.insert(obj); } {% endfor %} @@ -434,6 +434,7 @@ void rustbuffer_free(RustBuffer& buf) { {%- endif %} {%- endif %} {%- when Type::Object { module_path, name, imp } %} +{% include "scaffolding/obj.cpp" %} {%- when Type::CallbackInterface { module_path, name } %} {% include "scaffolding/callback.cpp" %} {%- else %} diff --git a/bindgen/src/bindings/cpp/templates/opt_tmpl.cpp b/bindgen/src/bindings/cpp/templates/opt_tmpl.cpp index 0107f69..99a22ee 100644 --- a/bindgen/src/bindings/cpp/templates/opt_tmpl.cpp +++ b/bindgen/src/bindings/cpp/templates/opt_tmpl.cpp @@ -60,4 +60,4 @@ int32_t {{ ffi_converter_name }}::allocation_size(const {{ type_name }} &val) { } return ret; -} \ No newline at end of file +} diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/obj.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/obj.cpp new file mode 100644 index 0000000..9f078c5 --- /dev/null +++ b/bindgen/src/bindings/cpp/templates/scaffolding/obj.cpp @@ -0,0 +1,23 @@ +#include +{{ type_name }} {{ ffi_converter_name }}::lift(void *ptr) { + return {{ name }}_map.at((uint64_t)ptr); +} + +void *{{ ffi_converter_name }}::lower(const {{ type_name }} &obj) { + return (void *)obj.get(); +} + +{{ type_name }} {{ ffi_converter_name }}::read(RustStream &stream) { + std::uintptr_t ptr; + stream >> ptr; + + return {{ name }}_map.at(ptr); +} + +void {{ ffi_converter_name }}::write(RustStream &stream, const {{ type_name }} &obj) { + stream << (uint64_t)obj.get(); +} + +int32_t {{ ffi_converter_name }}::allocation_size(const {{ type_name }} &) { + return 8; +} diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/obj.hpp b/bindgen/src/bindings/cpp/templates/scaffolding/obj.hpp new file mode 100644 index 0000000..6b5626d --- /dev/null +++ b/bindgen/src/bindings/cpp/templates/scaffolding/obj.hpp @@ -0,0 +1,8 @@ +{%- let type_name = typ|type_name %} +struct {{ typ|ffi_converter_name }} { + static {{ type_name }} lift(void *); + static void *lower(const {{ type_name }} &); + static {{ type_name }} read(RustStream &); + static void write(RustStream &, const {{ type_name }} &); + static int32_t allocation_size(const {{ type_name }} &); +}; diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/object_map.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/object_map.cpp new file mode 100644 index 0000000..0c93ebc --- /dev/null +++ b/bindgen/src/bindings/cpp/templates/scaffolding/object_map.cpp @@ -0,0 +1,34 @@ +template struct ObjectMap { + ObjectMap() = default; + + std::shared_ptr at(uint64_t ptr) { + std::lock_guard guard(this->mutex); + + return this->map.at(ptr); + } + + uint64_t insert(std::shared_ptr impl) { + std::lock_guard guard(this->mutex); + + this->map.insert({ (uint64_t)impl.get(), impl }); + + return (uint64_t)impl.get(); + } + + void erase(uint64_t ptr) { + std::lock_guard guard(this->mutex); + + if (this->map.at(ptr).use_count() == 1) { + this->map.erase(ptr); + } + } + private: + ObjectMap(const ObjectMap &) = delete; + ObjectMap(ObjectMap &&) = delete; + + ObjectMap &operator=(const ObjectMap &) = delete; + ObjectMap &operator=(ObjectMap &&) = delete; + + std::mutex mutex; + std::map> map; +}; From 5a30293825547a21e88eeedbe3dead4c929677c8 Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Mon, 26 Feb 2024 15:13:48 +0200 Subject: [PATCH 11/17] Add todolist scaffolding test Signed-off-by: Martynas Gurskas --- cpp-tests/CMakeLists.txt | 2 +- .../todolist/lib_todolist.cpp | 102 ++++++++++++++++++ .../todolist/lib_todolist.hpp | 79 ++++++++++++++ cpp-tests/tests/todolist/main.cpp | 2 +- 4 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 cpp-tests/scaffolding_tests/todolist/lib_todolist.cpp create mode 100644 cpp-tests/scaffolding_tests/todolist/lib_todolist.hpp diff --git a/cpp-tests/CMakeLists.txt b/cpp-tests/CMakeLists.txt index 5c72f02..5a931e4 100644 --- a/cpp-tests/CMakeLists.txt +++ b/cpp-tests/CMakeLists.txt @@ -82,7 +82,7 @@ scaffolding_test_case(chronological) scaffolding_test_case(geometry) # scaffolding_test_case(rondpoint) scaffolding_test_case(sprites) -# scaffolding_test_case(todolist) +scaffolding_test_case(todolist) # scaffolding_test_case(traits) # scaffolding_test_case(coverall) # scaffolding_test_case(trait_methods) diff --git a/cpp-tests/scaffolding_tests/todolist/lib_todolist.cpp b/cpp-tests/scaffolding_tests/todolist/lib_todolist.cpp new file mode 100644 index 0000000..925f0ff --- /dev/null +++ b/cpp-tests/scaffolding_tests/todolist/lib_todolist.cpp @@ -0,0 +1,102 @@ +#include "lib_todolist.hpp" + +#include + +std::shared_ptr todolist::get_default_list() { + std::lock_guard lock(todolist::default_list_mutex); + return todolist::default_list; +} + +void todolist::set_default_list(std::shared_ptr list) { + std::lock_guard lock(todolist::default_list_mutex); + todolist::default_list = list; +} + +todolist::TodoEntry todolist::create_entry_with(const std::string &todo) { + if (todo.empty()) { + throw todolist::todo_error::EmptyString("Cannot add empty string as entry"); + } + + return todolist::TodoEntry(todo); +} + +void todolist::TodoList::add_item(const std::string &todo) { + if (todo.empty()) { + throw todolist::todo_error::EmptyString("Cannot add empty string as item"); + } + + std::lock_guard lock(this->items_mutex); + if (std::find(this->items.begin(), this->items.end(), todo) != this->items.end()) { + throw todolist::todo_error::DuplicateTodo("Item already exists"); + } + + this->items.push_back(todo); +} + +void todolist::TodoList::add_entry(const todolist::TodoEntry &entry) { + this->add_item(entry.text); +} + +std::vector todolist::TodoList::get_entries() { + std::lock_guard lock(this->items_mutex); + std::vector entries; + for (const auto &item : this->items) { + entries.push_back(todolist::TodoEntry(item)); + } + return entries; +} + +std::vector todolist::TodoList::get_items() { + std::lock_guard lock(this->items_mutex); + return this->items; +} + +void todolist::TodoList::add_entries(const std::vector &entries) { + for (const auto &entry : entries) { + this->add_entry(entry); + } +} + +void todolist::TodoList::add_items(const std::vector &items) { + for (const auto &item : items) { + this->add_item(item); + } +} + +todolist::TodoEntry todolist::TodoList::get_last_entry() { + std::lock_guard lock(this->items_mutex); + if (this->items.empty()) { + throw todolist::todo_error::EmptyTodoList("List is empty"); + } + + return todolist::TodoEntry(this->items.back()); +} + +std::string todolist::TodoList::get_last() { + return this->get_last_entry().text; +} + +std::string todolist::TodoList::get_first() { + std::lock_guard lock(this->items_mutex); + if (this->items.empty()) { + throw todolist::todo_error::EmptyTodoList("List is empty"); + } + + return this->items.front(); +} + +void todolist::TodoList::clear_item(const std::string &todo) { + std::lock_guard lock(this->items_mutex); + auto it = std::find(this->items.begin(), this->items.end(), todo); + if (it == this->items.end()) { + throw todolist::todo_error::TodoDoesNotExist("Item not found"); + } + + this->items.erase(it); +} + +void todolist::TodoList::make_default() { + todolist::set_default_list(this->shared_from_this()); +} + +#include diff --git a/cpp-tests/scaffolding_tests/todolist/lib_todolist.hpp b/cpp-tests/scaffolding_tests/todolist/lib_todolist.hpp new file mode 100644 index 0000000..c7e0e7a --- /dev/null +++ b/cpp-tests/scaffolding_tests/todolist/lib_todolist.hpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include + +namespace { + namespace todolist { + class TodoError: public std::runtime_error { + public: + TodoError() : std::runtime_error("") {} + TodoError(const std::string &what_arg) : std::runtime_error(what_arg) {} + }; + + namespace todo_error { + class TodoDoesNotExist: public TodoError { + public: + TodoDoesNotExist() : TodoError("") {} + TodoDoesNotExist(const std::string &what_arg) : TodoError(what_arg) {} + }; + + class EmptyTodoList: public TodoError { + public: + EmptyTodoList() : TodoError("") {} + EmptyTodoList(const std::string &what_arg) : TodoError(what_arg) {} + }; + + class DuplicateTodo: public TodoError { + public: + DuplicateTodo() : TodoError("") {} + DuplicateTodo(const std::string &what_arg) : TodoError(what_arg) {} + }; + + class EmptyString: public TodoError { + public: + EmptyString() : TodoError("") {} + EmptyString(const std::string &what_arg) : TodoError(what_arg) {} + }; + + class DeligatedError: public TodoError { + public: + DeligatedError() : TodoError("") {} + DeligatedError(const std::string &what_arg) : TodoError(what_arg) {} + }; + } + + struct TodoEntry { + std::string text; + }; + + class TodoList: public std::enable_shared_from_this{ + public: + TodoList() = default; + + void add_item(const std::string &item); + void add_entry(const TodoEntry &entry); + std::vector get_entries(); + std::vector get_items(); + void add_entries(const std::vector &entries); + void add_items(const std::vector &items); + TodoEntry get_last_entry(); + std::string get_last(); + std::string get_first(); + void clear_item(const std::string &item); + void make_default(); + private: + std::vector items; + std::mutex items_mutex; + }; + + std::shared_ptr get_default_list(); + void set_default_list(std::shared_ptr list); + + TodoEntry create_entry_with(const std::string &text); + + static std::shared_ptr default_list = nullptr; + static std::mutex default_list_mutex; + } +} diff --git a/cpp-tests/tests/todolist/main.cpp b/cpp-tests/tests/todolist/main.cpp index cb6f2f1..0133b28 100644 --- a/cpp-tests/tests/todolist/main.cpp +++ b/cpp-tests/tests/todolist/main.cpp @@ -57,7 +57,7 @@ int main() { auto default_list = todolist::get_default_list(); ASSERT_TRUE(default_list); - ASSERT_TRUE(compare_lists(todo->get_entries(), default_list->get_entries())); + ASSERT_TRUE(compare_lists(todo->get_entries(), default_list->get_entries())); ASSERT_FALSE(compare_lists(todo2->get_entries(), default_list->get_entries())); } From dce453d182e88e8a84841c2daaabd9455768b03e Mon Sep 17 00:00:00 2001 From: Martynas Gurskas Date: Mon, 26 Feb 2024 15:42:13 +0200 Subject: [PATCH 12/17] Insert object into map on converter write, check for object existance in map before deleting, add traits test Signed-off-by: Martynas Gurskas --- .../cpp/templates/scaffolding/obj.cpp | 5 +-- .../cpp/templates/scaffolding/object_map.cpp | 8 +++-- cpp-tests/CMakeLists.txt | 2 +- .../scaffolding_tests/traits/lib_traits.cpp | 3 ++ .../scaffolding_tests/traits/lib_traits.hpp | 35 +++++++++++++++++++ cpp-tests/tests/traits/main.cpp | 2 +- 6 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 cpp-tests/scaffolding_tests/traits/lib_traits.cpp create mode 100644 cpp-tests/scaffolding_tests/traits/lib_traits.hpp diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/obj.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/obj.cpp index 9f078c5..240d41b 100644 --- a/bindgen/src/bindings/cpp/templates/scaffolding/obj.cpp +++ b/bindgen/src/bindings/cpp/templates/scaffolding/obj.cpp @@ -1,10 +1,10 @@ -#include {{ type_name }} {{ ffi_converter_name }}::lift(void *ptr) { return {{ name }}_map.at((uint64_t)ptr); } void *{{ ffi_converter_name }}::lower(const {{ type_name }} &obj) { - return (void *)obj.get(); + auto ret = {{ name }}_map.insert(obj); + return (void *)ret; } {{ type_name }} {{ ffi_converter_name }}::read(RustStream &stream) { @@ -15,6 +15,7 @@ void *{{ ffi_converter_name }}::lower(const {{ type_name }} &obj) { } void {{ ffi_converter_name }}::write(RustStream &stream, const {{ type_name }} &obj) { + {{ name }}_map.insert(obj); stream << (uint64_t)obj.get(); } diff --git a/bindgen/src/bindings/cpp/templates/scaffolding/object_map.cpp b/bindgen/src/bindings/cpp/templates/scaffolding/object_map.cpp index 0c93ebc..5daa70c 100644 --- a/bindgen/src/bindings/cpp/templates/scaffolding/object_map.cpp +++ b/bindgen/src/bindings/cpp/templates/scaffolding/object_map.cpp @@ -17,9 +17,11 @@ template struct ObjectMap { void erase(uint64_t ptr) { std::lock_guard guard(this->mutex); - - if (this->map.at(ptr).use_count() == 1) { - this->map.erase(ptr); + + if (this->map.find(ptr) != this->map.end()) { + if (this->map.at(ptr).use_count() == 1) { + this->map.erase(ptr); + } } } private: diff --git a/cpp-tests/CMakeLists.txt b/cpp-tests/CMakeLists.txt index 5a931e4..c8342b7 100644 --- a/cpp-tests/CMakeLists.txt +++ b/cpp-tests/CMakeLists.txt @@ -83,7 +83,7 @@ scaffolding_test_case(geometry) # scaffolding_test_case(rondpoint) scaffolding_test_case(sprites) scaffolding_test_case(todolist) -# scaffolding_test_case(traits) +scaffolding_test_case(traits) # scaffolding_test_case(coverall) # scaffolding_test_case(trait_methods) # scaffolding_test_case(custom_types_builtin) diff --git a/cpp-tests/scaffolding_tests/traits/lib_traits.cpp b/cpp-tests/scaffolding_tests/traits/lib_traits.cpp new file mode 100644 index 0000000..7e1bd61 --- /dev/null +++ b/cpp-tests/scaffolding_tests/traits/lib_traits.cpp @@ -0,0 +1,3 @@ +#include "lib_traits.hpp" + +#include diff --git a/cpp-tests/scaffolding_tests/traits/lib_traits.hpp b/cpp-tests/scaffolding_tests/traits/lib_traits.hpp new file mode 100644 index 0000000..270d3fa --- /dev/null +++ b/cpp-tests/scaffolding_tests/traits/lib_traits.hpp @@ -0,0 +1,35 @@ +#include +#include +#include + +namespace { + namespace traits { + class Button { + public: + virtual ~Button() = default; + virtual std::string name() = 0; + }; + + class GoButton: public Button { + public: + std::string name() override { + return "go"; + } + }; + + class StopButton: public Button { + public: + std::string name() override { + return "stop"; + } + }; + + std::vector> get_buttons() { + return {std::make_shared(), std::make_shared()}; + } + + std::shared_ptr