From 4c771357834e84d199d1266d2e0bc97c1e4de4f9 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Wed, 2 Sep 2026 16:33:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B4=E4=BD=93=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Error_handling_specification.md | 2 ++ .../Failure_Policy.cpp | 11 +++++-- .../Failure_Policy.hpp | 5 +++- kernel/kernel/include/relation/Dag.ipp | 9 +++--- .../src/Throttled_Latest_Only.cpp | 13 +++++---- .../test/Throttled_Latest_Only_Tests.cpp | 3 ++ .../module/task_flow/src/Task_Graph.cpp | 7 +++-- .../module/task_flow/src/Task_Runtime.cpp | 17 +++++++---- .../task_flow/src/Taskflow_Observation.cpp | 5 ++-- .../src/detail/Taskflow_Execution.ipp | 3 +- .../test/Taskflow_Observation_Tests.cpp | 13 +++++---- .../src/detail/Timer_Scheduler.cpp | 3 +- .../test/error/Failure_Policy_Tests.cpp | 7 +++-- kernel/kernel/test/model/Model_Tests.cpp | 29 +++++++++++++++++-- 14 files changed, 89 insertions(+), 38 deletions(-) diff --git a/Error_handling_specification.md b/Error_handling_specification.md index 950edae..97f371c 100644 --- a/Error_handling_specification.md +++ b/Error_handling_specification.md @@ -104,6 +104,8 @@ exception `fast_fail` 只改变 Unknown Failure 的处置方式, **不得改变 Known Result / Unknown Failure 的分类。** +Unknown Failure Policy 是进程级唯一配置,必须在启动异步工作前设置,任务在途时不得切换。实际失败点只向统一处理函数提交原始 `std::exception_ptr`,不得再逐次传入策略或在模块内保存策略副本。 + ## 4. 第三方库处理规则 第三方库自身使用返回码还是异常,不决定本系统的处理方式。 diff --git a/kernel/kernel/include/Error_handling_specification/Failure_Policy.cpp b/kernel/kernel/include/Error_handling_specification/Failure_Policy.cpp index 77fc5b9..227b3c9 100644 --- a/kernel/kernel/include/Error_handling_specification/Failure_Policy.cpp +++ b/kernel/kernel/include/Error_handling_specification/Failure_Policy.cpp @@ -1,8 +1,15 @@ #include "Failure_Policy.hpp" +#include namespace aethera { -[[noreturn]] void Failure_Policy::handle_unknown_failure(Unknown_Failure_Policy policy, std::exception_ptr failure) { - if (policy == Unknown_Failure_Policy::exception && failure) std::rethrow_exception(failure); +namespace { +std::atomic unknown_failure_policy{Unknown_Failure_Policy::fast_fail}; +} +void Failure_Policy::configure(Unknown_Failure_Policy policy) noexcept { + unknown_failure_policy.store(policy, std::memory_order_relaxed); +} +[[noreturn]] void Failure_Policy::handle_unknown_failure(std::exception_ptr failure) { + if (unknown_failure_policy.load(std::memory_order_relaxed) == Unknown_Failure_Policy::exception && failure) std::rethrow_exception(failure); std::terminate(); } } // namespace aethera diff --git a/kernel/kernel/include/Error_handling_specification/Failure_Policy.hpp b/kernel/kernel/include/Error_handling_specification/Failure_Policy.hpp index d7d2a3d..74766b9 100644 --- a/kernel/kernel/include/Error_handling_specification/Failure_Policy.hpp +++ b/kernel/kernel/include/Error_handling_specification/Failure_Policy.hpp @@ -7,6 +7,9 @@ enum struct Unknown_Failure_Policy : std::uint8_t { exception /* 原样重抛 Unknown Failure,由上层隔离边界处理。 */ }; struct Failure_Policy final { - [[noreturn]] static void handle_unknown_failure(Unknown_Failure_Policy policy, std::exception_ptr failure); + /* 设置进程级策略;必须在启动异步工作前完成,不得在任务在途时切换。 */ + static void configure(Unknown_Failure_Policy policy) noexcept; + /* 按当前进程级策略处理原始 Unknown Failure;空 failure 无法传播,只能终止。 */ + [[noreturn]] static void handle_unknown_failure(std::exception_ptr failure); }; } // namespace aethera diff --git a/kernel/kernel/include/relation/Dag.ipp b/kernel/kernel/include/relation/Dag.ipp index 76b232a..f61ab21 100644 --- a/kernel/kernel/include/relation/Dag.ipp +++ b/kernel/kernel/include/relation/Dag.ipp @@ -1,4 +1,5 @@ #pragma once +#include "Error_handling_specification/Failure_Policy.hpp" #include #include #include @@ -72,7 +73,7 @@ template Dag::Dag(Dag&&) noexcept = default; template auto Dag::operator=(Dag&&) noexcept -> Dag& = default; template template void Dag::for_each_topological(Callback&& callback) { const auto order = detail::dag_topological_indices(d->nodes, d->edges); - if (!order) throw std::logic_error("authoritative DAG is invalid"); + if (!order) Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::logic_error("authoritative DAG is invalid"))); for (const auto index : *order) std::invoke(callback, d->nodes[index].id, d->nodes[index].object); } template template void Dag::for_each_dependency(Callback&& callback) const { for (const auto& edge : d->edges) std::invoke(callback, edge.target, edge.source); } @@ -144,7 +145,7 @@ template void Owned_Dag::Internal::advance() } template void Owned_Dag::set(Graph graph) { typename Internal::Private::Deferred_Command command = [graph = std::move(graph)](Graph& current) mutable { current = std::move(graph); }; - if (!internal.d->queue.enqueue(std::move(command))) throw std::bad_alloc{}; + if (!internal.d->queue.enqueue(std::move(command))) Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::bad_alloc{})); } template void Owned_Dag::set(Edit edit, Edit_Completion completion) { typename Internal::Private::Deferred_Command command = [edit = std::move(edit), completion = std::move(completion)](Graph& current) mutable { @@ -255,10 +256,10 @@ template void Owned_Dag::set(Edit edit, Edit transaction_open = false; } catch (...) { if (transaction_open) rollback(); - throw; + Failure_Policy::handle_unknown_failure(std::current_exception()); } if (completion) completion({}); }; - if (!internal.d->queue.enqueue(std::move(command))) throw std::bad_alloc{}; + if (!internal.d->queue.enqueue(std::move(command))) Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::bad_alloc{})); } } // namespace aethera diff --git a/kernel/kernel/module/frame_policy/src/Throttled_Latest_Only.cpp b/kernel/kernel/module/frame_policy/src/Throttled_Latest_Only.cpp index 44df72f..dfb2493 100644 --- a/kernel/kernel/module/frame_policy/src/Throttled_Latest_Only.cpp +++ b/kernel/kernel/module/frame_policy/src/Throttled_Latest_Only.cpp @@ -1,4 +1,5 @@ #include "Throttled_Latest_Only.hpp" +#include "Error_handling_specification/Failure_Policy.hpp" #include #include #include @@ -35,7 +36,7 @@ auto Throttled_Latest_only::Private::start() -> std::expected std::expected std::ex } catch (...) { cancel_failed(); - throw; + Failure_Policy::handle_unknown_failure(std::current_exception()); } return {}; } @@ -214,7 +215,7 @@ void Throttled_Latest_only::Private::frame_due() { const auto result = scene->render(frames[selected_index].frame, [this, selected_index](proxy& completed_frame, std::exception_ptr failure) { if (failure) { abandon_render(selected_index); - std::rethrow_exception(failure); + Failure_Policy::handle_unknown_failure(failure); } rendered(selected_index, completed_frame); }); @@ -223,7 +224,7 @@ void Throttled_Latest_only::Private::frame_due() { } catch (...) { abandon_render(selected_index); - throw; + Failure_Policy::handle_unknown_failure(std::current_exception()); } } void Throttled_Latest_only::Private::record_render_submission(scene::Render_Result result) { @@ -296,7 +297,7 @@ void Throttled_Latest_only::Private::dispatch_send(std::size_t slot_index) { } catch (...) { abandon_send(slot_index); - throw; + Failure_Policy::handle_unknown_failure(std::current_exception()); } } void Throttled_Latest_only::Private::sent(std::size_t slot_index, proxy& completed_frame) { diff --git a/kernel/kernel/module/frame_policy/test/Throttled_Latest_Only_Tests.cpp b/kernel/kernel/module/frame_policy/test/Throttled_Latest_Only_Tests.cpp index fd7b2dc..64d9566 100644 --- a/kernel/kernel/module/frame_policy/test/Throttled_Latest_Only_Tests.cpp +++ b/kernel/kernel/module/frame_policy/test/Throttled_Latest_Only_Tests.cpp @@ -1,4 +1,5 @@ #include "frame_policy/src/Throttled_Latest_Only.hpp" +#include "Error_handling_specification/Failure_Policy.hpp" #include "statistics/Sliding_Statistics.hpp" #include @@ -458,6 +459,7 @@ TEST(Throttled_Latest_Only, Reports_Invalid_Configuration) { } TEST(Throttled_Latest_Only, Render_Failure_Releases_Slot_Before_It_Escapes) { + aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception); auto control = std::make_shared(); auto policy = make_policy(control, 60.0); ASSERT_TRUE(policy->start()); @@ -477,6 +479,7 @@ TEST(Throttled_Latest_Only, Render_Failure_Releases_Slot_Before_It_Escapes) { } TEST(Throttled_Latest_Only, Synchronous_Completions_And_Failure_Restore_Slot) { + aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception); auto control = std::make_shared(); control->complete_render_synchronously = true; control->throw_from_send = true; diff --git a/kernel/kernel/module/task_flow/src/Task_Graph.cpp b/kernel/kernel/module/task_flow/src/Task_Graph.cpp index 6f9470d..5fe0418 100644 --- a/kernel/kernel/module/task_flow/src/Task_Graph.cpp +++ b/kernel/kernel/module/task_flow/src/Task_Graph.cpp @@ -1,5 +1,6 @@ #include "Task_Graph.hpp" #include "detail/Taskflow_Execution.ipp" +#include "Error_handling_specification/Failure_Policy.hpp" #include #include #include @@ -151,7 +152,7 @@ detail::Task_Graph_Execution::~Task_Graph_Execution() { } } std::shared_ptr detail::Task_Graph_Execution::create(Task_Graph& graph) { - if (!graph.d) throw std::invalid_argument("Task graph is moved from"); + if (!graph.d) Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::invalid_argument("Task graph is moved from"))); auto execution = std::make_shared(Construction_Key{}); execution->root = graph.d; std::unordered_set visited; @@ -228,7 +229,7 @@ auto Task_Graph::add(std::string task_name, std::function work) -> std:: } catch (...) { private_data.get().record_failure(std::current_exception()); - throw; + Failure_Policy::handle_unknown_failure(std::current_exception()); } }); const auto node_id = d->unique_node_id(task_name); @@ -247,7 +248,7 @@ auto Task_Graph::add_condition(std::string task_name, std::function work) } catch (...) { private_data.get().record_failure(std::current_exception()); - throw; + Failure_Policy::handle_unknown_failure(std::current_exception()); } }); const auto node_id = d->unique_node_id(task_name); diff --git a/kernel/kernel/module/task_flow/src/Task_Runtime.cpp b/kernel/kernel/module/task_flow/src/Task_Runtime.cpp index 224021d..64221e3 100644 --- a/kernel/kernel/module/task_flow/src/Task_Runtime.cpp +++ b/kernel/kernel/module/task_flow/src/Task_Runtime.cpp @@ -11,18 +11,22 @@ #include #include namespace aethera { -detail::Taskflow_Completion_Boundary::Taskflow_Completion_Boundary(Unknown_Failure_Policy selected_failure_policy, Taskflow_Exception_Boundary selected_exception_boundary) : failure_policy(selected_failure_policy), exception_boundary(std::move(selected_exception_boundary)) {} +detail::Taskflow_Completion_Boundary::Taskflow_Completion_Boundary(Taskflow_Exception_Boundary selected_exception_boundary) : exception_boundary(std::move(selected_exception_boundary)) {} void detail::Taskflow_Completion_Boundary::invoke(Taskflow_Completion& completion, std::exception_ptr failure) const noexcept { try { completion(std::move(failure)); } catch (...) { - if (failure_policy == Unknown_Failure_Policy::fast_fail) Failure_Policy::handle_unknown_failure(failure_policy, std::current_exception()); try { - exception_boundary(std::current_exception()); + Failure_Policy::handle_unknown_failure(std::current_exception()); } catch (...) { - std::terminate(); + try { + exception_boundary(std::current_exception()); + } + catch (...) { + std::terminate(); + } } } } @@ -179,8 +183,9 @@ public: ? Initialize_Task_Runtime_Result::already_initialized : Initialize_Task_Runtime_Result::initialization_in_progress; } + Failure_Policy::configure(failure_policy); try { - auto next_completion_boundary = std::make_unique(failure_policy, std::move(exception_boundary)); + auto next_completion_boundary = std::make_unique(std::move(exception_boundary)); auto next_executor = std::make_unique(workers); (void)next_executor->make_observer(); completion_boundary = std::move(next_completion_boundary); @@ -191,7 +196,7 @@ public: phase.store( Runtime_Phase::uninitialized, std::memory_order_release); - throw; + Failure_Policy::handle_unknown_failure(std::current_exception()); } return Initialize_Task_Runtime_Result::initialized; } diff --git a/kernel/kernel/module/task_flow/src/Taskflow_Observation.cpp b/kernel/kernel/module/task_flow/src/Taskflow_Observation.cpp index 75c3924..0e5fbff 100644 --- a/kernel/kernel/module/task_flow/src/Taskflow_Observation.cpp +++ b/kernel/kernel/module/task_flow/src/Taskflow_Observation.cpp @@ -1,5 +1,6 @@ #include "Taskflow_Observation.hpp" #include "detail/Taskflow_Execution.ipp" +#include "Error_handling_specification/Failure_Policy.hpp" #include #include #include @@ -93,7 +94,7 @@ std::expected Taskfl : Take_Taskflow_Observation_Result::recording}; } if (auto failure = d->take_failure()) { - std::rethrow_exception(failure); + Failure_Policy::handle_unknown_failure(failure); } auto result = std::move(d->trace); d->worker_states.clear(); @@ -149,7 +150,7 @@ std::expectedphase.store( Observation_Phase::requested, std::memory_order_release); - throw; + Failure_Policy::handle_unknown_failure(std::current_exception()); } return Taskflow_Observation_Execution{std::move(observation.d)}; } diff --git a/kernel/kernel/module/task_flow/src/detail/Taskflow_Execution.ipp b/kernel/kernel/module/task_flow/src/detail/Taskflow_Execution.ipp index 2b17cf4..e73865c 100644 --- a/kernel/kernel/module/task_flow/src/detail/Taskflow_Execution.ipp +++ b/kernel/kernel/module/task_flow/src/detail/Taskflow_Execution.ipp @@ -19,10 +19,9 @@ class Worker; namespace aethera::detail { /* Process-lifetime worker boundary. The handler is immutable after runtime initialization and executes on the completing Taskflow worker. */ struct Taskflow_Completion_Boundary final { - Taskflow_Completion_Boundary(Unknown_Failure_Policy failure_policy, Taskflow_Exception_Boundary exception_boundary); + explicit Taskflow_Completion_Boundary(Taskflow_Exception_Boundary exception_boundary); void invoke(Taskflow_Completion& completion, std::exception_ptr failure) const noexcept; private: - Unknown_Failure_Policy failure_policy; Taskflow_Exception_Boundary exception_boundary; }; /* 单帧执行期间持有根图及全部子图;调用方保证同一张图不并发执行或重建。 */ diff --git a/kernel/kernel/module/task_flow/test/Taskflow_Observation_Tests.cpp b/kernel/kernel/module/task_flow/test/Taskflow_Observation_Tests.cpp index f009a4d..36a9502 100644 --- a/kernel/kernel/module/task_flow/test/Taskflow_Observation_Tests.cpp +++ b/kernel/kernel/module/task_flow/test/Taskflow_Observation_Tests.cpp @@ -169,9 +169,10 @@ TEST(Task_Runtime, Validates_Optional_Exception_Boundary) { } TEST(Taskflow_Completion_Boundary, Exception_Policy_Forwards_Completion_Failure) { + aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception); std::exception_ptr forwarded; - aethera::detail::Taskflow_Completion_Boundary boundary(aethera::Unknown_Failure_Policy::exception, [&forwarded](std::exception_ptr failure) { forwarded = std::move(failure); }); - aethera::Taskflow_Completion completion = [](std::exception_ptr failure) { std::rethrow_exception(failure); }; + aethera::detail::Taskflow_Completion_Boundary boundary([&forwarded](std::exception_ptr failure) { forwarded = std::move(failure); }); + aethera::Taskflow_Completion completion = [](std::exception_ptr failure) { aethera::Failure_Policy::handle_unknown_failure(failure); }; boundary.invoke(completion, std::make_exception_ptr(std::runtime_error("task failure"))); ASSERT_TRUE(forwarded); EXPECT_THROW(std::rethrow_exception(forwarded), std::runtime_error); @@ -179,15 +180,15 @@ TEST(Taskflow_Completion_Boundary, Exception_Policy_Forwards_Completion_Failure) #if GTEST_HAS_DEATH_TEST TEST(Taskflow_Completion_Boundary, Fast_Fail_Policy_Terminates_On_Completion_Failure) { - aethera::detail::Taskflow_Completion_Boundary boundary(aethera::Unknown_Failure_Policy::fast_fail, {}); + aethera::detail::Taskflow_Completion_Boundary boundary({}); aethera::Taskflow_Completion completion = [](std::exception_ptr) { throw std::runtime_error("completion failure"); }; - EXPECT_DEATH({ boundary.invoke(completion, {}); }, ""); + EXPECT_DEATH({ aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::fast_fail); boundary.invoke(completion, {}); }, ""); } TEST(Taskflow_Completion_Boundary, Exception_Policy_Terminates_If_The_Receiver_Throws) { - aethera::detail::Taskflow_Completion_Boundary boundary(aethera::Unknown_Failure_Policy::exception, [](std::exception_ptr) { throw std::runtime_error("receiver failure"); }); + aethera::detail::Taskflow_Completion_Boundary boundary([](std::exception_ptr) { throw std::runtime_error("receiver failure"); }); aethera::Taskflow_Completion completion = [](std::exception_ptr) { throw std::runtime_error("completion failure"); }; - EXPECT_DEATH({ boundary.invoke(completion, {}); }, ""); + EXPECT_DEATH({ aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception); boundary.invoke(completion, {}); }, ""); } #endif diff --git a/kernel/kernel/module/time_thread/src/detail/Timer_Scheduler.cpp b/kernel/kernel/module/time_thread/src/detail/Timer_Scheduler.cpp index 0d745b8..cf6e53b 100644 --- a/kernel/kernel/module/time_thread/src/detail/Timer_Scheduler.cpp +++ b/kernel/kernel/module/time_thread/src/detail/Timer_Scheduler.cpp @@ -1,4 +1,5 @@ #include "Timer_Scheduler.hpp" +#include "Error_handling_specification/Failure_Policy.hpp" #include "timer-wheel.h" @@ -41,7 +42,7 @@ struct Timer_Scheduler::Impl { explicit Impl(proxy time_source) : time_source(std::move(time_source)) { if (!this->time_source) { - throw std::invalid_argument("Timer time source is unavailable"); + Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::invalid_argument("Timer time source is unavailable"))); } } diff --git a/kernel/kernel/test/error/Failure_Policy_Tests.cpp b/kernel/kernel/test/error/Failure_Policy_Tests.cpp index 9e71768..7b4f2bd 100644 --- a/kernel/kernel/test/error/Failure_Policy_Tests.cpp +++ b/kernel/kernel/test/error/Failure_Policy_Tests.cpp @@ -4,9 +4,10 @@ namespace { TEST(Failure_Policy, Exception_Rethrows_The_Original_Unknown_Failure) { + aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception); const auto failure = std::make_exception_ptr(std::runtime_error("unknown failure")); try { - aethera::Failure_Policy::handle_unknown_failure(aethera::Unknown_Failure_Policy::exception, failure); + aethera::Failure_Policy::handle_unknown_failure(failure); } catch (const std::runtime_error& error) { EXPECT_STREQ(error.what(), "unknown failure"); return; @@ -16,10 +17,10 @@ TEST(Failure_Policy, Exception_Rethrows_The_Original_Unknown_Failure) { TEST(Failure_Policy, Fast_Fail_Terminates) { const auto failure = std::make_exception_ptr(std::runtime_error("unknown failure")); - EXPECT_DEATH(aethera::Failure_Policy::handle_unknown_failure(aethera::Unknown_Failure_Policy::fast_fail, failure), ""); + EXPECT_DEATH({ aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::fast_fail); aethera::Failure_Policy::handle_unknown_failure(failure); }, ""); } TEST(Failure_Policy, Missing_Unknown_Failure_Terminates) { - EXPECT_DEATH(aethera::Failure_Policy::handle_unknown_failure(aethera::Unknown_Failure_Policy::exception, {}), ""); + EXPECT_DEATH({ aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception); aethera::Failure_Policy::handle_unknown_failure({}); }, ""); } } // namespace diff --git a/kernel/kernel/test/model/Model_Tests.cpp b/kernel/kernel/test/model/Model_Tests.cpp index 12c006f..4d65605 100644 --- a/kernel/kernel/test/model/Model_Tests.cpp +++ b/kernel/kernel/test/model/Model_Tests.cpp @@ -4,15 +4,22 @@ #include namespace { PRO_DEF_MEM_DISPATCH(Test_Model_Private_Read, private_value); -struct Test_Model_Private_Facade : aethera::facade_builder::add_convention::support_relocation::build {}; +struct Test_Model_Private_Facade : aethera::facade_builder +::add_convention +::support_relocation +::build {}; PRO_DEF_MEM_DISPATCH(Other_Test_Model_Private_Read, other_private_value); -struct Other_Test_Model_Private_Facade : aethera::facade_builder::add_convention::support_relocation::build {}; +struct Other_Test_Model_Private_Facade : aethera::facade_builder +::add_convention +::support_relocation +::build {}; struct Number_List_Tag { template using Value = typename Layer::Numbers; }; struct Test_Model_Base : aethera::Model_Layer, aethera::Storage_Registration, aethera::Storage_Registration> { struct Prop : Prev { int base_number{}; + int same_name{}; }; struct State : Prev { int base_state{}; @@ -36,6 +43,7 @@ struct Test_Model : aethera::Def { struct Prop : Prev { int number{}; int other{}; + int same_name{}; }; struct State : Prev { int state{}; @@ -114,6 +122,23 @@ TEST(Model, Registered_Struct_Set_Publishes_To_The_Same_Tagged_Storage) { properties.internal.advance(); EXPECT_EQ(properties.internal.use()->number, 13); } +TEST(Model, Same_Name_Properties_In_Base_And_Derived_Layers_Are_Set_Independently) { + Test_Model::Builder builder; + builder + .set(&Test_Model_Base::Prop::same_name, 29) + .set(&Test_Model::Prop::same_name, 31); + auto model = builder.build(); + const auto* properties = model->d->get().internal.use(); + EXPECT_EQ(static_cast(*properties).same_name, 29); + EXPECT_EQ(properties->same_name, 31); + model->set(&Test_Model_Base::Prop::same_name, 37); + model->set(&Test_Model::Prop::same_name, 41); + auto& storage = model->d->get(); + storage.internal.advance(); + properties = storage.internal.use(); + EXPECT_EQ(static_cast(*properties).same_name, 37); + EXPECT_EQ(properties->same_name, 41); +} TEST(Model, Consumer_Facade_Selects_One_Model_Private_View) { auto model = aethera::make_model_proxy(); EXPECT_EQ(model->private_value(), 41);