From cc51c9d4a71e0ecb048e08f220941eefc22d7d18 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Wed, 11 Sep 2019 19:35:31 -0700 Subject: [PATCH] WIP fixes to prost-derive, and porting of tests to alloc configuration --- .travis.yml | 1 + Cargo.toml | 3 +- prost-derive/Cargo.toml | 13 +- prost-derive/src/field/map.rs | 6 +- prost-derive/src/field/mod.rs | 18 +- prost-derive/src/field/oneof.rs | 2 +- prost-derive/src/field/scalar.rs | 66 ++--- prost-derive/src/lib.rs | 1 + protobuf/build.rs | 2 +- tests-2015/Cargo.toml | 1 + tests-alloc/Cargo.toml | 19 +- tests-alloc/src/bin.rs | 403 ++++++++++++++++++++++++++++ tests-alloc/src/build.rs | 110 ++++++++ tests-infra/Cargo.toml | 14 + tests-infra/src/lib.rs | 162 +++++++++++ tests/Cargo.toml | 1 + tests/src/build.rs | 11 +- tests/src/lib.rs | 133 +-------- tests/src/message_encoding_alloc.rs | 369 +++++++++++++++++++++++++ 19 files changed, 1146 insertions(+), 189 deletions(-) create mode 100644 tests-alloc/src/bin.rs create mode 100644 tests-alloc/src/build.rs create mode 100644 tests-infra/Cargo.toml create mode 100644 tests-infra/src/lib.rs create mode 100644 tests/src/message_encoding_alloc.rs diff --git a/.travis.yml b/.travis.yml index d9ada51ec..b1e813438 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ rust: script: - cargo build --verbose --all --exclude benchmarks - cargo test --verbose --all --exclude benchmarks + - cargo run -p tests-alloc --verbose - if [[ $TRAVIS_RUST_VERSION = nightly* ]]; then cargo bench --verbose --no-run; fi diff --git a/Cargo.toml b/Cargo.toml index 780fa414b..6a09a9b75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ exclude = [ [features] default = ["prost-derive", "std"] no-recursion-limit = [] -std = [] +std = ["prost-derive/std"] # When std is disabled, we attempt to provide no_std support in prost # Config::use_alloc_collections() should be set when using prost_build in your build.rs # so that generated files will not have std:: either, and will use alloc crate instead @@ -60,5 +60,6 @@ name = "varint" name = "benchmark" harness = false +# TODO: Remove this after bytes releases 0.5, and before we make a release [patch.crates-io] bytes = { git = "https://github.com/tokio-rs/bytes", rev = "c17e40115f5bb2a2db71ed90dceae6ec643dc024" } diff --git a/prost-derive/Cargo.toml b/prost-derive/Cargo.toml index 7b9125970..8cb7089fd 100644 --- a/prost-derive/Cargo.toml +++ b/prost-derive/Cargo.toml @@ -13,8 +13,19 @@ edition = "2018" [lib] proc_macro = true +[features] +std = [ "failure/std" ] +# std feature means that prost-derive attempts to use std collections +# +# When this feature is disabled, we attempt to use alloc. +# This should be similar to putting `config.use_alloc_collections` in a +# prost-build config +# +# Generated code assumes that user's crate has `extern crate alloc` somewhere +default = ["std"] + [dependencies] -failure = { version = "0.1", default-features = false, features = ["std"] } +failure = { version = "0.1", default-features = false } itertools = "0.8" proc-macro2 = "0.4.4" quote = "0.6.3" diff --git a/prost-derive/src/field/map.rs b/prost-derive/src/field/map.rs index ed8d3e9b3..6096d4feb 100644 --- a/prost-derive/src/field/map.rs +++ b/prost-derive/src/field/map.rs @@ -240,6 +240,8 @@ impl Field { /// The Debug tries to convert any enumerations met into the variants if possible, instead of /// outputting the raw numbers. pub fn debug(&self, wrapper_name: TokenStream) -> TokenStream { + let libname = super::collections_lib_name(); + let type_name = match self.map_ty { MapTy::HashMap => Ident::new("HashMap", Span::call_site()), MapTy::BTreeMap => Ident::new("BTreeMap", Span::call_site()), @@ -263,14 +265,14 @@ impl Field { ValueTy::Scalar(ref ty) => { let value = ty.rust_type(); quote! { - struct #wrapper_name<'a>(&'a ::alloc::collections::#type_name<#key, #value>); + struct #wrapper_name<'a>(&'a ::#libname::collections::#type_name<#key, #value>); impl<'a> ::core::fmt::Debug for #wrapper_name<'a> { #fmt } } } ValueTy::Message => quote! { - struct #wrapper_name<'a, V: 'a>(&'a ::alloc::collections::#type_name<#key, V>); + struct #wrapper_name<'a, V: 'a>(&'a ::#libname::collections::#type_name<#key, V>); impl<'a, V> ::core::fmt::Debug for #wrapper_name<'a, V> where V: ::core::fmt::Debug + 'a, diff --git a/prost-derive/src/field/mod.rs b/prost-derive/src/field/mod.rs index 0d362d241..f96c91382 100644 --- a/prost-derive/src/field/mod.rs +++ b/prost-derive/src/field/mod.rs @@ -4,11 +4,11 @@ mod message; mod oneof; mod scalar; -use std::fmt; -use std::slice; +use core::fmt; +use core::slice; use failure::{bail, Error}; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Attribute, Ident, Lit, LitBool, Meta, MetaList, MetaNameValue, NestedMeta}; @@ -135,7 +135,7 @@ impl Field { pub fn default(&self) -> TokenStream { match *self { Field::Scalar(ref scalar) => scalar.default(), - _ => quote!(::std::default::Default::default()), + _ => quote!(::core::default::Default::default()), } } @@ -364,3 +364,13 @@ fn tags_attr(attr: &Meta) -> Result>, Error> { _ => bail!("invalid tag attribute: {:?}", attr), } } + +// Helper which builds an identifier corresponding `std` or `alloc` depending +// on feature selection +fn collections_lib_name() -> Ident { + if cfg!(std) { + Ident::new("std", Span::call_site()) + } else { + Ident::new("alloc", Span::call_site()) + } +} diff --git a/prost-derive/src/field/oneof.rs b/prost-derive/src/field/oneof.rs index 893a1f4ee..45a72ce64 100644 --- a/prost-derive/src/field/oneof.rs +++ b/prost-derive/src/field/oneof.rs @@ -90,6 +90,6 @@ impl Field { } pub fn clear(&self, ident: TokenStream) -> TokenStream { - quote!(#ident = ::std::option::Option::None) + quote!(#ident = ::core::option::Option::None) } } diff --git a/prost-derive/src/field/scalar.rs b/prost-derive/src/field/scalar.rs index e59bf2df6..5015576a3 100644 --- a/prost-derive/src/field/scalar.rs +++ b/prost-derive/src/field/scalar.rs @@ -1,4 +1,4 @@ -use std::fmt; +use core::fmt; use failure::{bail, format_err, Error}; use proc_macro2::{Span, TokenStream}; @@ -131,7 +131,7 @@ impl Field { } } Kind::Optional(..) => quote! { - if let ::std::option::Option::Some(ref value) = #ident { + if let ::core::option::Option::Some(ref value) = #ident { #encode_fn(#tag, value, buf); } }, @@ -204,17 +204,18 @@ impl Field { _ => quote!(#ident = #default), } } - Kind::Optional(_) => quote!(#ident = ::std::option::Option::None), + Kind::Optional(_) => quote!(#ident = ::core::option::Option::None), Kind::Repeated | Kind::Packed => quote!(#ident.clear()), } } /// Returns an expression which evaluates to the default value of the field. pub fn default(&self) -> TokenStream { + let libname = super::collections_lib_name(); match self.kind { Kind::Plain(ref value) | Kind::Required(ref value) => value.owned(), - Kind::Optional(_) => quote!(::std::option::Option::None), - Kind::Repeated | Kind::Packed => quote!(::std::vec::Vec::new()), + Kind::Optional(_) => quote!(::core::option::Option::None), + Kind::Repeated | Kind::Packed => quote!(::#libname::vec::Vec::new()), } } @@ -223,11 +224,11 @@ impl Field { if let Ty::Enumeration(ref ty) = self.ty { quote! { struct #wrap_name<'a>(&'a i32); - impl<'a> ::std::fmt::Debug for #wrap_name<'a> { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + impl<'a> ::core::fmt::Debug for #wrap_name<'a> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { match #ty::from_i32(*self.0) { - None => ::std::fmt::Debug::fmt(&self.0, f), - Some(en) => ::std::fmt::Debug::fmt(&en, f), + None => ::core::fmt::Debug::fmt(&self.0, f), + Some(en) => ::core::fmt::Debug::fmt(&en, f), } } } @@ -242,23 +243,24 @@ impl Field { /// Returns a fragment for formatting the field `ident` in `Debug`. pub fn debug(&self, wrapper_name: TokenStream) -> TokenStream { let wrapper = self.debug_inner(quote!(Inner)); + let libname = super::collections_lib_name(); let inner_ty = self.ty.rust_type(); match self.kind { Kind::Plain(_) | Kind::Required(_) => self.debug_inner(wrapper_name), Kind::Optional(_) => quote! { - struct #wrapper_name<'a>(&'a ::std::option::Option<#inner_ty>); - impl<'a> ::std::fmt::Debug for #wrapper_name<'a> { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + struct #wrapper_name<'a>(&'a ::core::option::Option<#inner_ty>); + impl<'a> ::core::fmt::Debug for #wrapper_name<'a> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { #wrapper - ::std::fmt::Debug::fmt(&self.0.as_ref().map(Inner), f) + ::core::fmt::Debug::fmt(&self.0.as_ref().map(Inner), f) } } }, Kind::Repeated | Kind::Packed => { quote! { - struct #wrapper_name<'a>(&'a ::std::vec::Vec<#inner_ty>); - impl<'a> ::std::fmt::Debug for #wrapper_name<'a> { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + struct #wrapper_name<'a>(&'a ::#libname::vec::Vec<#inner_ty>); + impl<'a> ::core::fmt::Debug for #wrapper_name<'a> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { let mut vec_builder = f.debug_list(); for v in self.0 { #wrapper @@ -300,13 +302,13 @@ impl Field { } pub fn #set(&mut self, value: #ty) { - self.#ident = ::std::option::Option::Some(value as i32); + self.#ident = ::core::option::Option::Some(value as i32); } } } Kind::Repeated | Kind::Packed => { quote! { - pub fn #ident(&self) -> ::std::iter::FilterMap<::std::iter::Cloned<::std::slice::Iter>, + pub fn #ident(&self) -> ::core::iter::FilterMap<::core::iter::Cloned<::core::slice::Iter>, fn(i32) -> Option<#ty>> { self.#ident.iter().cloned().filter_map(#ty::from_i32) } @@ -320,16 +322,16 @@ impl Field { let ty = self.ty.rust_ref_type(); let match_some = if self.ty.is_numeric() { - quote!(::std::option::Option::Some(val) => val,) + quote!(::core::option::Option::Some(val) => val,) } else { - quote!(::std::option::Option::Some(ref val) => &val[..],) + quote!(::core::option::Option::Some(ref val) => &val[..],) }; Some(quote! { pub fn #ident(&self) -> #ty { match self.#ident { #match_some - ::std::option::Option::None => #default, + ::core::option::Option::None => #default, } } }) @@ -465,9 +467,10 @@ impl Ty { // TODO: rename to 'owned_type'. pub fn rust_type(&self) -> TokenStream { + let libname = super::collections_lib_name(); match *self { - Ty::String => quote!(::std::string::String), - Ty::Bytes => quote!(::std::vec::Vec), + Ty::String => quote!(::#libname::string::String), + Ty::Bytes => quote!(::#libname::vec::Vec), _ => self.rust_ref_type(), } } @@ -628,16 +631,16 @@ impl DefaultValue { match value { "inf" => { return Ok(DefaultValue::Path(parse_str::( - "::std::f32::INFINITY", + "::core::f32::INFINITY", )?)); } "-inf" => { return Ok(DefaultValue::Path(parse_str::( - "::std::f32::NEG_INFINITY", + "::core::f32::NEG_INFINITY", )?)); } "nan" => { - return Ok(DefaultValue::Path(parse_str::("::std::f32::NAN")?)); + return Ok(DefaultValue::Path(parse_str::("::core::f32::NAN")?)); } _ => (), } @@ -646,16 +649,16 @@ impl DefaultValue { match value { "inf" => { return Ok(DefaultValue::Path(parse_str::( - "::std::f64::INFINITY", + "::core::f64::INFINITY", )?)); } "-inf" => { return Ok(DefaultValue::Path(parse_str::( - "::std::f64::NEG_INFINITY", + "::core::f64::NEG_INFINITY", )?)); } "nan" => { - return Ok(DefaultValue::Path(parse_str::("::std::f64::NAN")?)); + return Ok(DefaultValue::Path(parse_str::("::core::f64::NAN")?)); } _ => (), } @@ -745,12 +748,13 @@ impl DefaultValue { } pub fn owned(&self) -> TokenStream { + let libname = super::collections_lib_name(); match *self { DefaultValue::String(ref value) if value.is_empty() => { - quote!(::std::string::String::new()) + quote!(::#libname::string::String::new()) } DefaultValue::String(ref value) => quote!(#value.to_owned()), - DefaultValue::Bytes(ref value) if value.is_empty() => quote!(::std::vec::Vec::new()), + DefaultValue::Bytes(ref value) if value.is_empty() => quote!(::#libname::vec::Vec::new()), DefaultValue::Bytes(ref value) => { let lit = LitByteStr::new(value, Span::call_site()); quote!(#lit.to_owned()) diff --git a/prost-derive/src/lib.rs b/prost-derive/src/lib.rs index 609cf07c7..646534a46 100644 --- a/prost-derive/src/lib.rs +++ b/prost-derive/src/lib.rs @@ -3,6 +3,7 @@ #![recursion_limit = "4096"] extern crate proc_macro; +extern crate alloc; use failure::bail; use quote::quote; diff --git a/protobuf/build.rs b/protobuf/build.rs index 158c1a9e7..8365f1f86 100644 --- a/protobuf/build.rs +++ b/protobuf/build.rs @@ -174,7 +174,7 @@ fn install_conformance_test_runner(src_dir: &Path, prefix_dir: &Path) { let rc = Command::new("make") .arg("-j") - .arg(&num_jobs) + .arg("1") // Note: 1 instead of NUM_JOBS to work around bugs in makefile .arg("install") .current_dir(src_dir.join("conformance")) .status() diff --git a/tests-2015/Cargo.toml b/tests-2015/Cargo.toml index e0f6eea6d..7491f7343 100644 --- a/tests-2015/Cargo.toml +++ b/tests-2015/Cargo.toml @@ -21,6 +21,7 @@ cfg-if = "0.1" prost = { path = ".." } prost-types = { path = "../prost-types" } protobuf = { path = "../protobuf" } +tests-infra = { path = "../tests-infra" } [dev-dependencies] diff = "0.1" diff --git a/tests-alloc/Cargo.toml b/tests-alloc/Cargo.toml index 453ea5380..87ed5ff82 100644 --- a/tests-alloc/Cargo.toml +++ b/tests-alloc/Cargo.toml @@ -5,21 +5,20 @@ authors = ["Dan Burkert "] publish = false edition = "2018" -build = "../tests/src/build.rs" +build = "src/build.rs" -[lib] -doctest = false -path = "../tests/src/lib.rs" - -[features] -default = ["nostd-collections"] -nostd-collections = [] +# The standard libtest relies on std, so if we want to test no_std builds, +# we should make a binary and not use #[test] +[[bin]] +name = "tests-alloc" +path = "src/bin.rs" [dependencies] bytes = { version = "0.5", default-features = false } cfg-if = "0.1" -prost = { path = ".." } -prost-types = { path = "../prost-types" } +prost = { path = "..", default-features = false } +prost-derive = { path = "../prost-derive", default-features = false } +prost-types = { path = "../prost-types", default-features = false } protobuf = { path = "../protobuf" } [dev-dependencies] diff --git a/tests-alloc/src/bin.rs b/tests-alloc/src/bin.rs new file mode 100644 index 000000000..5067f9b6c --- /dev/null +++ b/tests-alloc/src/bin.rs @@ -0,0 +1,403 @@ +#![no_std] + +extern crate alloc; + +extern crate tests_infra; + +pub mod foo { + pub mod bar_baz { + include!(concat!(env!("OUT_DIR"), "/foo.bar_baz.rs")); + } +} + +pub mod nesting { + include!(concat!(env!("OUT_DIR"), "/nesting.rs")); +} + +pub mod recursive_oneof { + include!(concat!(env!("OUT_DIR"), "/recursive_oneof.rs")); +} + +/// This tests the custom attributes support by abusing docs. +/// +/// Docs really are full-blown attributes. So we use them to ensure we can place them on everything +/// we need. If they aren't put onto something or allowed not to be there (by the generator), +/// compilation fails. +#[deny(missing_docs)] +pub mod custom_attributes { + include!(concat!(env!("OUT_DIR"), "/foo.custom.attrs.rs")); +} + +/// Also for testing custom attributes, but on oneofs. +/// +/// Unfortunately, an OneOf field generates a companion module in the .rs file. There's no +/// reasonable way to place a doc comment on that, so we do the test with `derive(Ord)` and have it +/// in a separate file. +pub mod oneof_attributes { + include!(concat!(env!("OUT_DIR"), "/foo.custom.one_of_attrs.rs")); +} + +/// Issue https://github.com/danburkert/prost/issues/118 +/// +/// When a message contains an enum field with a default value, we +/// must ensure that the appropriate name conventions are used. +pub mod default_enum_value { + include!(concat!(env!("OUT_DIR"), "/default_enum_value.rs")); +} + +pub mod groups { + include!(concat!(env!("OUT_DIR"), "/groups.rs")); +} + +use tests_infra::*; + +use alloc::collections::{BTreeMap, BTreeSet}; + +use protobuf::test_messages::proto3::TestAllTypesProto3; + +// Tests + + fn test_all_types_proto3() { + // Some selected encoded messages, mostly collected from failed fuzz runs. + let msgs: &[&[u8]] = &[ + &[0x28, 0x28, 0x28, 0xFF, 0xFF, 0xFF, 0xFF, 0x68], + &[0x92, 0x01, 0x00, 0x92, 0xF4, 0x01, 0x02, 0x00, 0x00], + &[0x5d, 0xff, 0xff, 0xff, 0xff, 0x28, 0xff, 0xff, 0x21], + &[0x98, 0x04, 0x02, 0x08, 0x0B, 0x98, 0x04, 0x02, 0x08, 0x02], + // optional_int32: -1 + &[0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08], + // repeated_bool: [true, true] + &[0xDA, 0x02, 0x02, 0x2A, 0x03], + // oneof_double: nan + &[0xb1, 0x07, 0xf6, 0x3d, 0xf5, 0xff, 0x27, 0x3d, 0xf5, 0xff], + // optional_float: -0.0 + &[0xdd, 0x00, 0x00, 0x00, 0x00, 0x80], + // optional_value: nan + &[ + 0xE2, 0x13, 0x1B, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x08, 0xFF, 0x0E, + ], + ]; + + for msg in msgs { + roundtrip::(msg).unwrap(); + } + } + + fn test_ident_conversions() { + let msg = foo::bar_baz::FooBarBaz { + foo_bar_baz: 42, + fuzz_busters: vec![foo::bar_baz::foo_bar_baz::FuzzBuster { + t: BTreeMap::::new(), + nested_self: None, + }], + p_i_e: 0, + r#as: 4, + r#break: 5, + r#const: 6, + r#continue: 7, + r#else: 8, + r#enum: 9, + r#false: 10, + r#fn: 11, + r#for: 12, + r#if: 13, + r#impl: 14, + r#in: 15, + r#let: 16, + r#loop: 17, + r#match: 18, + r#mod: 19, + r#move: 20, + r#mut: 21, + r#pub: 22, + r#ref: 23, + r#return: 24, + r#static: 25, + r#struct: 26, + r#trait: 27, + r#true: 28, + r#type: 29, + r#unsafe: 30, + r#use: 31, + r#where: 32, + r#while: 33, + r#dyn: 34, + r#abstract: 35, + r#become: 36, + r#box: 37, + r#do: 38, + r#final: 39, + r#macro: 40, + r#override: 41, + r#priv: 42, + r#typeof: 43, + r#unsized: 44, + r#virtual: 45, + r#yield: 46, + r#async: 47, + r#await: 48, + r#try: 49, + self_: 50, + super_: 51, + extern_: 52, + crate_: 53, + }; + + let _ = foo::bar_baz::foo_bar_baz::Self_ {}; + + // Test enum ident conversion. + let _ = foo::bar_baz::foo_bar_baz::StrawberryRhubarbPie::Foo; + let _ = foo::bar_baz::foo_bar_baz::StrawberryRhubarbPie::Bar; + let _ = foo::bar_baz::foo_bar_baz::StrawberryRhubarbPie::FooBar; + let _ = foo::bar_baz::foo_bar_baz::StrawberryRhubarbPie::FuzzBuster; + let _ = foo::bar_baz::foo_bar_baz::StrawberryRhubarbPie::NormalRustEnumCase; + + let mut buf = Vec::new(); + msg.encode(&mut buf).expect("encode"); + roundtrip::(&buf).unwrap(); + } + + fn test_custom_type_attributes() { + // We abuse the ident conversion protobuf for the custom attribute additions. We placed + // `Ord` on the FooBarBaz (which is not implemented by ordinary messages). + let mut set1 = BTreeSet::new(); + let msg1 = foo::bar_baz::FooBarBaz::default(); + set1.insert(msg1); + // Similar, but for oneof fields + let mut set2 = BTreeSet::new(); + let msg2 = oneof_attributes::Msg::default(); + set2.insert(msg2.field); + } + + fn test_nesting() { + use crate::nesting::{A, B}; + let _ = A { + a: Some(Box::new(A::default())), + repeated_a: Vec::::new(), + map_a: BTreeMap::::new(), + b: Some(Box::new(B::default())), + repeated_b: Vec::::new(), + map_b: BTreeMap::::new(), + }; + } + + fn test_deep_nesting() { + fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> { + use crate::nesting::A; + + let mut a = Box::new(A::default()); + for _ in 0..depth { + let mut next = Box::new(A::default()); + next.a = Some(a); + a = next; + } + + let mut buf = Vec::new(); + a.encode(&mut buf).unwrap(); + A::decode(buf).map(|_| ()) + } + + assert!(build_and_roundtrip(100).is_ok()); + assert!(build_and_roundtrip(101).is_err()); + } + + fn test_deep_nesting_oneof() { + fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> { + use crate::recursive_oneof::{a, A, C}; + + let mut a = Box::new(A { + kind: Some(a::Kind::C(C {})), + }); + for _ in 0..depth { + a = Box::new(A { + kind: Some(a::Kind::A(a)), + }); + } + + let mut buf = Vec::new(); + a.encode(&mut buf).unwrap(); + A::decode(buf).map(|_| ()) + } + + assert!(build_and_roundtrip(99).is_ok()); + assert!(build_and_roundtrip(100).is_err()); + } + + fn test_deep_nesting_group() { + fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> { + use crate::groups::{nested_group2::OptionalGroup, NestedGroup2}; + + let mut a = NestedGroup2::default(); + for _ in 0..depth { + a = NestedGroup2 { + optionalgroup: Some(Box::new(OptionalGroup { + nested_group: Some(a), + })), + }; + } + + let mut buf = Vec::new(); + a.encode(&mut buf).unwrap(); + NestedGroup2::decode(buf).map(|_| ()) + } + + assert!(build_and_roundtrip(50).is_ok()); + assert!(build_and_roundtrip(51).is_err()); + } + + fn test_deep_nesting_repeated() { + fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> { + use crate::nesting::C; + + let mut c = C::default(); + for _ in 0..depth { + let mut next = C::default(); + next.r.push(c); + c = next; + } + + let mut buf = Vec::new(); + c.encode(&mut buf).unwrap(); + C::decode(buf).map(|_| ()) + } + + assert!(build_and_roundtrip(100).is_ok()); + assert!(build_and_roundtrip(101).is_err()); + } + + fn test_deep_nesting_map() { + fn build_and_roundtrip(depth: usize) -> Result<(), prost::DecodeError> { + use crate::nesting::D; + + let mut d = D::default(); + for _ in 0..depth { + let mut next = D::default(); + next.m.insert("foo".to_owned(), d); + d = next; + } + + let mut buf = Vec::new(); + d.encode(&mut buf).unwrap(); + D::decode(buf).map(|_| ()) + } + + assert!(build_and_roundtrip(50).is_ok()); + assert!(build_and_roundtrip(51).is_err()); + } + + fn test_recursive_oneof() { + use crate::recursive_oneof::{a, A, B, C}; + let _ = A { + kind: Some(a::Kind::B(Box::new(B { + a: Some(Box::new(A { + kind: Some(a::Kind::C(C {})), + })), + }))), + }; + } + + fn test_default_enum() { + let msg = default_enum_value::Test::default(); + assert_eq!(msg.privacy_level_1(), default_enum_value::PrivacyLevel::One); + assert_eq!( + msg.privacy_level_3(), + default_enum_value::PrivacyLevel::PrivacyLevelThree + ); + assert_eq!( + msg.privacy_level_4(), + default_enum_value::PrivacyLevel::PrivacyLevelprivacyLevelFour + ); + } + + fn test_group() { + // optional group + let msg1_bytes = &[0x0B, 0x10, 0x20, 0x0C]; + + let msg1 = groups::Test1 { + groupa: Some(groups::test1::GroupA { i2: Some(32) }), + }; + + let mut bytes = Vec::new(); + msg1.encode(&mut bytes).unwrap(); + assert_eq!(&bytes, msg1_bytes); + + // skip group while decoding + let data: &[u8] = &[ + 0x0B, // start group (tag=1) + 0x30, 0x01, // unused int32 (tag=6) + 0x2B, 0x30, 0xFF, 0x01, 0x2C, // unused group (tag=5) + 0x10, 0x20, // int32 (tag=2) + 0x0C, // end group (tag=1) + ]; + assert_eq!(groups::Test1::decode(data), Ok(msg1)); + + // repeated group + let msg2_bytes: &[u8] = &[ + 0x20, 0x40, 0x2B, 0x30, 0xFF, 0x01, 0x2C, 0x2B, 0x30, 0x01, 0x2C, 0x38, 0x64, + ]; + + let msg2 = groups::Test2 { + i14: Some(64), + groupb: vec![ + groups::test2::GroupB { i16: Some(255) }, + groups::test2::GroupB { i16: Some(1) }, + ], + i17: Some(100), + }; + + let mut bytes = Vec::new(); + msg2.encode(&mut bytes).unwrap(); + assert_eq!(&*bytes, msg2_bytes); + + assert_eq!(groups::Test2::decode(msg2_bytes), Ok(msg2)); + } + + fn test_group_oneof() { + let msg = groups::OneofGroup { + i1: Some(42), + field: Some(groups::oneof_group::Field::S2("foo".to_string())), + }; + check_message(&msg); + + let msg = groups::OneofGroup { + i1: Some(42), + field: Some(groups::oneof_group::Field::G(groups::oneof_group::G { + i2: None, + s1: "foo".to_string(), + t1: None, + })), + }; + check_message(&msg); + + let msg = groups::OneofGroup { + i1: Some(42), + field: Some(groups::oneof_group::Field::G(groups::oneof_group::G { + i2: Some(99), + s1: "foo".to_string(), + t1: Some(groups::Test1 { + groupa: Some(groups::test1::GroupA { i2: None }), + }), + })), + }; + check_message(&msg); + + check_message(&groups::OneofGroup::default()); + } + +fn main() { + test_all_types_proto3(); + test_ident_conversions(); + test_custom_type_attributes(); + test_nesting(); + test_deep_nesting(); + test_deep_nesting_oneof(); + test_deep_nesting_group(); + test_deep_nesting_repeated(); + test_deep_nesting_map(); + test_recursive_oneof(); + test_default_enum(); + test_group(); + test_group_oneof(); +} diff --git a/tests-alloc/src/build.rs b/tests-alloc/src/build.rs new file mode 100644 index 000000000..c8e39abb9 --- /dev/null +++ b/tests-alloc/src/build.rs @@ -0,0 +1,110 @@ +#[macro_use] +extern crate cfg_if; + +use std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + let _ = env_logger::init(); + + // The source directory. + let src = PathBuf::from("../tests/src"); + let includes = &[src.clone()]; + + let mut config = prost_build::Config::new(); + + // Generate BTreeMap fields for all messages. This forces encoded output to be consistent, so + // that encode/decode roundtrips can use encoded output for comparison. Otherwise trying to + // compare based on the Rust PartialEq implementations is difficult, due to presence of NaN + // values. + // + // Note nostd collections implies Btree everywhere anyways + config.use_alloc_collections_lib(); + + // Tests for custom attributes + config.type_attribute("Foo.Bar_Baz.Foo_barBaz", "#[derive(Eq, PartialOrd, Ord)]"); + config.type_attribute( + "Foo.Bar_Baz.Foo_barBaz.fuzz_buster", + "#[derive(Eq, PartialOrd, Ord)]", + ); + config.type_attribute("Foo.Custom.Attrs.Msg", "#[allow(missing_docs)]"); + config.type_attribute("Foo.Custom.Attrs.Msg.field", "/// Oneof docs"); + config.type_attribute("Foo.Custom.Attrs.AnEnum", "#[allow(missing_docs)]"); + config.type_attribute("Foo.Custom.Attrs.AnotherEnum", "/// Oneof docs"); + config.type_attribute( + "Foo.Custom.OneOfAttrs.Msg.field", + "#[derive(Eq, PartialOrd, Ord)]", + ); + config.field_attribute("Foo.Custom.Attrs.AnotherEnum.C", "/// The C docs"); + config.field_attribute("Foo.Custom.Attrs.AnotherEnum.D", "/// The D docs"); + config.field_attribute("Foo.Custom.Attrs.Msg.field.a", "/// Oneof A docs"); + config.field_attribute("Foo.Custom.Attrs.Msg.field.b", "/// Oneof B docs"); + + config + .compile_protos(&[src.join("ident_conversion.proto")], includes) + .unwrap(); + + config + .compile_protos(&[src.join("nesting.proto")], includes) + .unwrap(); + + config + .compile_protos(&[src.join("recursive_oneof.proto")], includes) + .unwrap(); + + config + .compile_protos(&[src.join("custom_attributes.proto")], includes) + .unwrap(); + + config + .compile_protos(&[src.join("oneof_attributes.proto")], includes) + .unwrap(); + + config + .compile_protos(&[src.join("no_unused_results.proto")], includes) + .unwrap(); + + config + .compile_protos(&[src.join("default_enum_value.proto")], includes) + .unwrap(); + + config + .compile_protos(&[src.join("groups.proto")], includes) + .unwrap(); + + config + .compile_protos(&[src.join("well_known_types.proto")], includes) + .unwrap(); + + config + .compile_protos( + &[src.join("packages/widget_factory.proto")], + &[src.join("packages")], + ) + .unwrap(); + + let out_dir = + &PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable not set")) + .join("extern_paths"); + fs::create_dir_all(out_dir).expect("failed to create prefix directory"); + config.out_dir(out_dir); + + // Compile some of the module examples as an extern path. The extern path syntax is edition + // specific, since the way crate-internal fully qualified paths has changed. + cfg_if! { + if #[cfg(feature = "edition-2015")] { + const EXTERN_PATH: &str = "::packages::gizmo"; + } else { + const EXTERN_PATH: &str = "crate::packages::gizmo"; + } + }; + config.extern_path(".packages.gizmo", EXTERN_PATH); + + config + .compile_protos( + &[src.join("packages").join("widget_factory.proto")], + &[src.join("packages")], + ) + .unwrap(); +} diff --git a/tests-infra/Cargo.toml b/tests-infra/Cargo.toml new file mode 100644 index 000000000..81c097b5b --- /dev/null +++ b/tests-infra/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tests-infra" +version = "0.0.0" +authors = ["Dan Burkert "] +publish = false +edition = "2018" + +[lib] +doctest = false + +[dependencies] +bytes = "0.5" +cfg-if = "0.1" +prost = { path = "..", default-features = false } diff --git a/tests-infra/src/lib.rs b/tests-infra/src/lib.rs new file mode 100644 index 000000000..d652628fe --- /dev/null +++ b/tests-infra/src/lib.rs @@ -0,0 +1,162 @@ +#![no_std] + +extern crate alloc; + +use alloc::string::String; +use alloc::vec::Vec; +use alloc::format; + +use core::default::Default; +use core::cmp::PartialEq; +use core::fmt::Display; + +#[macro_use] +extern crate cfg_if; + +use prost; +use prost::Message; + +cfg_if! { + if #[cfg(feature = "edition-2015")] { + extern crate bytes; + extern crate prost; + extern crate prost_types; + extern crate protobuf; + #[cfg(test)] + extern crate prost_build; + #[cfg(test)] + extern crate tempfile; + #[cfg(test)] + extern crate tests_infra; + } +} + +pub enum RoundtripResult { + /// The roundtrip succeeded. + Ok(Vec), + /// The data could not be decoded. This could indicate a bug in prost, + /// or it could indicate that the input was bogus. + DecodeError(prost::DecodeError), + /// Re-encoding or validating the data failed. This indicates a bug in `prost`. + Error(String), +} + +impl RoundtripResult { + /// Unwrap the roundtrip result. + pub fn unwrap(self) -> Vec { + match self { + RoundtripResult::Ok(buf) => buf, + RoundtripResult::DecodeError(error) => { + panic!("failed to decode the roundtrip data: {}", error) + } + RoundtripResult::Error(error) => panic!("failed roundtrip: {}", error), + } + } + + /// Unwrap the roundtrip result. Panics if the result was a validation or re-encoding error. + pub fn unwrap_error(self) -> Result, prost::DecodeError> { + match self { + RoundtripResult::Ok(buf) => Ok(buf), + RoundtripResult::DecodeError(error) => Err(error), + RoundtripResult::Error(error) => panic!("failed roundtrip: {}", error), + } + } +} + +/// Tests round-tripping a message type. The message should be compiled with `BTreeMap` fields, +/// otherwise the comparison may fail due to inconsistent `HashMap` entry encoding ordering. +pub fn roundtrip(data: &[u8]) -> RoundtripResult +where + M: Message + Default, +{ + // Try to decode a message from the data. If decoding fails, continue. + let all_types = match M::decode(data) { + Ok(all_types) => all_types, + Err(error) => return RoundtripResult::DecodeError(error), + }; + + let encoded_len = all_types.encoded_len(); + + // TODO: Reenable this once sign-extension in negative int32s is figured out. + // assert!(encoded_len <= data.len(), "encoded_len: {}, len: {}, all_types: {:?}", + // encoded_len, data.len(), all_types); + + let mut buf1 = Vec::new(); + if let Err(error) = all_types.encode(&mut buf1) { + return RoundtripResult::Error(to_string(&error)); + } + if encoded_len != buf1.len() { + return RoundtripResult::Error( + format!( + "expected encoded len ({}) did not match actual encoded len ({})", + encoded_len, + buf1.len() + ) + .into(), + ); + } + + let roundtrip = match M::decode(&buf1[..]) { + Ok(roundtrip) => roundtrip, + Err(error) => return RoundtripResult::Error(to_string(&error)), + }; + + let mut buf2 = Vec::new(); + if let Err(error) = roundtrip.encode(&mut buf2) { + return RoundtripResult::Error(to_string(&error)); + } + + /* + // Useful for debugging: + eprintln!(" data: {:?}", data.iter().map(|x| format!("0x{:x}", x)).collect::>()); + eprintln!(" buf1: {:?}", buf1.iter().map(|x| format!("0x{:x}", x)).collect::>()); + eprintln!("a: {:?}\nb: {:?}", all_types, roundtrip); + */ + + if buf1 != buf2 { + return RoundtripResult::Error("roundtripped encoded buffers do not match".into()); + } + + RoundtripResult::Ok(buf1) +} + +/// Generic rountrip serialization check for messages. +pub fn check_message(msg: &M) +where + M: Message + Default + PartialEq, +{ + let expected_len = msg.encoded_len(); + + let mut buf = Vec::with_capacity(18); + msg.encode(&mut buf).unwrap(); + assert_eq!(expected_len, buf.len()); + + use bytes::Buf; + let buf = (&buf[..]).to_bytes(); + let roundtrip = M::decode(buf).unwrap(); + + // FIXME(chris) + // if buf.has_remaining() { + // panic!("expected buffer to be empty: {}", buf.remaining()); + // } + + assert_eq!(msg, &roundtrip); +} + +/// Serialize from A should equal Serialize from B +pub fn check_serialize_equivalent(msg_a: &M, msg_b: &N) +where + M: Message + Default + PartialEq, + N: Message + Default + PartialEq, +{ + let mut buf_a = Vec::new(); + msg_a.encode(&mut buf_a).unwrap(); + let mut buf_b = Vec::new(); + msg_b.encode(&mut buf_b).unwrap(); + assert_eq!(buf_a, buf_b); +} + +// helper +fn to_string(obj: &T) -> String { + format!("{}", obj) +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index b8580c292..743cf2b4b 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -16,6 +16,7 @@ cfg-if = "0.1" prost = { path = ".." } prost-types = { path = "../prost-types" } protobuf = { path = "../protobuf" } +tests-infra = { path = "../tests-infra" } [dev-dependencies] diff = "0.1" diff --git a/tests/src/build.rs b/tests/src/build.rs index a0f2a6869..3451034fe 100644 --- a/tests/src/build.rs +++ b/tests/src/build.rs @@ -26,15 +26,8 @@ fn main() { // that encode/decode roundtrips can use encoded output for comparison. Otherwise trying to // compare based on the Rust PartialEq implementations is difficult, due to presence of NaN // values. - // - // Note nostd collections implies Btree everywhere anyways - cfg_if! { - if #[cfg(feature = "nostd-collections")] { - config.use_alloc_collections_lib(); - } else { - config.btree_map(&["."]); - } - } + config.btree_map(&["."]); + // Tests for custom attributes config.type_attribute("Foo.Bar_Baz.Foo_barBaz", "#[derive(Eq, PartialOrd, Ord)]"); config.type_attribute( diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 5ccc7b8e5..e67c02756 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,6 +1,8 @@ #[macro_use] extern crate cfg_if; +extern crate alloc; + cfg_if! { if #[cfg(feature = "edition-2015")] { extern crate bytes; @@ -11,6 +13,8 @@ cfg_if! { extern crate prost_build; #[cfg(test)] extern crate tempfile; + #[cfg(test)] + extern crate tests_infra; } } @@ -74,135 +78,6 @@ pub mod groups { include!(concat!(env!("OUT_DIR"), "/groups.rs")); } -use std::error::Error; - -use bytes::{Buf}; - -use prost::Message; - -pub enum RoundtripResult { - /// The roundtrip succeeded. - Ok(Vec), - /// The data could not be decoded. This could indicate a bug in prost, - /// or it could indicate that the input was bogus. - DecodeError(prost::DecodeError), - /// Re-encoding or validating the data failed. This indicates a bug in `prost`. - Error(Box), -} - -impl RoundtripResult { - /// Unwrap the roundtrip result. - pub fn unwrap(self) -> Vec { - match self { - RoundtripResult::Ok(buf) => buf, - RoundtripResult::DecodeError(error) => { - panic!("failed to decode the roundtrip data: {}", error) - } - RoundtripResult::Error(error) => panic!("failed roundtrip: {}", error), - } - } - - /// Unwrap the roundtrip result. Panics if the result was a validation or re-encoding error. - pub fn unwrap_error(self) -> Result, prost::DecodeError> { - match self { - RoundtripResult::Ok(buf) => Ok(buf), - RoundtripResult::DecodeError(error) => Err(error), - RoundtripResult::Error(error) => panic!("failed roundtrip: {}", error), - } - } -} - -/// Tests round-tripping a message type. The message should be compiled with `BTreeMap` fields, -/// otherwise the comparison may fail due to inconsistent `HashMap` entry encoding ordering. -pub fn roundtrip(data: &[u8]) -> RoundtripResult -where - M: Message + Default, -{ - // Try to decode a message from the data. If decoding fails, continue. - let all_types = match M::decode(data) { - Ok(all_types) => all_types, - Err(error) => return RoundtripResult::DecodeError(error), - }; - - let encoded_len = all_types.encoded_len(); - - // TODO: Reenable this once sign-extension in negative int32s is figured out. - // assert!(encoded_len <= data.len(), "encoded_len: {}, len: {}, all_types: {:?}", - // encoded_len, data.len(), all_types); - - let mut buf1 = Vec::new(); - if let Err(error) = all_types.encode(&mut buf1) { - return RoundtripResult::Error(error.into()); - } - if encoded_len != buf1.len() { - return RoundtripResult::Error( - format!( - "expected encoded len ({}) did not match actual encoded len ({})", - encoded_len, - buf1.len() - ) - .into(), - ); - } - - let roundtrip = match M::decode(&buf1) { - Ok(roundtrip) => roundtrip, - Err(error) => return RoundtripResult::Error(error.into()), - }; - - let mut buf2 = Vec::new(); - if let Err(error) = roundtrip.encode(&mut buf2) { - return RoundtripResult::Error(error.into()); - } - - /* - // Useful for debugging: - eprintln!(" data: {:?}", data.iter().map(|x| format!("0x{:x}", x)).collect::>()); - eprintln!(" buf1: {:?}", buf1.iter().map(|x| format!("0x{:x}", x)).collect::>()); - eprintln!("a: {:?}\nb: {:?}", all_types, roundtrip); - */ - - if buf1 != buf2 { - return RoundtripResult::Error("roundtripped encoded buffers do not match".into()); - } - - RoundtripResult::Ok(buf1) -} - -/// Generic rountrip serialization check for messages. -pub fn check_message(msg: &M) -where - M: Message + Default + PartialEq, -{ - let expected_len = msg.encoded_len(); - - let mut buf = Vec::with_capacity(18); - msg.encode(&mut buf).unwrap(); - assert_eq!(expected_len, buf.len()); - - let mut buf = buf.into_buf(); - let roundtrip = M::decode(&mut buf).unwrap(); - - if buf.has_remaining() { - panic!(format!("expected buffer to be empty: {}", buf.remaining())); - } - - assert_eq!(msg, &roundtrip); -} - -/// Serialize from A should equal Serialize from B -pub fn check_serialize_equivalent(msg_a: &M, msg_b: &N) -where - M: Message + Default + PartialEq, - N: Message + Default + PartialEq, -{ - let mut buf_a = Vec::new(); - msg_a.encode(&mut buf_a).unwrap(); - let mut buf_b = Vec::new(); - msg_b.encode(&mut buf_b).unwrap(); - assert_eq!(buf_a, buf_b); -} - #[cfg(test)] mod tests { diff --git a/tests/src/message_encoding_alloc.rs b/tests/src/message_encoding_alloc.rs new file mode 100644 index 000000000..2d0c34b5f --- /dev/null +++ b/tests/src/message_encoding_alloc.rs @@ -0,0 +1,369 @@ +// This is the same as message_encoding.rs but all HashMap have been replaced +// with BTreeMap and items grabbed from alloc namespace + +use prost::{Enumeration, Message, Oneof}; + +use crate::check_message; +use crate::check_serialize_equivalent; + +#[derive(Clone, PartialEq, Message)] +pub struct RepeatedFloats { + #[prost(float, tag = "11")] + pub single_float: f32, + #[prost(float, repeated, packed = "true", tag = "41")] + pub repeated_float: Vec, +} + +#[test] +fn check_repeated_floats() { + check_message(&RepeatedFloats { + single_float: 0.0, + repeated_float: vec![ + 0.1, + 340282300000000000000000000000000000000.0, + 0.000000000000000000000000000000000000011754944, + ], + }); +} + +#[test] +fn check_scalar_types() { + check_message(&ScalarTypes::default()); +} + +/// A protobuf message which contains all scalar types. +#[derive(Clone, PartialEq, Message)] +pub struct ScalarTypes { + #[prost(int32, tag = "001")] + pub int32: i32, + #[prost(int64, tag = "002")] + pub int64: i64, + #[prost(uint32, tag = "003")] + pub uint32: u32, + #[prost(uint64, tag = "004")] + pub uint64: u64, + #[prost(sint32, tag = "005")] + pub sint32: i32, + #[prost(sint64, tag = "006")] + pub sint64: i64, + #[prost(fixed32, tag = "007")] + pub fixed32: u32, + #[prost(fixed64, tag = "008")] + pub fixed64: u64, + #[prost(sfixed32, tag = "009")] + pub sfixed32: i32, + #[prost(sfixed64, tag = "010")] + pub sfixed64: i64, + #[prost(float, tag = "011")] + pub float: f32, + #[prost(double, tag = "012")] + pub double: f64, + #[prost(bool, tag = "013")] + pub _bool: bool, + #[prost(string, tag = "014")] + pub string: String, + #[prost(bytes, tag = "015")] + pub bytes: Vec, + + #[prost(int32, required, tag = "101")] + pub required_int32: i32, + #[prost(int64, required, tag = "102")] + pub required_int64: i64, + #[prost(uint32, required, tag = "103")] + pub required_uint32: u32, + #[prost(uint64, required, tag = "104")] + pub required_uint64: u64, + #[prost(sint32, required, tag = "105")] + pub required_sint32: i32, + #[prost(sint64, required, tag = "106")] + pub required_sint64: i64, + #[prost(fixed32, required, tag = "107")] + pub required_fixed32: u32, + #[prost(fixed64, required, tag = "108")] + pub required_fixed64: u64, + #[prost(sfixed32, required, tag = "109")] + pub required_sfixed32: i32, + #[prost(sfixed64, required, tag = "110")] + pub required_sfixed64: i64, + #[prost(float, required, tag = "111")] + pub required_float: f32, + #[prost(double, required, tag = "112")] + pub required_double: f64, + #[prost(bool, required, tag = "113")] + pub required_bool: bool, + #[prost(string, required, tag = "114")] + pub required_string: String, + #[prost(bytes, required, tag = "115")] + pub required_bytes: Vec, + + #[prost(int32, optional, tag = "201")] + pub optional_int32: Option, + #[prost(int64, optional, tag = "202")] + pub optional_int64: Option, + #[prost(uint32, optional, tag = "203")] + pub optional_uint32: Option, + #[prost(uint64, optional, tag = "204")] + pub optional_uint64: Option, + #[prost(sint32, optional, tag = "205")] + pub optional_sint32: Option, + #[prost(sint64, optional, tag = "206")] + pub optional_sint64: Option, + + #[prost(fixed32, optional, tag = "207")] + pub optional_fixed32: Option, + #[prost(fixed64, optional, tag = "208")] + pub optional_fixed64: Option, + #[prost(sfixed32, optional, tag = "209")] + pub optional_sfixed32: Option, + #[prost(sfixed64, optional, tag = "210")] + pub optional_sfixed64: Option, + #[prost(float, optional, tag = "211")] + pub optional_float: Option, + #[prost(double, optional, tag = "212")] + pub optional_double: Option, + #[prost(bool, optional, tag = "213")] + pub optional_bool: Option, + #[prost(string, optional, tag = "214")] + pub optional_string: Option, + #[prost(bytes, optional, tag = "215")] + pub optional_bytes: Option>, + + #[prost(int32, repeated, packed = "false", tag = "301")] + pub repeated_int32: Vec, + #[prost(int64, repeated, packed = "false", tag = "302")] + pub repeated_int64: Vec, + #[prost(uint32, repeated, packed = "false", tag = "303")] + pub repeated_uint32: Vec, + #[prost(uint64, repeated, packed = "false", tag = "304")] + pub repeated_uint64: Vec, + #[prost(sint32, repeated, packed = "false", tag = "305")] + pub repeated_sint32: Vec, + #[prost(sint64, repeated, packed = "false", tag = "306")] + pub repeated_sint64: Vec, + #[prost(fixed32, repeated, packed = "false", tag = "307")] + pub repeated_fixed32: Vec, + #[prost(fixed64, repeated, packed = "false", tag = "308")] + pub repeated_fixed64: Vec, + #[prost(sfixed32, repeated, packed = "false", tag = "309")] + pub repeated_sfixed32: Vec, + #[prost(sfixed64, repeated, packed = "false", tag = "310")] + pub repeated_sfixed64: Vec, + #[prost(float, repeated, packed = "false", tag = "311")] + pub repeated_float: Vec, + #[prost(double, repeated, packed = "false", tag = "312")] + pub repeated_double: Vec, + #[prost(bool, repeated, packed = "false", tag = "313")] + pub repeated_bool: Vec, + #[prost(string, repeated, packed = "false", tag = "315")] + pub repeated_string: Vec, + #[prost(bytes, repeated, packed = "false", tag = "316")] + pub repeated_bytes: Vec>, + + #[prost(int32, repeated, tag = "401")] + pub packed_int32: Vec, + #[prost(int64, repeated, tag = "402")] + pub packed_int64: Vec, + #[prost(uint32, repeated, tag = "403")] + pub packed_uint32: Vec, + #[prost(uint64, repeated, tag = "404")] + pub packed_uint64: Vec, + #[prost(sint32, repeated, tag = "405")] + pub packed_sint32: Vec, + #[prost(sint64, repeated, tag = "406")] + pub packed_sint64: Vec, + #[prost(fixed32, repeated, tag = "407")] + pub packed_fixed32: Vec, + + #[prost(fixed64, repeated, tag = "408")] + pub packed_fixed64: Vec, + #[prost(sfixed32, repeated, tag = "409")] + pub packed_sfixed32: Vec, + #[prost(sfixed64, repeated, tag = "410")] + pub packed_sfixed64: Vec, + #[prost(float, repeated, tag = "411")] + pub packed_float: Vec, + #[prost(double, repeated, tag = "412")] + pub packed_double: Vec, + #[prost(bool, repeated, tag = "413")] + pub packed_bool: Vec, + #[prost(string, repeated, tag = "415")] + pub packed_string: Vec, + #[prost(bytes, repeated, tag = "416")] + pub packed_bytes: Vec>, +} + +#[test] +fn check_tags_inferred() { + check_message(&TagsInferred::default()); + check_serialize_equivalent(&TagsInferred::default(), &TagsQualified::default()); + + let tags_inferred = TagsInferred { + one: true, + two: Some(42), + three: vec![0.0, 1.0, 1.0], + skip_to_nine: "nine".to_owned(), + ten: 0, + eleven: Default::default(), + back_to_five: vec![1, 0, 1], + six: Basic::default(), + }; + check_message(&tags_inferred); + + let tags_qualified = TagsQualified { + one: true, + two: Some(42), + three: vec![0.0, 1.0, 1.0], + five: vec![1, 0, 1], + six: Basic::default(), + nine: "nine".to_owned(), + ten: 0, + eleven: Default::default(), + }; + check_serialize_equivalent(&tags_inferred, &tags_qualified); +} + +#[derive(Clone, PartialEq, Message)] +pub struct TagsInferred { + #[prost(bool)] + pub one: bool, + #[prost(int32, optional)] + pub two: Option, + #[prost(float, repeated)] + pub three: Vec, + + #[prost(tag = "9", string, required)] + pub skip_to_nine: String, + #[prost(enumeration = "BasicEnumeration", default = "ONE")] + pub ten: i32, + #[prost(btree_map = "string, string")] + pub eleven: ::alloc::collections::BTreeMap, + + #[prost(tag = "5", bytes)] + pub back_to_five: Vec, + #[prost(message, required)] + pub six: Basic, +} + +#[derive(Clone, PartialEq, Message)] +pub struct TagsQualified { + #[prost(tag = "1", bool)] + pub one: bool, + #[prost(tag = "2", int32, optional)] + pub two: Option, + #[prost(tag = "3", float, repeated)] + pub three: Vec, + + #[prost(tag = "5", bytes)] + pub five: Vec, + #[prost(tag = "6", message, required)] + pub six: Basic, + + #[prost(tag = "9", string, required)] + pub nine: String, + #[prost(tag = "10", enumeration = "BasicEnumeration", default = "ONE")] + pub ten: i32, + #[prost(tag = "11", btree_map = "string, string")] + pub eleven: ::alloc::collections::BTreeMap, +} + +/// A prost message with default value. +#[derive(Clone, PartialEq, Message)] +pub struct DefaultValues { + #[prost(int32, tag = "1", default = "42")] + pub int32: i32, + + #[prost(int32, optional, tag = "2", default = "88")] + pub optional_int32: Option, + + #[prost(string, tag = "3", default = "fourty two")] + pub string: String, + + #[prost(enumeration = "BasicEnumeration", tag = "4", default = "ONE")] + pub enumeration: i32, + + #[prost(enumeration = "BasicEnumeration", optional, tag = "5", default = "TWO")] + pub optional_enumeration: Option, + + #[prost(enumeration = "BasicEnumeration", repeated, tag = "6")] + pub repeated_enumeration: Vec, +} + +#[test] +fn check_default_values() { + let default = DefaultValues::default(); + assert_eq!(default.int32, 42); + assert_eq!(default.optional_int32, None); + assert_eq!(&default.string, "fourty two"); + assert_eq!(default.enumeration, BasicEnumeration::ONE as i32); + assert_eq!(default.optional_enumeration, None); + assert_eq!(&default.repeated_enumeration, &[]); + assert_eq!(0, default.encoded_len()); +} + +/// A protobuf enum. +#[derive(Clone, Copy, Debug, PartialEq, Enumeration)] +pub enum BasicEnumeration { + ZERO = 0, + ONE = 1, + TWO = 2, + THREE = 3, +} + +#[derive(Clone, PartialEq, Message)] +pub struct Basic { + #[prost(int32, tag = "1")] + pub int32: i32, + + #[prost(bool, repeated, packed = "false", tag = "2")] + pub bools: Vec, + + #[prost(string, tag = "3")] + pub string: String, + + #[prost(string, optional, tag = "4")] + pub optional_string: Option, + + #[prost(enumeration = "BasicEnumeration", tag = "5")] + pub enumeration: i32, + + #[prost(btree_map = "int32, enumeration(BasicEnumeration)", tag = "6")] + pub enumeration_map: ::alloc::collections::BTreeMap, + + #[prost(btree_map = "string, string", tag = "7")] + pub string_map: ::alloc::collections::BTreeMap, + + #[prost(btree_map = "int32, enumeration(BasicEnumeration)", tag = "10")] + pub enumeration_btree_map: ::alloc::collections::BTreeMap, + + #[prost(btree_map = "string, string", tag = "11")] + pub string_btree_map: ::alloc::collections::BTreeMap, + + #[prost(oneof = "BasicOneof", tags = "8, 9")] + pub oneof: Option, +} + +#[derive(Clone, PartialEq, Message)] +pub struct Compound { + #[prost(message, optional, tag = "1")] + pub optional_message: Option, + + #[prost(message, required, tag = "2")] + pub required_message: Basic, + + #[prost(message, repeated, tag = "3")] + pub repeated_message: Vec, + + #[prost(btree_map = "sint32, message", tag = "4")] + pub message_map: ::alloc::collections::BTreeMap, + + #[prost(btree_map = "sint32, message", tag = "5")] + pub message_btree_map: ::alloc::collections::BTreeMap, +} + +#[derive(Clone, PartialEq, Oneof)] +pub enum BasicOneof { + #[prost(int32, tag = "8")] + Int(i32), + #[prost(string, tag = "9")] + String(String), +}