Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

error: proc macro panicked on mocking GRPC interface generated by tonic #637

Open
arvid-norlander opened this issue Jan 31, 2025 · 6 comments

Comments

@arvid-norlander
Copy link

arvid-norlander commented Jan 31, 2025

If I use a GRPC subscription, generate rust bindings with tonic and try to mock that I get

error: proc macro panicked
  --> src/main.rs:15:1
   |
15 | / mockall::mock! {
16 | |     pub(crate) MyMockService {}
17 | |
18 | |     #[tonic::async_trait]
...  |
29 | |     }
30 | | }
   | |_^
   |
   = help: message: called `Result::unwrap()` on an `Err` value: Error("expected `fn`")

This seems like an internal error in mockall. I have created a minimal (or at least somewhat minimal) reproducer, the full cargo project is attached, but the code is:

#[derive(Clone, PartialEq)]
pub struct SubscribeToMyGrpcServiceUpdatesResponse {
    pub a: ::core::option::Option<i32>,
}

#[tonic::async_trait]
pub trait MyGrpcServicesService: std::marker::Send + std::marker::Sync + 'static {
    /// Server streaming response type for the SubscribeToMyGrpcServiceUpdates method.
    type SubscribeToMyGrpcServiceUpdatesStream: tonic::codegen::tokio_stream::Stream<
            Item = std::result::Result<SubscribeToMyGrpcServiceUpdatesResponse, tonic::Status>,
        > + std::marker::Send
        + 'static;
}

mockall::mock! {
    pub(crate) MyMockService {}

    #[tonic::async_trait]
    impl MyGrpcServicesService for MyMockService {
      /// Server streaming response type for the SubscribeToMyGrpcServiceUpdates method.
        type SubscribeToMyGrpcServiceUpdatesStream: tonic::codegen::tokio_stream::Stream<
                Item = std::result::Result<
                    SubscribeToMyGrpcServiceUpdatesResponse,
                    tonic::Status,
                >,
            >
            + std::marker::Send
            + 'static;
    }
}

Here is the repro project (the above code + the needed dependencies in Cargo.toml basically): mockall_panic_repro.tar.gz

This happens with mockall 0.13.1, I have not checked if it happens with older versions.

If I remove the type and only try to mock the functions (not included in the small reproducer) it complains as well:

error[E0046]: not all trait items implemented, missing: `SubscribeToMyGrpcServiceUpdatesStream`

So it seems associated type support is a bit lacking when using mock!, the error message if you just blindly copy the trait definition into the mock! results in a very confusing error message. It would be good to have something that points at the appropriate line and says something like "ADT = SomeType expected, found ADT definition" or similar.

@asomers
Copy link
Owner

asomers commented Feb 1, 2025

The problem here is that you didn't copy the trait definition into the mock!. Not in a way that exposes it to mockall, at least. Your trait implementation relies on another proc macro: #[tonic::async_trait]. That proc macro must be adding some methods of its own, but Mockall has no way to know about them, because Rust expands the mock! macro before the #[tonic::async_trait] one.

But not all hope is lost! There is precedent for mocking macros like this. Mockall contains some special case code to handle #[async_trait]. Perhaps the same could be done for #[tonic::async_trait]. But first we'll need to know what that macro actually does. Is there documentation for it? Do you know what it expands to?

BTW, for better error messages, build Mockall with the "nightly" crate feature.

@arvid-norlander
Copy link
Author

arvid-norlander commented Feb 3, 2025

Thanks for your reply! I didn't think about the outside-in expansion of macros, that makes sense.

It appears that tonic::async_trait is just a re-export by the tonic crate of the normal async_trait. Presumably done for semver headache avoidance. In macro code it is going to be fraught with peril to match on the name anyway though (since macros aren't type aware)?

As for nightly, I presume it also requires a nightly compiler? Or is this purely an experimental option for mockall itself?

@asomers
Copy link
Owner

asomers commented Feb 3, 2025

Yes, the "nightly" crate feature requires a nightly compiler. If tonic::async_trait is the same thing as async_trait, then it should be possible to make this work.

@arvid-norlander
Copy link
Author

arvid-norlander commented Feb 4, 2025

I have been looking at this and I can't see how to idiomatically define a mock! of a trait where a function returns an associated type that is a tokio Stream. I can get it compile by creating a custom mock stream and implementing Stream for that.

But that means I need to mock poll_next and size_hint manually, which is awkward to say the least. Plus lifetimes become interesting in my case.

Meanwhile the documentation suggests something easier might be done in https://docs.rs/mockall/latest/mockall/index.html#impl-future but there isn't any clear example for Stream, nor is it obvious how to do this with associated types. Searching the docs yield no results for "Stream" at all, likely because rustdoc doesn't do full text search...

And impl in associated types is apparently an unstable feature, or so rustc tells me.

@asomers
Copy link
Owner

asomers commented Feb 4, 2025

Here's an example for impl Stream:

pub fn bar(&self) -> impl Stream<Item=u32>
.

But if I understand correctly, your real problem is that the trait requires your method to return some specific type that implements Stream. If so, then your mock method must return exactly that type, too. There's no getting around that limitation without somehow changing the definition of your trait.

@arvid-norlander
Copy link
Author

Yeah that is correct, sorry I probably ended up redacting too much (out of caution with a proprietary code base). I basically have a code-generated trait like:

trait A {
    type SomeType: Stream<Item = ...> + Send + 'static;
    fn someFn(...) -> SomeType;
}

To mock that does, it does indeed seem I need to name a specific type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants