Protocol Buffers - Google's data interchange format (grpc依赖) https://developers.google.com/protocol-buffers/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

413 lines
15 KiB

// Protocol Buffers - Google's data interchange format
// Copyright 2023 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 enums_rust_proto::{test_map_with_nested_enum, TestMapWithNestedEnum};
use googletest::prelude::*;
use map_unittest_rust_proto::{MapEnum, TestMap, TestMapWithMessages};
use paste::paste;
use protobuf::ProtoString;
use std::collections::HashMap;
use unittest_rust_proto::TestAllTypes;
macro_rules! generate_map_primitives_tests {
(
$(($k_type:ty, $v_type:ty, $k_field:ident, $v_field:ident,
$k_nonzero:expr, $v_nonzero:expr $(,)?)),*
$(,)?
) => {
paste! { $(
#[googletest::test]
fn [< test_map_ $k_field _ $v_field >]() {
let mut msg = TestMap::new();
assert_that!(msg.[< map_ $k_field _ $v_field >]().len(), eq(0));
assert_that!(
msg.[< map_ $k_field _ $v_field >](),
elements_are![]
);
assert_that!(
msg.[< map_ $k_field _ $v_field >]().keys().collect::<Vec<_>>(),
elements_are![]
);
assert_that!(
msg.[< map_ $k_field _ $v_field >]().values().collect::<Vec<_>>(),
elements_are![]
);
let k = <$k_type>::default();
let v = <$v_type>::default();
assert_that!(msg.[< map_ $k_field _ $v_field _mut>]().insert(k, v), eq(true));
assert_that!(msg.[< map_ $k_field _ $v_field _mut>]().insert(k, v), eq(false));
assert_that!(msg.[< map_ $k_field _ $v_field >]().len(), eq(1));
assert_that!(
msg.[< map_ $k_field _ $v_field >](),
elements_are![eq((k, v))]
);
assert_that!(
msg.[< map_ $k_field _ $v_field >]().keys().collect::<Vec<_>>(),
elements_are![eq(&k)]
);
assert_that!(
msg.[< map_ $k_field _ $v_field >]().values().collect::<Vec<_>>(),
elements_are![eq(&v)]
);
let k2: $k_type = $k_nonzero;
let v2: $v_type = $v_nonzero;
assert_that!(msg.[< map_ $k_field _ $v_field _mut>]().insert(k2, v2), eq(true));
assert_that!(msg.[< map_ $k_field _ $v_field >](), len(eq(2)));
assert_that!(
msg.[< map_ $k_field _ $v_field >](),
unordered_elements_are![
eq((k, v)),
eq((k2, v2)),
]
);
assert_that!(
msg.[< map_ $k_field _ $v_field >]().keys().collect::<Vec<_>>(),
unordered_elements_are![eq(&k), eq(&k2)]
);
assert_that!(
msg.[< map_ $k_field _ $v_field >]().values().collect::<Vec<_>>(),
unordered_elements_are![eq(&v), eq(&v2)]
);
}
)* }
};
}
generate_map_primitives_tests!(
(i32, i32, int32, int32, 1, 1),
(i64, i64, int64, int64, 1, 1),
(u32, u32, uint32, uint32, 1, 1),
(u64, u64, uint64, uint64, 1, 1),
(i32, i32, sint32, sint32, 1, 1),
(i64, i64, sint64, sint64, 1, 1),
(u32, u32, fixed32, fixed32, 1, 1),
(u64, u64, fixed64, fixed64, 1, 1),
(i32, i32, sfixed32, sfixed32, 1, 1),
(i64, i64, sfixed64, sfixed64, 1, 1),
(i32, f32, int32, float, 1, 1.),
(i32, f64, int32, double, 1, 1.),
(bool, bool, bool, bool, true, true),
(i32, &[u8], int32, bytes, 1, b"foo"),
(i32, MapEnum, int32, enum, 1, MapEnum::Baz),
);
#[googletest::test]
fn collect_as_hashmap() {
// Highlights conversion from protobuf map to hashmap.
let mut msg = TestMap::new();
msg.map_string_string_mut().insert("hello", "world");
msg.map_string_string_mut().insert("fizz", "buzz");
msg.map_string_string_mut().insert("boo", "blah");
let hashmap: HashMap<String, String> =
msg.map_string_string().iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
assert_that!(
hashmap,
unordered_elements_are![
(eq("hello"), eq("world")),
(eq("fizz"), eq("buzz")),
(eq("boo"), eq("blah")),
]
);
}
#[googletest::test]
fn test_string_maps() {
let mut msg = TestMap::new();
msg.map_string_string_mut().insert("hello", "world");
msg.map_string_string_mut().insert("fizz", "buzz");
assert_that!(msg.map_string_string().len(), eq(2));
assert_that!(msg.map_string_string().get("fizz").unwrap(), eq("buzz"));
assert_that!(msg.map_string_string().get("not found"), eq(None));
msg.map_string_string_mut().clear();
assert_that!(msg.map_string_string().len(), eq(0));
}
#[googletest::test]
fn test_nested_enum_maps() {
// Verify that C++ thunks are generated and are with the right name for strings
TestMapWithNestedEnum::new()
.string_map_mut()
.insert("foo", test_map_with_nested_enum::inner_nested::NestedEnum::Foo);
}
#[googletest::test]
fn test_bytes_and_string_copied() {
let mut msg = TestMap::new();
{
// Ensure val is dropped after inserting into the map.
let mut key = String::from("hello");
let mut val = String::from("world");
msg.map_string_string_mut().insert(key.as_str(), &val);
msg.map_int32_bytes_mut().insert(1, val.as_bytes());
// Validate that map keys are copied by mutating the originals.
key.replace_range(.., "ayo");
val.replace_range(.., "wOrld");
}
assert_that!(msg.map_string_string_mut().get("hello").unwrap(), eq("world"));
assert_that!(msg.map_string_string(), unordered_elements_are![(eq("hello"), eq("world"))]);
assert_that!(msg.map_int32_bytes_mut().get(1).unwrap(), eq(b"world"));
}
#[googletest::test]
fn test_map_setter() {
// Set Map
{
let mut msg = TestMap::new();
let mut map = protobuf::Map::<ProtoString, ProtoString>::new();
map.as_mut().copy_from([("hello", "world"), ("fizz", "buzz")]);
msg.set_map_string_string(map);
assert_that!(
msg.map_string_string(),
unordered_elements_are![
eq(("hello".into(), "world".into())),
eq(("fizz".into(), "buzz".into()))
]
);
}
// Set MapView
{
let mut msg = TestMap::new();
let mut map = protobuf::Map::<ProtoString, ProtoString>::new();
map.as_mut().copy_from([("hello", "world"), ("fizz", "buzz")]);
msg.set_map_string_string(map.as_view());
assert_that!(
msg.map_string_string(),
unordered_elements_are![
eq(("hello".into(), "world".into())),
eq(("fizz".into(), "buzz".into()))
]
);
}
// Set MapMut
{
let mut msg = TestMap::new();
let mut map = protobuf::Map::<ProtoString, ProtoString>::new();
map.as_mut().copy_from([("hello", "world"), ("fizz", "buzz")]);
msg.set_map_string_string(map.as_mut());
assert_that!(
msg.map_string_string(),
unordered_elements_are![
eq(("hello".into(), "world".into())),
eq(("fizz".into(), "buzz".into()))
]
);
// The original map should remain unchanged.
assert_that!(
map.as_view(),
unordered_elements_are![
eq(("hello".into(), "world".into())),
eq(("fizz".into(), "buzz".into()))
]
);
}
}
Rust: cut down on the amount of generated C++ code needed for maps With the C++ kernel for Rust, we currently need to generate quite a few C++ thunks for operations on map fields. For each message we generate, we generate these thunks for all possible map types that could have that message as a value. These operations are for things such as insertion, removal, clearing, iterating, etc. The reason we do this is that templated types don't play well with FFI, so we effectively need separate FFI endpoints for every possible combination of key and value types used (or even potentially used) as a map field. This CL fixes the problem by replacing the generated thunks with functions in the runtime that can operate on `proto2::MessageLite*` without needing to care about the specific message type. The way it works is that we implement the operations using either `UntypedMapBase` (the base class of all map types, which knows nothing about the key and value types) or `KeyMapBase`, which knows the key type but not the value type. I roughly followed the example of the table-driven parser, which has a similar problem of needing to operate generically on maps without having access to the concrete types. I removed 54 thunks per message (that's 6 key types times 9 operations per key), but had to add two new thunks per message: - The `size_info` thunk looks up the `MapNodeSizeInfoT`, which is stored in a small constant table. The important thing here is an offset indicating where to look for the value in each map entry. This offset can be different for every pair of key and value types, but we can safely assume that the result does not depend on the signedness of the key. As a result we only need to store four entries per message: one each for i32, i64, bool, and string. - The `placement_new` thunk move-constructs a message in place. We need this to be able to efficiently implement map insertion. There are two big things that this CL does not address yet but which I plan to follow up on: - Enums still generate many map-related C++ thunks that could be replaced with a common implementation. This should actually be much easier to handle than messages, because every enum has the same representation as an i32. - We still generate six `ProxiedInMapValue` implementations for every message, but it should be possible to replace these with a blanket implementation that works for all message types. PiperOrigin-RevId: 657681421
4 months ago
#[test]
fn test_map_creation_with_message_values() {
// Maps are usually created and owned by a parent message, but let's verify that
// we can successfully create and destroy them independently.
macro_rules! test_for_each_key {
($($key_t:ty, $key:expr;)*) => {
$(
let msg = TestAllTypes::new();
let mut map = protobuf::Map::<$key_t, TestAllTypes>::new();
map.as_mut().insert($key, msg);
assert_that!(map.as_view().len(), eq(1));
)*
}
}
test_for_each_key!(
i32, -5;
u32, 13u32;
i64, 7;
u64, 11u64;
bool, false;
ProtoString, "looooooooooooooooooooooooong string";
);
}
#[test]
fn test_map_clearing_with_message_values() {
macro_rules! test_for_each_key {
($($key_t:ty, $key:expr;)*) => {
$(
let msg = TestAllTypes::new();
let mut map = protobuf::Map::<$key_t, TestAllTypes>::new();
map.as_mut().insert($key, msg);
assert_that!(map.as_view().len(), eq(1));
map.as_mut().clear();
assert_that!(map.as_view().len(), eq(0));
)*
}
}
test_for_each_key!(
i32, -5;
u32, 13u32;
i64, 7;
u64, 11u64;
bool, false;
ProtoString, "looooooooooooooooooooooooong string";
);
}
macro_rules! generate_map_with_msg_values_tests {
(
$(($k_field:ident, $k_nonzero:expr, $k_other:expr $(,)?)),*
$(,)?
) => {
paste! { $(
#[googletest::test]
fn [< test_map_ $k_field _all_types >]() {
// We need to cover the following upb/c++ thunks:
// TODO - b/323883851: Add test once Map::new is public.
// * new
// * free (covered implicitly by drop)
// * clear, size, insert, get, remove, iter, iter_next (all covered below)
let mut msg = TestMapWithMessages::new();
assert_that!(msg.[< map_ $k_field _all_types >]().len(), eq(0));
assert_that!(msg.[< map_ $k_field _all_types >]().get($k_nonzero), none());
// this block makes sure `insert` copies/moves, not borrows.
{
let mut msg_val = TestAllTypes::new();
msg_val.set_optional_int32(1001);
assert_that!(
msg
.[< map_ $k_field _all_types_mut >]()
.insert($k_nonzero, msg_val.as_view()),
eq(true),
"`insert` should return true when key was inserted."
);
assert_that!(
msg
.[< map_ $k_field _all_types_mut >]()
.insert($k_nonzero, msg_val.as_view()),
eq(false),
"`insert` should return false when key was already present."
);
}
assert_that!(
msg.[< map_ $k_field _all_types >]().len(),
eq(1),
"`size` thunk should return correct len.");
assert_that!(
msg.[< map_ $k_field _all_types >]().get($k_nonzero),
some(anything()),
"`get` should return Some when key present.");
assert_that!(
msg.[< map_ $k_field _all_types >]().get($k_nonzero).unwrap().optional_int32(),
eq(1001));
assert_that!(
msg.[< map_ $k_field _all_types >]().get($k_other),
none(),
"`get` should return None when key missing.");
msg.[< map_ $k_field _all_types_mut >]().clear();
assert_that!(
msg.[< map_ $k_field _all_types >]().len(),
eq(0),
"`clear` should drop all elements.");
assert_that!(
msg.[< map_ $k_field _all_types_mut >]().insert($k_nonzero, TestAllTypes::new()),
eq(true));
assert_that!(
msg.[< map_ $k_field _all_types_mut >]().remove($k_nonzero),
eq(true),
"`remove` should return true when key was present.");
assert_that!(msg.[< map_ $k_field _all_types >](), empty());
assert_that!(
msg.[< map_ $k_field _all_types_mut >]().remove($k_nonzero),
eq(false),
"`remove` should return false when key was missing.");
// empty iter
// assert_that!(
// msg.[< map_ $k_field _all_types_mut >]().iter().collect::<Vec<_>>(),
// elements_are![],
// "`iter` should work when empty."
// );
assert_that!(
msg.[< map_ $k_field _all_types_mut >]().keys().count(),
eq(0),
"`iter` should work when empty."
);
assert_that!(
msg.[< map_ $k_field _all_types_mut >]().values().count(),
eq(0),
"`iter` should work when empty."
);
// single element iter
assert_that!(
msg.[< map_ $k_field _all_types_mut >]().insert($k_nonzero, TestAllTypes::new()),
eq(true));
// assert_that!(
// msg.[< map_ $k_field _all_types >]().iter().collect::<Vec<_>>(),
// unordered_elements_are![
// eq(($k_nonzero, anything())),
// ]
// );
assert_that!(
msg.[< map_ $k_field _all_types >]().keys().collect::<Vec<_>>(),
unordered_elements_are![eq(&$k_nonzero)]
);
assert_that!(
msg.[< map_ $k_field _all_types >]().values().count(),
eq(1));
// 2 element iter
assert_that!(
msg
.[< map_ $k_field _all_types_mut >]()
.insert($k_other, TestAllTypes::new()),
eq(true));
assert_that!(
msg.[< map_ $k_field _all_types >](),
len(eq(2))
);
assert_that!(
msg.[< map_ $k_field _all_types >]().keys().collect::<Vec<_>>(),
unordered_elements_are![eq(&$k_nonzero), eq(&$k_other)]
);
assert_that!(
msg.[< map_ $k_field _all_types >]().values().count(),
eq(2)
);
}
)* }
}
}
generate_map_with_msg_values_tests!(
(int32, 1i32, 2i32),
(int64, 1i64, 2i64),
(uint32, 1u32, 2u32),
(uint64, 1u64, 2u64),
(sint32, 1, 2),
(sint64, 1, 2),
(fixed32, 1u32, 2u32),
(fixed64, 1u64, 2u64),
(sfixed32, 1, 2),
(sfixed64, 1, 2),
(bool, true, false),
(string, "foo", "bar"),
);