From 918a9de8ffbe13698dfe1dcc7e616bccaa0dcdc1 Mon Sep 17 00:00:00 2001 From: Marius Eriksen Date: Tue, 11 Nov 2025 11:07:39 -0800 Subject: [PATCH] [hyperactor] assert_behaves! This adds a macro that asserts an actor's behavior; we use it to test the mesh behavior: ``` hyperactor::assert_behaves!(TestMeshController as Controller); ``` It is implemented by statically validating that the actor type can bind to the defined behavior. This then provide a way to both document the intended behavior of an actor, and also to statically validate at the definition site that the behavior is implemented correctly. (We already statically validate this at the *use* site, but then the errors are already "at a distance".) Differential Revision: [D86786231](https://our.internmc.facebook.com/intern/diff/D86786231/) **NOTE FOR REVIEWERS**: This PR has internal Meta-specific changes or comments, please review them on [Phabricator](https://our.internmc.facebook.com/intern/diff/D86786231/)! [ghstack-poisoned] --- hyperactor/src/actor.rs | 40 ++++++++++++++++++++++++++++ hyperactor_mesh/src/resource/mesh.rs | 12 +-------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/hyperactor/src/actor.rs b/hyperactor/src/actor.rs index 09b81ed41..62cec37dd 100644 --- a/hyperactor/src/actor.rs +++ b/hyperactor/src/actor.rs @@ -715,6 +715,46 @@ pub trait Binds: Referable { /// is handled by a specific actor type. pub trait RemoteHandles: Referable {} +/// Check if the actor behaves-as the a given behavior (defined by [`behavior!`]). +/// +/// ``` +/// # use serde::Serialize; +/// # use serde::Deserialize; +/// # use hyperactor::Named; +/// +/// // First, define a behavior, based on handling a single message type `()`. +/// hyperactor::behavior!( +/// UnitBehavior, +/// (), +/// ) +/// +/// #[derive(hyperactor::Actor, Default)] +/// struct MyActor; +/// +/// #[async_trait::async_trait] +/// impl hyperactor::Handler<()> for MyActor { +/// async fn handle( +/// &mut self, +/// _cx: &hyperactor::Context, +/// _message: (), +/// ) -> Result<(), anyhow::Error> { +/// // no-op +/// Ok((b)) +/// } +/// } +/// +/// hyperactor::assert_behaves!(MyActor as UnitBehavior); +/// ``` +#[macro_export] +macro_rules! assert_behaves { + ($ty:ty as $behavior:ty) => { + const _: fn() = || { + fn check>() {} + check::<$behavior>(); + }; + }; +} + #[cfg(test)] mod tests { use std::sync::Mutex; diff --git a/hyperactor_mesh/src/resource/mesh.rs b/hyperactor_mesh/src/resource/mesh.rs index 08026c533..736ddc9a3 100644 --- a/hyperactor_mesh/src/resource/mesh.rs +++ b/hyperactor_mesh/src/resource/mesh.rs @@ -64,7 +64,6 @@ hyperactor::behavior!( #[cfg(test)] mod test { use hyperactor::Actor; - use hyperactor::ActorRef; use hyperactor::Context; use hyperactor::Handler; @@ -114,14 +113,5 @@ mod test { _message: Stop => unimplemented!(), } - #[test] - fn test_controller_behavior() { - use hyperactor::ActorHandle; - - // This is a compile-time check that TestMeshController implements - // the Controller behavior correctly. - fn _assert_bind(handle: ActorHandle) -> ActorRef> { - handle.bind() - } - } + hyperactor::assert_behaves!(TestMeshController as Controller); }