diff --git a/rust/BUILD b/rust/BUILD index d85721b8f9..9640d25332 100644 --- a/rust/BUILD +++ b/rust/BUILD @@ -162,6 +162,48 @@ rust_library( deps = [":protobuf_cpp"], ) +alias( + name = "protobuf_gtest_matchers", + actual = select({ + ":use_upb_kernel": ":protobuf_gtest_matchers_upb", + "//conditions:default": ":protobuf_gtest_matchers_cpp", + }), +) + +rust_library( + name = "protobuf_gtest_matchers_cpp", + testonly = True, + srcs = ["gtest_matchers.rs"], + aliases = { + "//rust:protobuf_cpp": "protobuf", + }, + visibility = [ + "//rust:__subpackages__", + "//src/google/protobuf:__subpackages__", + ], + deps = [ + ":protobuf_cpp", + "@crate_index//:googletest", + ], +) + +rust_library( + name = "protobuf_gtest_matchers_upb", + testonly = True, + srcs = ["gtest_matchers.rs"], + aliases = { + "//rust:protobuf_upb": "protobuf", + }, + visibility = [ + "//rust:__subpackages__", + "//src/google/protobuf:__subpackages__", + ], + deps = [ + ":protobuf_upb", + "@crate_index//:googletest", + ], +) + rust_library( name = "utf8", srcs = ["utf8.rs"], diff --git a/rust/cpp.rs b/rust/cpp.rs index d9738384a2..61c27ed941 100644 --- a/rust/cpp.rs +++ b/rust/cpp.rs @@ -9,19 +9,19 @@ use crate::__internal::{Enum, Private}; use crate::{ - IntoProxied, Map, MapIter, Mut, ProtoBytes, ProtoStr, ProtoString, Proxied, ProxiedInMapValue, - ProxiedInRepeated, Repeated, RepeatedMut, RepeatedView, View, MapMut, MapView, + IntoProxied, Map, MapIter, MapMut, MapView, Mut, ProtoBytes, ProtoStr, ProtoString, Proxied, + ProxiedInMapValue, ProxiedInRepeated, Repeated, RepeatedMut, RepeatedView, View, }; +use core::fmt::Debug; use paste::paste; -use std::fmt; -use std::slice; use std::convert::identity; -use core::fmt::Debug; +use std::ffi::{c_int, c_void}; +use std::fmt; use std::marker::PhantomData; +use std::mem::{ManuallyDrop, MaybeUninit}; use std::ops::Deref; use std::ptr::{self, NonNull}; -use std::ffi::{c_int, c_void}; -use std::mem::{ManuallyDrop, MaybeUninit}; +use std::slice; /// Defines a set of opaque, unique, non-accessible pointees. /// @@ -339,14 +339,8 @@ pub fn debug_string(msg: RawMessage, f: &mut fmt::Formatter<'_>) -> fmt::Result extern "C" { /// # Safety /// - `msg1` and `msg2` legally dereferencable MessageLite* pointers. - fn proto2_rust_messagelite_equals(msg1: RawMessage, msg2: RawMessage) -> bool; -} - -/// # Safety -/// - `msg1` and `msg2` legally dereferencable MessageLite* pointers. -pub unsafe fn raw_message_equals(msg1: RawMessage, msg2: RawMessage) -> bool { - // SAFETY: Same constraints placed on caller. - unsafe { proto2_rust_messagelite_equals(msg1, msg2) } + #[link_name = "proto2_rust_messagelite_equals"] + pub fn raw_message_equals(msg1: RawMessage, msg2: RawMessage) -> bool; } pub type RawMapIter = UntypedMapIterator; diff --git a/rust/gtest_matchers.rs b/rust/gtest_matchers.rs new file mode 100644 index 0000000000..0dc833a758 --- /dev/null +++ b/rust/gtest_matchers.rs @@ -0,0 +1,35 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2024 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +use googletest::description::Description; +use googletest::matcher::{Matcher, MatcherBase, MatcherResult}; +use protobuf::__internal::MatcherEq; + +#[derive(MatcherBase)] +pub struct MessageMatcher { + expected: T, +} + +impl Matcher<&T> for MessageMatcher +where + T: MatcherEq, +{ + fn matches(&self, actual: &T) -> MatcherResult { + actual.matches(&self.expected).into() + } + + fn describe(&self, matcher_result: MatcherResult) -> Description { + match matcher_result { + MatcherResult::Match => format!("is equal to {:?}", self.expected).into(), + MatcherResult::NoMatch => format!("is not equal to {:?}", self.expected).into(), + } + } +} + +pub fn proto_eq(expected: T) -> MessageMatcher { + MessageMatcher { expected } +} diff --git a/rust/internal.rs b/rust/internal.rs index a2397bdbe7..b8849373cf 100644 --- a/rust/internal.rs +++ b/rust/internal.rs @@ -14,6 +14,7 @@ pub use paste::paste; pub use crate::r#enum::Enum; pub use crate::ProtoStr; +pub use std::fmt::Debug; // TODO: Temporarily re-export these symbols which are now under // __runtime under __internal since some external callers using it through @@ -31,3 +32,8 @@ pub struct Private; /// permit in other crates; this trait is intended to be available to crates /// which were generated by protoc, but not to application code. pub trait SealedInternal {} + +/// A trait used by the proto_eq() gtest macro. +pub trait MatcherEq: SealedInternal + Debug { + fn matches(&self, o: &Self) -> bool; +} diff --git a/rust/test/shared/BUILD b/rust/test/shared/BUILD index 4c789cdfba..f799745105 100644 --- a/rust/test/shared/BUILD +++ b/rust/test/shared/BUILD @@ -466,10 +466,54 @@ rust_test( srcs = ["proto_macro_test.rs"], aliases = { "//rust:protobuf_upb": "protobuf", + "//rust:protobuf_gtest_matchers_upb": "protobuf_gtest_matchers", }, deps = [ + "//rust:protobuf_gtest_matchers_upb", "//rust:protobuf_upb", "//rust/test:unittest_upb_rust_proto", "@crate_index//:googletest", ], ) + +rust_test( + name = "gtest_matchers_cpp_test", + srcs = ["gtest_matchers_test.rs"], + aliases = { + "//rust:protobuf_cpp": "protobuf", + "//rust:protobuf_gtest_matchers_cpp": "protobuf_gtest_matchers", + }, + proc_macro_deps = [ + "@crate_index//:paste", + ], + deps = [ + "//rust:protobuf_cpp", + "//rust:protobuf_gtest_matchers_cpp", + "//rust/test:unittest_cpp_rust_proto", + "//rust/test:unittest_edition_cpp_rust_proto", + "//rust/test:unittest_proto3_cpp_rust_proto", + "//rust/test:unittest_proto3_optional_cpp_rust_proto", + "@crate_index//:googletest", + ], +) + +rust_test( + name = "gtest_matchers_upb_test", + srcs = ["gtest_matchers_test.rs"], + aliases = { + "//rust:protobuf_upb": "protobuf", + "//rust:protobuf_gtest_matchers_upb": "protobuf_gtest_matchers", + }, + proc_macro_deps = [ + "@crate_index//:paste", + ], + deps = [ + "//rust:protobuf_gtest_matchers_upb", + "//rust:protobuf_upb", + "//rust/test:unittest_edition_upb_rust_proto", + "//rust/test:unittest_proto3_optional_upb_rust_proto", + "//rust/test:unittest_proto3_upb_rust_proto", + "//rust/test:unittest_upb_rust_proto", + "@crate_index//:googletest", + ], +) diff --git a/rust/test/shared/gtest_matchers_test.rs b/rust/test/shared/gtest_matchers_test.rs new file mode 100644 index 0000000000..9180409899 --- /dev/null +++ b/rust/test/shared/gtest_matchers_test.rs @@ -0,0 +1,59 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2024 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +use googletest::prelude::*; +use paste::paste; +use protobuf_gtest_matchers::proto_eq; +use unittest_edition_rust_proto::TestAllTypes as TestAllTypesEditions; +use unittest_proto3_rust_proto::TestAllTypes as TestAllTypesProto3; +use unittest_rust_proto::TestAllTypes as TestAllTypesProto2; + +macro_rules! generate_eq_msgs_tests { + ($(($type: ident, $name_ext: ident)),*) => { + paste! {$( + #[gtest] + fn []() { + let mut msg = [< $type >]::new(); + let mut msg2 = [< $type >]::new(); + msg.set_optional_int32(1); + msg2.set_optional_int32(1); + assert_that!(&msg.as_view(), proto_eq(msg2.as_view())); + assert_that!(&msg.as_mut(), proto_eq(msg2.as_mut())); + assert_that!(msg, proto_eq(msg2)); + } + )*} + } +} + +macro_rules! generate_not_eq_msgs_tests { + ($(($type: ident, $name_ext: ident)),*) => { + paste! {$( + #[gtest] + fn []() { + let mut msg = [< $type >]::new(); + let mut msg2 = [< $type >]::new(); + msg.set_optional_int32(1); + msg2.set_optional_int32(0); + assert_that!(&msg.as_view(), not(proto_eq(msg2.as_view()))); + assert_that!(&msg.as_mut(), not(proto_eq(msg2.as_mut()))); + assert_that!(&msg, not(proto_eq(msg2))); + } + )*} + } +} + +generate_eq_msgs_tests!( + (TestAllTypesEditions, editions), + (TestAllTypesProto3, proto3), + (TestAllTypesProto2, proto2) +); + +generate_not_eq_msgs_tests!( + (TestAllTypesEditions, editions), + (TestAllTypesProto3, proto3), + (TestAllTypesProto2, proto2) +); diff --git a/src/google/protobuf/compiler/rust/message.cc b/src/google/protobuf/compiler/rust/message.cc index 77f3a7c5b9..eccd575a86 100644 --- a/src/google/protobuf/compiler/rust/message.cc +++ b/src/google/protobuf/compiler/rust/message.cc @@ -1357,6 +1357,30 @@ void GenerateRs(Context& ctx, const Descriptor& msg) { self.msg.as_ptr() as *const _ } } + + impl $pbi$::MatcherEq for $Msg$ { + fn matches(&self, o: &Self) -> bool { + $pbi$::MatcherEq::matches( + &$pb$::AsView::as_view(self), + &$pb$::AsView::as_view(o)) + } + } + + impl<'a> $pbi$::MatcherEq for $Msg$Mut<'a> { + fn matches(&self, o: &Self) -> bool { + $pbi$::MatcherEq::matches( + &$pb$::AsView::as_view(self), + &$pb$::AsView::as_view(o)) + } + } + + impl<'a> $pbi$::MatcherEq for $Msg$View<'a> { + fn matches(&self, o: &Self) -> bool { + unsafe { + $pbr$::raw_message_equals(self.msg, o.msg) + } + } + } )rs"); } else { ctx.Emit({{"Msg", RsSafeName(msg.name())}}, R"rs( @@ -1377,6 +1401,34 @@ void GenerateRs(Context& ctx, const Descriptor& msg) { self.msg.as_ptr() as *const _ } } + + impl $pbi$::MatcherEq for $Msg$ { + fn matches(&self, o: &Self) -> bool { + $pbi$::MatcherEq::matches( + &$pb$::AsView::as_view(self), + &$pb$::AsView::as_view(o)) + } + } + + impl<'a> $pbi$::MatcherEq for $Msg$Mut<'a> { + fn matches(&self, o: &Self) -> bool { + $pbi$::MatcherEq::matches( + &$pb$::AsView::as_view(self), + &$pb$::AsView::as_view(o)) + } + } + + impl<'a> $pbi$::MatcherEq for $Msg$View<'a> { + fn matches(&self, o: &Self) -> bool { + unsafe { + $pbr$::upb_Message_IsEqual( + self.msg, + o.msg, + <$Msg$ as $pbr$::AssociatedMiniTable>::mini_table(), + 0) + } + } + } )rs"); } } // NOLINT(readability/fn_size)