| // 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! { $( |
| #[gtest] |
| 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), |
| ); |
| |
| #[gtest] |
| 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")), |
| ] |
| ); |
| } |
| |
| #[gtest] |
| 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)); |
| } |
| |
| #[gtest] |
| 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); |
| } |
| |
| #[gtest] |
| 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")); |
| } |
| |
| #[gtest] |
| 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())) |
| ] |
| ); |
| } |
| } |
| |
| #[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! { $( |
| #[gtest] |
| 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"), |
| ); |