diff options
Diffstat (limited to '')
148 files changed, 3348 insertions, 0 deletions
diff --git a/tests/inputs/bool/bool.json b/tests/inputs/bool/bool.json new file mode 100644 index 0000000..348e031 --- /dev/null +++ b/tests/inputs/bool/bool.json @@ -0,0 +1,3 @@ +{ + "value": true +} diff --git a/tests/inputs/bool/bool.proto b/tests/inputs/bool/bool.proto new file mode 100644 index 0000000..77836b8 --- /dev/null +++ b/tests/inputs/bool/bool.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package bool; + +message Test { + bool value = 1; +} diff --git a/tests/inputs/bool/test_bool.py b/tests/inputs/bool/test_bool.py new file mode 100644 index 0000000..f9554ae --- /dev/null +++ b/tests/inputs/bool/test_bool.py @@ -0,0 +1,19 @@ +import pytest + +from tests.output_aristaproto.bool import Test +from tests.output_aristaproto_pydantic.bool import Test as TestPyd + + +def test_value(): + message = Test() + assert not message.value, "Boolean is False by default" + + +def test_pydantic_no_value(): + with pytest.raises(ValueError): + TestPyd() + + +def test_pydantic_value(): + message = Test(value=False) + assert not message.value diff --git a/tests/inputs/bytes/bytes.json b/tests/inputs/bytes/bytes.json new file mode 100644 index 0000000..34c4554 --- /dev/null +++ b/tests/inputs/bytes/bytes.json @@ -0,0 +1,3 @@ +{ + "data": "SGVsbG8sIFdvcmxkIQ==" +} diff --git a/tests/inputs/bytes/bytes.proto b/tests/inputs/bytes/bytes.proto new file mode 100644 index 0000000..9895468 --- /dev/null +++ b/tests/inputs/bytes/bytes.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package bytes; + +message Test { + bytes data = 1; +} diff --git a/tests/inputs/casing/casing.json b/tests/inputs/casing/casing.json new file mode 100644 index 0000000..559104b --- /dev/null +++ b/tests/inputs/casing/casing.json @@ -0,0 +1,4 @@ +{ + "camelCase": 1, + "snakeCase": "ONE" +} diff --git a/tests/inputs/casing/casing.proto b/tests/inputs/casing/casing.proto new file mode 100644 index 0000000..2023d93 --- /dev/null +++ b/tests/inputs/casing/casing.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package casing; + +enum my_enum { + ZERO = 0; + ONE = 1; + TWO = 2; +} + +message Test { + int32 camelCase = 1; + my_enum snake_case = 2; + snake_case_message snake_case_message = 3; + int32 UPPERCASE = 4; +} + +message snake_case_message { + +}
\ No newline at end of file diff --git a/tests/inputs/casing/test_casing.py b/tests/inputs/casing/test_casing.py new file mode 100644 index 0000000..0fa609b --- /dev/null +++ b/tests/inputs/casing/test_casing.py @@ -0,0 +1,23 @@ +import tests.output_aristaproto.casing as casing +from tests.output_aristaproto.casing import Test + + +def test_message_attributes(): + message = Test() + assert hasattr( + message, "snake_case_message" + ), "snake_case field name is same in python" + assert hasattr(message, "camel_case"), "CamelCase field is snake_case in python" + assert hasattr(message, "uppercase"), "UPPERCASE field is lowercase in python" + + +def test_message_casing(): + assert hasattr( + casing, "SnakeCaseMessage" + ), "snake_case Message name is converted to CamelCase in python" + + +def test_enum_casing(): + assert hasattr( + casing, "MyEnum" + ), "snake_case Enum name is converted to CamelCase in python" diff --git a/tests/inputs/casing_inner_class/casing_inner_class.proto b/tests/inputs/casing_inner_class/casing_inner_class.proto new file mode 100644 index 0000000..fae2a4c --- /dev/null +++ b/tests/inputs/casing_inner_class/casing_inner_class.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package casing_inner_class; + +message Test { + message inner_class { + sint32 old_exp = 1; + } + inner_class inner = 2; +}
\ No newline at end of file diff --git a/tests/inputs/casing_inner_class/test_casing_inner_class.py b/tests/inputs/casing_inner_class/test_casing_inner_class.py new file mode 100644 index 0000000..7c43add --- /dev/null +++ b/tests/inputs/casing_inner_class/test_casing_inner_class.py @@ -0,0 +1,14 @@ +import tests.output_aristaproto.casing_inner_class as casing_inner_class + + +def test_message_casing_inner_class_name(): + assert hasattr( + casing_inner_class, "TestInnerClass" + ), "Inline defined Message is correctly converted to CamelCase" + + +def test_message_casing_inner_class_attributes(): + message = casing_inner_class.Test() + assert hasattr( + message.inner, "old_exp" + ), "Inline defined Message attribute is snake_case" diff --git a/tests/inputs/casing_message_field_uppercase/casing_message_field_uppercase.proto b/tests/inputs/casing_message_field_uppercase/casing_message_field_uppercase.proto new file mode 100644 index 0000000..c6d42c3 --- /dev/null +++ b/tests/inputs/casing_message_field_uppercase/casing_message_field_uppercase.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package casing_message_field_uppercase; + +message Test { + int32 UPPERCASE = 1; + int32 UPPERCASE_V2 = 2; + int32 UPPER_CAMEL_CASE = 3; +}
\ No newline at end of file diff --git a/tests/inputs/casing_message_field_uppercase/casing_message_field_uppercase.py b/tests/inputs/casing_message_field_uppercase/casing_message_field_uppercase.py new file mode 100644 index 0000000..01a5234 --- /dev/null +++ b/tests/inputs/casing_message_field_uppercase/casing_message_field_uppercase.py @@ -0,0 +1,14 @@ +from tests.output_aristaproto.casing_message_field_uppercase import Test + + +def test_message_casing(): + message = Test() + assert hasattr( + message, "uppercase" + ), "UPPERCASE attribute is converted to 'uppercase' in python" + assert hasattr( + message, "uppercase_v2" + ), "UPPERCASE_V2 attribute is converted to 'uppercase_v2' in python" + assert hasattr( + message, "upper_camel_case" + ), "UPPER_CAMEL_CASE attribute is converted to upper_camel_case in python" diff --git a/tests/inputs/config.py b/tests/inputs/config.py new file mode 100644 index 0000000..6da1f88 --- /dev/null +++ b/tests/inputs/config.py @@ -0,0 +1,30 @@ +# Test cases that are expected to fail, e.g. unimplemented features or bug-fixes. +# Remove from list when fixed. +xfail = { + "namespace_keywords", # 70 + "googletypes_struct", # 9 + "googletypes_value", # 9 + "import_capitalized_package", + "example", # This is the example in the readme. Not a test. +} + +services = { + "googletypes_request", + "googletypes_response", + "googletypes_response_embedded", + "service", + "service_separate_packages", + "import_service_input_message", + "googletypes_service_returns_empty", + "googletypes_service_returns_googletype", + "example_service", + "empty_service", + "service_uppercase", +} + + +# Indicate json sample messages to skip when testing that json (de)serialization +# is symmetrical becuase some cases legitimately are not symmetrical. +# Each key references the name of the test scenario and the values in the tuple +# Are the names of the json files. +non_symmetrical_json = {"empty_repeated": ("empty_repeated",)} diff --git a/tests/inputs/deprecated/deprecated.json b/tests/inputs/deprecated/deprecated.json new file mode 100644 index 0000000..43b2b65 --- /dev/null +++ b/tests/inputs/deprecated/deprecated.json @@ -0,0 +1,6 @@ +{ + "message": { + "value": "hello" + }, + "value": 10 +} diff --git a/tests/inputs/deprecated/deprecated.proto b/tests/inputs/deprecated/deprecated.proto new file mode 100644 index 0000000..81d69c0 --- /dev/null +++ b/tests/inputs/deprecated/deprecated.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package deprecated; + +// Some documentation about the Test message. +message Test { + Message message = 1 [deprecated=true]; + int32 value = 2; +} + +message Message { + option deprecated = true; + string value = 1; +} diff --git a/tests/inputs/double/double-negative.json b/tests/inputs/double/double-negative.json new file mode 100644 index 0000000..e0776c7 --- /dev/null +++ b/tests/inputs/double/double-negative.json @@ -0,0 +1,3 @@ +{ + "count": -123.45 +} diff --git a/tests/inputs/double/double.json b/tests/inputs/double/double.json new file mode 100644 index 0000000..321412e --- /dev/null +++ b/tests/inputs/double/double.json @@ -0,0 +1,3 @@ +{ + "count": 123.45 +} diff --git a/tests/inputs/double/double.proto b/tests/inputs/double/double.proto new file mode 100644 index 0000000..66aea95 --- /dev/null +++ b/tests/inputs/double/double.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package double; + +message Test { + double count = 1; +} diff --git a/tests/inputs/empty_repeated/empty_repeated.json b/tests/inputs/empty_repeated/empty_repeated.json new file mode 100644 index 0000000..12a801c --- /dev/null +++ b/tests/inputs/empty_repeated/empty_repeated.json @@ -0,0 +1,3 @@ +{ + "msg": [{"values":[]}] +} diff --git a/tests/inputs/empty_repeated/empty_repeated.proto b/tests/inputs/empty_repeated/empty_repeated.proto new file mode 100644 index 0000000..f787301 --- /dev/null +++ b/tests/inputs/empty_repeated/empty_repeated.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package empty_repeated; + +message MessageA { + repeated float values = 1; +} + +message Test { + repeated MessageA msg = 1; +} diff --git a/tests/inputs/empty_service/empty_service.proto b/tests/inputs/empty_service/empty_service.proto new file mode 100644 index 0000000..e96ff64 --- /dev/null +++ b/tests/inputs/empty_service/empty_service.proto @@ -0,0 +1,7 @@ +/* Empty service without comments */ +syntax = "proto3"; + +package empty_service; + +service Test { +} diff --git a/tests/inputs/entry/entry.proto b/tests/inputs/entry/entry.proto new file mode 100644 index 0000000..3f2af4d --- /dev/null +++ b/tests/inputs/entry/entry.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package entry; + +// This is a minimal example of a repeated message field that caused issues when +// checking whether a message is a map. +// +// During the check wheter a field is a "map", the string "entry" is added to +// the field name, checked against the type name and then further checks are +// made against the nested type of a parent message. In this edge-case, the +// first check would pass even though it shouldn't and that would cause an +// error because the parent type does not have a "nested_type" attribute. + +message Test { + repeated ExportEntry export = 1; +} + +message ExportEntry { + string name = 1; +} diff --git a/tests/inputs/enum/enum.json b/tests/inputs/enum/enum.json new file mode 100644 index 0000000..d68f1c5 --- /dev/null +++ b/tests/inputs/enum/enum.json @@ -0,0 +1,9 @@ +{ + "choice": "FOUR", + "choices": [ + "ZERO", + "ONE", + "THREE", + "FOUR" + ] +} diff --git a/tests/inputs/enum/enum.proto b/tests/inputs/enum/enum.proto new file mode 100644 index 0000000..5e2e80c --- /dev/null +++ b/tests/inputs/enum/enum.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package enum; + +// Tests that enums are correctly serialized and that it correctly handles skipped and out-of-order enum values +message Test { + Choice choice = 1; + repeated Choice choices = 2; +} + +enum Choice { + ZERO = 0; + ONE = 1; + // TWO = 2; + FOUR = 4; + THREE = 3; +} + +// A "C" like enum with the enum name prefixed onto members, these should be stripped +enum ArithmeticOperator { + ARITHMETIC_OPERATOR_NONE = 0; + ARITHMETIC_OPERATOR_PLUS = 1; + ARITHMETIC_OPERATOR_MINUS = 2; + ARITHMETIC_OPERATOR_0_PREFIXED = 3; +} diff --git a/tests/inputs/enum/test_enum.py b/tests/inputs/enum/test_enum.py new file mode 100644 index 0000000..cf14c68 --- /dev/null +++ b/tests/inputs/enum/test_enum.py @@ -0,0 +1,114 @@ +from tests.output_aristaproto.enum import ( + ArithmeticOperator, + Choice, + Test, +) + + +def test_enum_set_and_get(): + assert Test(choice=Choice.ZERO).choice == Choice.ZERO + assert Test(choice=Choice.ONE).choice == Choice.ONE + assert Test(choice=Choice.THREE).choice == Choice.THREE + assert Test(choice=Choice.FOUR).choice == Choice.FOUR + + +def test_enum_set_with_int(): + assert Test(choice=0).choice == Choice.ZERO + assert Test(choice=1).choice == Choice.ONE + assert Test(choice=3).choice == Choice.THREE + assert Test(choice=4).choice == Choice.FOUR + + +def test_enum_is_comparable_with_int(): + assert Test(choice=Choice.ZERO).choice == 0 + assert Test(choice=Choice.ONE).choice == 1 + assert Test(choice=Choice.THREE).choice == 3 + assert Test(choice=Choice.FOUR).choice == 4 + + +def test_enum_to_dict(): + assert ( + "choice" not in Test(choice=Choice.ZERO).to_dict() + ), "Default enum value is not serialized" + assert ( + Test(choice=Choice.ZERO).to_dict(include_default_values=True)["choice"] + == "ZERO" + ) + assert Test(choice=Choice.ONE).to_dict()["choice"] == "ONE" + assert Test(choice=Choice.THREE).to_dict()["choice"] == "THREE" + assert Test(choice=Choice.FOUR).to_dict()["choice"] == "FOUR" + + +def test_repeated_enum_is_comparable_with_int(): + assert Test(choices=[Choice.ZERO]).choices == [0] + assert Test(choices=[Choice.ONE]).choices == [1] + assert Test(choices=[Choice.THREE]).choices == [3] + assert Test(choices=[Choice.FOUR]).choices == [4] + + +def test_repeated_enum_set_and_get(): + assert Test(choices=[Choice.ZERO]).choices == [Choice.ZERO] + assert Test(choices=[Choice.ONE]).choices == [Choice.ONE] + assert Test(choices=[Choice.THREE]).choices == [Choice.THREE] + assert Test(choices=[Choice.FOUR]).choices == [Choice.FOUR] + + +def test_repeated_enum_to_dict(): + assert Test(choices=[Choice.ZERO]).to_dict()["choices"] == ["ZERO"] + assert Test(choices=[Choice.ONE]).to_dict()["choices"] == ["ONE"] + assert Test(choices=[Choice.THREE]).to_dict()["choices"] == ["THREE"] + assert Test(choices=[Choice.FOUR]).to_dict()["choices"] == ["FOUR"] + + all_enums_dict = Test( + choices=[Choice.ZERO, Choice.ONE, Choice.THREE, Choice.FOUR] + ).to_dict() + assert (all_enums_dict["choices"]) == ["ZERO", "ONE", "THREE", "FOUR"] + + +def test_repeated_enum_with_single_value_to_dict(): + assert Test(choices=Choice.ONE).to_dict()["choices"] == ["ONE"] + assert Test(choices=1).to_dict()["choices"] == ["ONE"] + + +def test_repeated_enum_with_non_list_iterables_to_dict(): + assert Test(choices=(1, 3)).to_dict()["choices"] == ["ONE", "THREE"] + assert Test(choices=(1, 3)).to_dict()["choices"] == ["ONE", "THREE"] + assert Test(choices=(Choice.ONE, Choice.THREE)).to_dict()["choices"] == [ + "ONE", + "THREE", + ] + + def enum_generator(): + yield Choice.ONE + yield Choice.THREE + + assert Test(choices=enum_generator()).to_dict()["choices"] == ["ONE", "THREE"] + + +def test_enum_mapped_on_parse(): + # test default value + b = Test().parse(bytes(Test())) + assert b.choice.name == Choice.ZERO.name + assert b.choices == [] + + # test non default value + a = Test().parse(bytes(Test(choice=Choice.ONE))) + assert a.choice.name == Choice.ONE.name + assert b.choices == [] + + # test repeated + c = Test().parse(bytes(Test(choices=[Choice.THREE, Choice.FOUR]))) + assert c.choices[0].name == Choice.THREE.name + assert c.choices[1].name == Choice.FOUR.name + + # bonus: defaults after empty init are also mapped + assert Test().choice.name == Choice.ZERO.name + + +def test_renamed_enum_members(): + assert set(ArithmeticOperator.__members__) == { + "NONE", + "PLUS", + "MINUS", + "_0_PREFIXED", + } diff --git a/tests/inputs/example/example.proto b/tests/inputs/example/example.proto new file mode 100644 index 0000000..56bd364 --- /dev/null +++ b/tests/inputs/example/example.proto @@ -0,0 +1,911 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + + +syntax = "proto2"; + +package example; + +// package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2" and "proto3". + optional string syntax = 12; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported in proto3. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REQUIRED = 2; + LABEL_REPEATED = 3; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + // TODO(kenton): Base-64 encode? + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; + + // If true, this is a proto3 "optional". When a proto3 field is optional, it + // tracks presence regardless of field type. + // + // When proto3_optional is true, this field must be belong to a oneof to + // signal to old proto3 clients that presence is tracked for this field. This + // oneof is known as a "synthetic" oneof, and this field must be its sole + // member (each proto3 optional field gets its own synthetic oneof). Synthetic + // oneofs exist in the descriptor only, and do not generate any API. Synthetic + // oneofs must be ordered after all "real" oneofs. + // + // For message fields, proto3_optional doesn't create any semantic change, + // since non-repeated message fields always track presence. However it still + // indicates the semantic detail of whether the user wrote "optional" or not. + // This can be useful for round-tripping the .proto file. For consistency we + // give message fields a synthetic oneof also, even though it is not required + // to track presence. This is especially important because the parser can't + // tell if a field is a message or an enum, so it must always create a + // synthetic oneof. + // + // Proto2 optional fields do not set this flag, because they already indicate + // optional with `LABEL_OPTIONAL`. + optional bool proto3_optional = 17; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + + // If set, all the classes from the .proto file are wrapped in a single + // outer class with the given name. This applies to both Proto1 + // (equivalent to the old "--one_java_file" option) and Proto2 (where + // a .proto always translates to a single class, but you may want to + // explicitly choose the class name). + optional string java_outer_classname = 8; + + // If set true, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the outer class + // named by java_outer_classname. However, the outer class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // If set true, then the Java2 code generator will generate code that + // throws an exception whenever an attempt is made to assign a non-UTF-8 + // byte sequence to a string field. + // Message reflection will do the same. + // However, an extension field still accepts non-UTF-8 byte sequences. + // This option has no effect on when used with the lite runtime. + optional bool java_string_check_utf8 = 27 [default = false]; + + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + + + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool php_generic_services = 42 [default = false]; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = true]; + + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map<KeyType, ValueType> map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is not yet implemented in the open source + // release -- sorry, we'll try to include it in a future version! + optional CType ctype = 1 [default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // + // Note that implementations may choose not to check required fields within + // a lazy sub-message. That is, calling IsInitialized() on the outer message + // may return true even if the inner message has missing required fields. + // This is necessary because otherwise the inner message would have to be + // parsed in order to perform the check, defeating the purpose of lazy + // parsing. An implementation which chooses not to check required fields + // must be consistent about it. That is, for any particular sub-message, the + // implementation must either *always* check its required fields, or *never* + // check its required fields, regardless of whether or not the message has + // been parsed. + optional bool lazy = 5 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype +} + +message OneofOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents + // "foo.(bar.baz).qux". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition. For + // example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to qux. + // // + // // Another line attached to qux. + // optional double qux = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to qux or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified offset. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + } +} diff --git a/tests/inputs/example_service/example_service.proto b/tests/inputs/example_service/example_service.proto new file mode 100644 index 0000000..96455cc --- /dev/null +++ b/tests/inputs/example_service/example_service.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package example_service; + +service Test { + rpc ExampleUnaryUnary(ExampleRequest) returns (ExampleResponse); + rpc ExampleUnaryStream(ExampleRequest) returns (stream ExampleResponse); + rpc ExampleStreamUnary(stream ExampleRequest) returns (ExampleResponse); + rpc ExampleStreamStream(stream ExampleRequest) returns (stream ExampleResponse); +} + +message ExampleRequest { + string example_string = 1; + int64 example_integer = 2; +} + +message ExampleResponse { + string example_string = 1; + int64 example_integer = 2; +} diff --git a/tests/inputs/example_service/test_example_service.py b/tests/inputs/example_service/test_example_service.py new file mode 100644 index 0000000..551e3fe --- /dev/null +++ b/tests/inputs/example_service/test_example_service.py @@ -0,0 +1,86 @@ +from typing import ( + AsyncIterable, + AsyncIterator, +) + +import pytest +from grpclib.testing import ChannelFor + +from tests.output_aristaproto.example_service import ( + ExampleRequest, + ExampleResponse, + TestBase, + TestStub, +) + + +class ExampleService(TestBase): + async def example_unary_unary( + self, example_request: ExampleRequest + ) -> "ExampleResponse": + return ExampleResponse( + example_string=example_request.example_string, + example_integer=example_request.example_integer, + ) + + async def example_unary_stream( + self, example_request: ExampleRequest + ) -> AsyncIterator["ExampleResponse"]: + response = ExampleResponse( + example_string=example_request.example_string, + example_integer=example_request.example_integer, + ) + yield response + yield response + yield response + + async def example_stream_unary( + self, example_request_iterator: AsyncIterator["ExampleRequest"] + ) -> "ExampleResponse": + async for example_request in example_request_iterator: + return ExampleResponse( + example_string=example_request.example_string, + example_integer=example_request.example_integer, + ) + + async def example_stream_stream( + self, example_request_iterator: AsyncIterator["ExampleRequest"] + ) -> AsyncIterator["ExampleResponse"]: + async for example_request in example_request_iterator: + yield ExampleResponse( + example_string=example_request.example_string, + example_integer=example_request.example_integer, + ) + + +@pytest.mark.asyncio +async def test_calls_with_different_cardinalities(): + example_request = ExampleRequest("test string", 42) + + async with ChannelFor([ExampleService()]) as channel: + stub = TestStub(channel) + + # unary unary + response = await stub.example_unary_unary(example_request) + assert response.example_string == example_request.example_string + assert response.example_integer == example_request.example_integer + + # unary stream + async for response in stub.example_unary_stream(example_request): + assert response.example_string == example_request.example_string + assert response.example_integer == example_request.example_integer + + # stream unary + async def request_iterator(): + yield example_request + yield example_request + yield example_request + + response = await stub.example_stream_unary(request_iterator()) + assert response.example_string == example_request.example_string + assert response.example_integer == example_request.example_integer + + # stream stream + async for response in stub.example_stream_stream(request_iterator()): + assert response.example_string == example_request.example_string + assert response.example_integer == example_request.example_integer diff --git a/tests/inputs/field_name_identical_to_type/field_name_identical_to_type.json b/tests/inputs/field_name_identical_to_type/field_name_identical_to_type.json new file mode 100644 index 0000000..7a6e7ae --- /dev/null +++ b/tests/inputs/field_name_identical_to_type/field_name_identical_to_type.json @@ -0,0 +1,7 @@ +{ + "int": 26, + "float": 26.0, + "str": "value-for-str", + "bytes": "001a", + "bool": true +}
\ No newline at end of file diff --git a/tests/inputs/field_name_identical_to_type/field_name_identical_to_type.proto b/tests/inputs/field_name_identical_to_type/field_name_identical_to_type.proto new file mode 100644 index 0000000..81a0fc4 --- /dev/null +++ b/tests/inputs/field_name_identical_to_type/field_name_identical_to_type.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package field_name_identical_to_type; + +// Tests that messages may contain fields with names that are identical to their python types (PR #294) + +message Test { + int32 int = 1; + float float = 2; + string str = 3; + bytes bytes = 4; + bool bool = 5; +}
\ No newline at end of file diff --git a/tests/inputs/fixed/fixed.json b/tests/inputs/fixed/fixed.json new file mode 100644 index 0000000..8858780 --- /dev/null +++ b/tests/inputs/fixed/fixed.json @@ -0,0 +1,6 @@ +{ + "foo": 4294967295, + "bar": -2147483648, + "baz": "18446744073709551615", + "qux": "-9223372036854775808" +} diff --git a/tests/inputs/fixed/fixed.proto b/tests/inputs/fixed/fixed.proto new file mode 100644 index 0000000..0f0ffb4 --- /dev/null +++ b/tests/inputs/fixed/fixed.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package fixed; + +message Test { + fixed32 foo = 1; + sfixed32 bar = 2; + fixed64 baz = 3; + sfixed64 qux = 4; +} diff --git a/tests/inputs/float/float.json b/tests/inputs/float/float.json new file mode 100644 index 0000000..3adac97 --- /dev/null +++ b/tests/inputs/float/float.json @@ -0,0 +1,9 @@ +{ + "positive": "Infinity", + "negative": "-Infinity", + "nan": "NaN", + "three": 3.0, + "threePointOneFour": 3.14, + "negThree": -3.0, + "negThreePointOneFour": -3.14 + } diff --git a/tests/inputs/float/float.proto b/tests/inputs/float/float.proto new file mode 100644 index 0000000..fea12b3 --- /dev/null +++ b/tests/inputs/float/float.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package float; + +// Some documentation about the Test message. +message Test { + double positive = 1; + double negative = 2; + double nan = 3; + double three = 4; + double three_point_one_four = 5; + double neg_three = 6; + double neg_three_point_one_four = 7; +} diff --git a/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto b/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto new file mode 100644 index 0000000..66ef8a6 --- /dev/null +++ b/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; +package google_impl_behavior_equivalence; + +message Foo { int64 bar = 1; } + +message Test { + oneof group { + string string = 1; + int64 integer = 2; + Foo foo = 3; + } +} + +message Spam { + google.protobuf.Timestamp ts = 1; +} + +message Request { Empty foo = 1; } + +message Empty {} diff --git a/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py b/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py new file mode 100644 index 0000000..c621f11 --- /dev/null +++ b/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py @@ -0,0 +1,93 @@ +from datetime import ( + datetime, + timezone, +) + +import pytest +from google.protobuf import json_format +from google.protobuf.timestamp_pb2 import Timestamp + +import aristaproto +from tests.output_aristaproto.google_impl_behavior_equivalence import ( + Empty, + Foo, + Request, + Spam, + Test, +) +from tests.output_reference.google_impl_behavior_equivalence.google_impl_behavior_equivalence_pb2 import ( + Empty as ReferenceEmpty, + Foo as ReferenceFoo, + Request as ReferenceRequest, + Spam as ReferenceSpam, + Test as ReferenceTest, +) + + +def test_oneof_serializes_similar_to_google_oneof(): + tests = [ + (Test(string="abc"), ReferenceTest(string="abc")), + (Test(integer=2), ReferenceTest(integer=2)), + (Test(foo=Foo(bar=1)), ReferenceTest(foo=ReferenceFoo(bar=1))), + # Default values should also behave the same within oneofs + (Test(string=""), ReferenceTest(string="")), + (Test(integer=0), ReferenceTest(integer=0)), + (Test(foo=Foo(bar=0)), ReferenceTest(foo=ReferenceFoo(bar=0))), + ] + for message, message_reference in tests: + # NOTE: As of July 2020, MessageToJson inserts newlines in the output string so, + # just compare dicts + assert message.to_dict() == json_format.MessageToDict(message_reference) + + +def test_bytes_are_the_same_for_oneof(): + message = Test(string="") + message_reference = ReferenceTest(string="") + + message_bytes = bytes(message) + message_reference_bytes = message_reference.SerializeToString() + + assert message_bytes == message_reference_bytes + + message2 = Test().parse(message_reference_bytes) + message_reference2 = ReferenceTest() + message_reference2.ParseFromString(message_reference_bytes) + + assert message == message2 + assert message_reference == message_reference2 + + # None of these fields were explicitly set BUT they should not actually be null + # themselves + assert not hasattr(message, "foo") + assert object.__getattribute__(message, "foo") == aristaproto.PLACEHOLDER + assert not hasattr(message2, "foo") + assert object.__getattribute__(message2, "foo") == aristaproto.PLACEHOLDER + + assert isinstance(message_reference.foo, ReferenceFoo) + assert isinstance(message_reference2.foo, ReferenceFoo) + + +@pytest.mark.parametrize("dt", (datetime.min.replace(tzinfo=timezone.utc),)) +def test_datetime_clamping(dt): # see #407 + ts = Timestamp() + ts.FromDatetime(dt) + assert bytes(Spam(dt)) == ReferenceSpam(ts=ts).SerializeToString() + message_bytes = bytes(Spam(dt)) + + assert ( + Spam().parse(message_bytes).ts.timestamp() + == ReferenceSpam.FromString(message_bytes).ts.seconds + ) + + +def test_empty_message_field(): + message = Request() + reference_message = ReferenceRequest() + + message.foo = Empty() + reference_message.foo.CopyFrom(ReferenceEmpty()) + + assert aristaproto.serialized_on_wire(message.foo) + assert reference_message.HasField("foo") + + assert bytes(message) == reference_message.SerializeToString() diff --git a/tests/inputs/googletypes/googletypes-missing.json b/tests/inputs/googletypes/googletypes-missing.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/inputs/googletypes/googletypes-missing.json @@ -0,0 +1 @@ +{} diff --git a/tests/inputs/googletypes/googletypes.json b/tests/inputs/googletypes/googletypes.json new file mode 100644 index 0000000..0a002e9 --- /dev/null +++ b/tests/inputs/googletypes/googletypes.json @@ -0,0 +1,7 @@ +{ + "maybe": false, + "ts": "1972-01-01T10:00:20.021Z", + "duration": "1.200s", + "important": 10, + "empty": {} +} diff --git a/tests/inputs/googletypes/googletypes.proto b/tests/inputs/googletypes/googletypes.proto new file mode 100644 index 0000000..ef8cb4a --- /dev/null +++ b/tests/inputs/googletypes/googletypes.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package googletypes; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/empty.proto"; + +message Test { + google.protobuf.BoolValue maybe = 1; + google.protobuf.Timestamp ts = 2; + google.protobuf.Duration duration = 3; + google.protobuf.Int32Value important = 4; + google.protobuf.Empty empty = 5; +} diff --git a/tests/inputs/googletypes_request/googletypes_request.proto b/tests/inputs/googletypes_request/googletypes_request.proto new file mode 100644 index 0000000..1cedcaa --- /dev/null +++ b/tests/inputs/googletypes_request/googletypes_request.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package googletypes_request; + +import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +// Tests that google types can be used as params + +service Test { + rpc SendDouble (google.protobuf.DoubleValue) returns (Input); + rpc SendFloat (google.protobuf.FloatValue) returns (Input); + rpc SendInt64 (google.protobuf.Int64Value) returns (Input); + rpc SendUInt64 (google.protobuf.UInt64Value) returns (Input); + rpc SendInt32 (google.protobuf.Int32Value) returns (Input); + rpc SendUInt32 (google.protobuf.UInt32Value) returns (Input); + rpc SendBool (google.protobuf.BoolValue) returns (Input); + rpc SendString (google.protobuf.StringValue) returns (Input); + rpc SendBytes (google.protobuf.BytesValue) returns (Input); + rpc SendDatetime (google.protobuf.Timestamp) returns (Input); + rpc SendTimedelta (google.protobuf.Duration) returns (Input); + rpc SendEmpty (google.protobuf.Empty) returns (Input); +} + +message Input { + +} diff --git a/tests/inputs/googletypes_request/test_googletypes_request.py b/tests/inputs/googletypes_request/test_googletypes_request.py new file mode 100644 index 0000000..8351f71 --- /dev/null +++ b/tests/inputs/googletypes_request/test_googletypes_request.py @@ -0,0 +1,47 @@ +from datetime import ( + datetime, + timedelta, +) +from typing import ( + Any, + Callable, +) + +import pytest + +import aristaproto.lib.google.protobuf as protobuf +from tests.mocks import MockChannel +from tests.output_aristaproto.googletypes_request import ( + Input, + TestStub, +) + + +test_cases = [ + (TestStub.send_double, protobuf.DoubleValue, 2.5), + (TestStub.send_float, protobuf.FloatValue, 2.5), + (TestStub.send_int64, protobuf.Int64Value, -64), + (TestStub.send_u_int64, protobuf.UInt64Value, 64), + (TestStub.send_int32, protobuf.Int32Value, -32), + (TestStub.send_u_int32, protobuf.UInt32Value, 32), + (TestStub.send_bool, protobuf.BoolValue, True), + (TestStub.send_string, protobuf.StringValue, "string"), + (TestStub.send_bytes, protobuf.BytesValue, bytes(0xFF)[0:4]), + (TestStub.send_datetime, protobuf.Timestamp, datetime(2038, 1, 19, 3, 14, 8)), + (TestStub.send_timedelta, protobuf.Duration, timedelta(seconds=123456)), +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize(["service_method", "wrapper_class", "value"], test_cases) +async def test_channel_receives_wrapped_type( + service_method: Callable[[TestStub, Input], Any], wrapper_class: Callable, value +): + wrapped_value = wrapper_class() + wrapped_value.value = value + channel = MockChannel(responses=[Input()]) + service = TestStub(channel) + + await service_method(service, wrapped_value) + + assert channel.requests[0]["request"] == type(wrapped_value) diff --git a/tests/inputs/googletypes_response/googletypes_response.proto b/tests/inputs/googletypes_response/googletypes_response.proto new file mode 100644 index 0000000..8917d1c --- /dev/null +++ b/tests/inputs/googletypes_response/googletypes_response.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package googletypes_response; + +import "google/protobuf/wrappers.proto"; + +// Tests that wrapped values can be used directly as return values + +service Test { + rpc GetDouble (Input) returns (google.protobuf.DoubleValue); + rpc GetFloat (Input) returns (google.protobuf.FloatValue); + rpc GetInt64 (Input) returns (google.protobuf.Int64Value); + rpc GetUInt64 (Input) returns (google.protobuf.UInt64Value); + rpc GetInt32 (Input) returns (google.protobuf.Int32Value); + rpc GetUInt32 (Input) returns (google.protobuf.UInt32Value); + rpc GetBool (Input) returns (google.protobuf.BoolValue); + rpc GetString (Input) returns (google.protobuf.StringValue); + rpc GetBytes (Input) returns (google.protobuf.BytesValue); +} + +message Input { + +} diff --git a/tests/inputs/googletypes_response/test_googletypes_response.py b/tests/inputs/googletypes_response/test_googletypes_response.py new file mode 100644 index 0000000..4ac340e --- /dev/null +++ b/tests/inputs/googletypes_response/test_googletypes_response.py @@ -0,0 +1,64 @@ +from typing import ( + Any, + Callable, + Optional, +) + +import pytest + +import aristaproto.lib.google.protobuf as protobuf +from tests.mocks import MockChannel +from tests.output_aristaproto.googletypes_response import ( + Input, + TestStub, +) + + +test_cases = [ + (TestStub.get_double, protobuf.DoubleValue, 2.5), + (TestStub.get_float, protobuf.FloatValue, 2.5), + (TestStub.get_int64, protobuf.Int64Value, -64), + (TestStub.get_u_int64, protobuf.UInt64Value, 64), + (TestStub.get_int32, protobuf.Int32Value, -32), + (TestStub.get_u_int32, protobuf.UInt32Value, 32), + (TestStub.get_bool, protobuf.BoolValue, True), + (TestStub.get_string, protobuf.StringValue, "string"), + (TestStub.get_bytes, protobuf.BytesValue, bytes(0xFF)[0:4]), +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize(["service_method", "wrapper_class", "value"], test_cases) +async def test_channel_receives_wrapped_type( + service_method: Callable[[TestStub, Input], Any], wrapper_class: Callable, value +): + wrapped_value = wrapper_class() + wrapped_value.value = value + channel = MockChannel(responses=[wrapped_value]) + service = TestStub(channel) + method_param = Input() + + await service_method(service, method_param) + + assert channel.requests[0]["response_type"] != Optional[type(value)] + assert channel.requests[0]["response_type"] == type(wrapped_value) + + +@pytest.mark.asyncio +@pytest.mark.xfail +@pytest.mark.parametrize(["service_method", "wrapper_class", "value"], test_cases) +async def test_service_unwraps_response( + service_method: Callable[[TestStub, Input], Any], wrapper_class: Callable, value +): + """ + grpclib does not unwrap wrapper values returned by services + """ + wrapped_value = wrapper_class() + wrapped_value.value = value + service = TestStub(MockChannel(responses=[wrapped_value])) + method_param = Input() + + response_value = await service_method(service, method_param) + + assert response_value == value + assert type(response_value) == type(value) diff --git a/tests/inputs/googletypes_response_embedded/googletypes_response_embedded.proto b/tests/inputs/googletypes_response_embedded/googletypes_response_embedded.proto new file mode 100644 index 0000000..47284e3 --- /dev/null +++ b/tests/inputs/googletypes_response_embedded/googletypes_response_embedded.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package googletypes_response_embedded; + +import "google/protobuf/wrappers.proto"; + +// Tests that wrapped values are supported as part of output message +service Test { + rpc getOutput (Input) returns (Output); +} + +message Input { + +} + +message Output { + google.protobuf.DoubleValue double_value = 1; + google.protobuf.FloatValue float_value = 2; + google.protobuf.Int64Value int64_value = 3; + google.protobuf.UInt64Value uint64_value = 4; + google.protobuf.Int32Value int32_value = 5; + google.protobuf.UInt32Value uint32_value = 6; + google.protobuf.BoolValue bool_value = 7; + google.protobuf.StringValue string_value = 8; + google.protobuf.BytesValue bytes_value = 9; +} diff --git a/tests/inputs/googletypes_response_embedded/test_googletypes_response_embedded.py b/tests/inputs/googletypes_response_embedded/test_googletypes_response_embedded.py new file mode 100644 index 0000000..3d31728 --- /dev/null +++ b/tests/inputs/googletypes_response_embedded/test_googletypes_response_embedded.py @@ -0,0 +1,40 @@ +import pytest + +from tests.mocks import MockChannel +from tests.output_aristaproto.googletypes_response_embedded import ( + Input, + Output, + TestStub, +) + + +@pytest.mark.asyncio +async def test_service_passes_through_unwrapped_values_embedded_in_response(): + """ + We do not not need to implement value unwrapping for embedded well-known types, + as this is already handled by grpclib. This test merely shows that this is the case. + """ + output = Output( + double_value=10.0, + float_value=12.0, + int64_value=-13, + uint64_value=14, + int32_value=-15, + uint32_value=16, + bool_value=True, + string_value="string", + bytes_value=bytes(0xFF)[0:4], + ) + + service = TestStub(MockChannel(responses=[output])) + response = await service.get_output(Input()) + + assert response.double_value == 10.0 + assert response.float_value == 12.0 + assert response.int64_value == -13 + assert response.uint64_value == 14 + assert response.int32_value == -15 + assert response.uint32_value == 16 + assert response.bool_value + assert response.string_value == "string" + assert response.bytes_value == bytes(0xFF)[0:4] diff --git a/tests/inputs/googletypes_service_returns_empty/googletypes_service_returns_empty.proto b/tests/inputs/googletypes_service_returns_empty/googletypes_service_returns_empty.proto new file mode 100644 index 0000000..2153ad5 --- /dev/null +++ b/tests/inputs/googletypes_service_returns_empty/googletypes_service_returns_empty.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package googletypes_service_returns_empty; + +import "google/protobuf/empty.proto"; + +service Test { + rpc Send (RequestMessage) returns (google.protobuf.Empty) { + } +} + +message RequestMessage { +}
\ No newline at end of file diff --git a/tests/inputs/googletypes_service_returns_googletype/googletypes_service_returns_googletype.proto b/tests/inputs/googletypes_service_returns_googletype/googletypes_service_returns_googletype.proto new file mode 100644 index 0000000..457707b --- /dev/null +++ b/tests/inputs/googletypes_service_returns_googletype/googletypes_service_returns_googletype.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package googletypes_service_returns_googletype; + +import "google/protobuf/empty.proto"; +import "google/protobuf/struct.proto"; + +// Tests that imports are generated correctly when returning Google well-known types + +service Test { + rpc GetEmpty (RequestMessage) returns (google.protobuf.Empty); + rpc GetStruct (RequestMessage) returns (google.protobuf.Struct); + rpc GetListValue (RequestMessage) returns (google.protobuf.ListValue); + rpc GetValue (RequestMessage) returns (google.protobuf.Value); +} + +message RequestMessage { +}
\ No newline at end of file diff --git a/tests/inputs/googletypes_struct/googletypes_struct.json b/tests/inputs/googletypes_struct/googletypes_struct.json new file mode 100644 index 0000000..ecc175e --- /dev/null +++ b/tests/inputs/googletypes_struct/googletypes_struct.json @@ -0,0 +1,5 @@ +{ + "struct": { + "key": true + } +} diff --git a/tests/inputs/googletypes_struct/googletypes_struct.proto b/tests/inputs/googletypes_struct/googletypes_struct.proto new file mode 100644 index 0000000..2b8b5c5 --- /dev/null +++ b/tests/inputs/googletypes_struct/googletypes_struct.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package googletypes_struct; + +import "google/protobuf/struct.proto"; + +message Test { + google.protobuf.Struct struct = 1; +} diff --git a/tests/inputs/googletypes_value/googletypes_value.json b/tests/inputs/googletypes_value/googletypes_value.json new file mode 100644 index 0000000..db52d5c --- /dev/null +++ b/tests/inputs/googletypes_value/googletypes_value.json @@ -0,0 +1,11 @@ +{ + "value1": "hello world", + "value2": true, + "value3": 1, + "value4": null, + "value5": [ + 1, + 2, + 3 + ] +} diff --git a/tests/inputs/googletypes_value/googletypes_value.proto b/tests/inputs/googletypes_value/googletypes_value.proto new file mode 100644 index 0000000..d5089d5 --- /dev/null +++ b/tests/inputs/googletypes_value/googletypes_value.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package googletypes_value; + +import "google/protobuf/struct.proto"; + +// Tests that fields of type google.protobuf.Value can contain arbitrary JSON-values. + +message Test { + google.protobuf.Value value1 = 1; + google.protobuf.Value value2 = 2; + google.protobuf.Value value3 = 3; + google.protobuf.Value value4 = 4; + google.protobuf.Value value5 = 5; +} diff --git a/tests/inputs/import_capitalized_package/capitalized.proto b/tests/inputs/import_capitalized_package/capitalized.proto new file mode 100644 index 0000000..e80c95c --- /dev/null +++ b/tests/inputs/import_capitalized_package/capitalized.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + + +package import_capitalized_package.Capitalized; + +message Message { + +} diff --git a/tests/inputs/import_capitalized_package/test.proto b/tests/inputs/import_capitalized_package/test.proto new file mode 100644 index 0000000..38c9b2d --- /dev/null +++ b/tests/inputs/import_capitalized_package/test.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package import_capitalized_package; + +import "capitalized.proto"; + +// Tests that we can import from a package with a capital name, that looks like a nested type, but isn't. + +message Test { + Capitalized.Message message = 1; +} diff --git a/tests/inputs/import_child_package_from_package/child.proto b/tests/inputs/import_child_package_from_package/child.proto new file mode 100644 index 0000000..d99c7c3 --- /dev/null +++ b/tests/inputs/import_child_package_from_package/child.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_child_package_from_package.package.childpackage; + +message ChildMessage { + +} diff --git a/tests/inputs/import_child_package_from_package/import_child_package_from_package.proto b/tests/inputs/import_child_package_from_package/import_child_package_from_package.proto new file mode 100644 index 0000000..66e0aa8 --- /dev/null +++ b/tests/inputs/import_child_package_from_package/import_child_package_from_package.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package import_child_package_from_package; + +import "package_message.proto"; + +// Tests generated imports when a message in a package refers to a message in a nested child package. + +message Test { + package.PackageMessage message = 1; +} diff --git a/tests/inputs/import_child_package_from_package/package_message.proto b/tests/inputs/import_child_package_from_package/package_message.proto new file mode 100644 index 0000000..79d66f3 --- /dev/null +++ b/tests/inputs/import_child_package_from_package/package_message.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +import "child.proto"; + +package import_child_package_from_package.package; + +message PackageMessage { + package.childpackage.ChildMessage c = 1; +} diff --git a/tests/inputs/import_child_package_from_root/child.proto b/tests/inputs/import_child_package_from_root/child.proto new file mode 100644 index 0000000..2a46d5f --- /dev/null +++ b/tests/inputs/import_child_package_from_root/child.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_child_package_from_root.childpackage; + +message Message { + +} diff --git a/tests/inputs/import_child_package_from_root/import_child_package_from_root.proto b/tests/inputs/import_child_package_from_root/import_child_package_from_root.proto new file mode 100644 index 0000000..6299831 --- /dev/null +++ b/tests/inputs/import_child_package_from_root/import_child_package_from_root.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package import_child_package_from_root; + +import "child.proto"; + +// Tests generated imports when a message in root refers to a message in a child package. + +message Test { + childpackage.Message child = 1; +} diff --git a/tests/inputs/import_circular_dependency/import_circular_dependency.proto b/tests/inputs/import_circular_dependency/import_circular_dependency.proto new file mode 100644 index 0000000..8b159e2 --- /dev/null +++ b/tests/inputs/import_circular_dependency/import_circular_dependency.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package import_circular_dependency; + +import "root.proto"; +import "other.proto"; + +// This test-case verifies support for circular dependencies in the generated python files. +// +// This is important because we generate 1 python file/module per package, rather than 1 file per proto file. +// +// Scenario: +// +// The proto messages depend on each other in a non-circular way: +// +// Test -------> RootPackageMessage <--------------. +// `------------------------------------> OtherPackageMessage +// +// Test and RootPackageMessage are in different files, but belong to the same package (root): +// +// (Test -------> RootPackageMessage) <------------. +// `------------------------------------> OtherPackageMessage +// +// After grouping the packages into single files or modules, a circular dependency is created: +// +// (root: Test & RootPackageMessage) <-------> (other: OtherPackageMessage) +message Test { + RootPackageMessage message = 1; + other.OtherPackageMessage other = 2; +} diff --git a/tests/inputs/import_circular_dependency/other.proto b/tests/inputs/import_circular_dependency/other.proto new file mode 100644 index 0000000..833b869 --- /dev/null +++ b/tests/inputs/import_circular_dependency/other.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +import "root.proto"; +package import_circular_dependency.other; + +message OtherPackageMessage { + RootPackageMessage rootPackageMessage = 1; +} diff --git a/tests/inputs/import_circular_dependency/root.proto b/tests/inputs/import_circular_dependency/root.proto new file mode 100644 index 0000000..7383947 --- /dev/null +++ b/tests/inputs/import_circular_dependency/root.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_circular_dependency; + +message RootPackageMessage { + +} diff --git a/tests/inputs/import_cousin_package/cousin.proto b/tests/inputs/import_cousin_package/cousin.proto new file mode 100644 index 0000000..2870dfe --- /dev/null +++ b/tests/inputs/import_cousin_package/cousin.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; + +package import_cousin_package.cousin.cousin_subpackage; + +message CousinMessage { +} diff --git a/tests/inputs/import_cousin_package/test.proto b/tests/inputs/import_cousin_package/test.proto new file mode 100644 index 0000000..89ec3d8 --- /dev/null +++ b/tests/inputs/import_cousin_package/test.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package import_cousin_package.test.subpackage; + +import "cousin.proto"; + +// Verify that we can import message unrelated to us + +message Test { + cousin.cousin_subpackage.CousinMessage message = 1; +} diff --git a/tests/inputs/import_cousin_package_same_name/cousin.proto b/tests/inputs/import_cousin_package_same_name/cousin.proto new file mode 100644 index 0000000..84b6a40 --- /dev/null +++ b/tests/inputs/import_cousin_package_same_name/cousin.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; + +package import_cousin_package_same_name.cousin.subpackage; + +message CousinMessage { +} diff --git a/tests/inputs/import_cousin_package_same_name/test.proto b/tests/inputs/import_cousin_package_same_name/test.proto new file mode 100644 index 0000000..7b420d3 --- /dev/null +++ b/tests/inputs/import_cousin_package_same_name/test.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package import_cousin_package_same_name.test.subpackage; + +import "cousin.proto"; + +// Verify that we can import a message unrelated to us, in a subpackage with the same name as us. + +message Test { + cousin.subpackage.CousinMessage message = 1; +} diff --git a/tests/inputs/import_packages_same_name/import_packages_same_name.proto b/tests/inputs/import_packages_same_name/import_packages_same_name.proto new file mode 100644 index 0000000..dff7efe --- /dev/null +++ b/tests/inputs/import_packages_same_name/import_packages_same_name.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package import_packages_same_name; + +import "users_v1.proto"; +import "posts_v1.proto"; + +// Tests generated message can correctly reference two packages with the same leaf-name + +message Test { + users.v1.User user = 1; + posts.v1.Post post = 2; +} diff --git a/tests/inputs/import_packages_same_name/posts_v1.proto b/tests/inputs/import_packages_same_name/posts_v1.proto new file mode 100644 index 0000000..d3b9b1c --- /dev/null +++ b/tests/inputs/import_packages_same_name/posts_v1.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_packages_same_name.posts.v1; + +message Post { + +} diff --git a/tests/inputs/import_packages_same_name/users_v1.proto b/tests/inputs/import_packages_same_name/users_v1.proto new file mode 100644 index 0000000..d3a17e9 --- /dev/null +++ b/tests/inputs/import_packages_same_name/users_v1.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_packages_same_name.users.v1; + +message User { + +} diff --git a/tests/inputs/import_parent_package_from_child/import_parent_package_from_child.proto b/tests/inputs/import_parent_package_from_child/import_parent_package_from_child.proto new file mode 100644 index 0000000..edc4736 --- /dev/null +++ b/tests/inputs/import_parent_package_from_child/import_parent_package_from_child.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +import "parent_package_message.proto"; + +package import_parent_package_from_child.parent.child; + +// Tests generated imports when a message refers to a message defined in its parent package + +message Test { + ParentPackageMessage message_implicit = 1; + parent.ParentPackageMessage message_explicit = 2; +} diff --git a/tests/inputs/import_parent_package_from_child/parent_package_message.proto b/tests/inputs/import_parent_package_from_child/parent_package_message.proto new file mode 100644 index 0000000..fb3fd31 --- /dev/null +++ b/tests/inputs/import_parent_package_from_child/parent_package_message.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; + +package import_parent_package_from_child.parent; + +message ParentPackageMessage { +} diff --git a/tests/inputs/import_root_package_from_child/child.proto b/tests/inputs/import_root_package_from_child/child.proto new file mode 100644 index 0000000..bd51967 --- /dev/null +++ b/tests/inputs/import_root_package_from_child/child.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package import_root_package_from_child.child; + +import "root.proto"; + +// Verify that we can import root message from child package + +message Test { + RootMessage message = 1; +} diff --git a/tests/inputs/import_root_package_from_child/root.proto b/tests/inputs/import_root_package_from_child/root.proto new file mode 100644 index 0000000..6ae955a --- /dev/null +++ b/tests/inputs/import_root_package_from_child/root.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_root_package_from_child; + + +message RootMessage { +} diff --git a/tests/inputs/import_root_sibling/import_root_sibling.proto b/tests/inputs/import_root_sibling/import_root_sibling.proto new file mode 100644 index 0000000..759e606 --- /dev/null +++ b/tests/inputs/import_root_sibling/import_root_sibling.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package import_root_sibling; + +import "sibling.proto"; + +// Tests generated imports when a message in the root package refers to another message in the root package + +message Test { + SiblingMessage sibling = 1; +} diff --git a/tests/inputs/import_root_sibling/sibling.proto b/tests/inputs/import_root_sibling/sibling.proto new file mode 100644 index 0000000..6b6ba2e --- /dev/null +++ b/tests/inputs/import_root_sibling/sibling.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_root_sibling; + +message SiblingMessage { + +} diff --git a/tests/inputs/import_service_input_message/child_package_request_message.proto b/tests/inputs/import_service_input_message/child_package_request_message.proto new file mode 100644 index 0000000..54fc112 --- /dev/null +++ b/tests/inputs/import_service_input_message/child_package_request_message.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_service_input_message.child; + +message ChildRequestMessage { + int32 child_argument = 1; +}
\ No newline at end of file diff --git a/tests/inputs/import_service_input_message/import_service_input_message.proto b/tests/inputs/import_service_input_message/import_service_input_message.proto new file mode 100644 index 0000000..cbf48fa --- /dev/null +++ b/tests/inputs/import_service_input_message/import_service_input_message.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package import_service_input_message; + +import "request_message.proto"; +import "child_package_request_message.proto"; + +// Tests generated service correctly imports the RequestMessage + +service Test { + rpc DoThing (RequestMessage) returns (RequestResponse); + rpc DoThing2 (child.ChildRequestMessage) returns (RequestResponse); + rpc DoThing3 (Nested.RequestMessage) returns (RequestResponse); +} + + +message RequestResponse { + int32 value = 1; +} + +message Nested { + message RequestMessage { + int32 nestedArgument = 1; + } +}
\ No newline at end of file diff --git a/tests/inputs/import_service_input_message/request_message.proto b/tests/inputs/import_service_input_message/request_message.proto new file mode 100644 index 0000000..36a6e78 --- /dev/null +++ b/tests/inputs/import_service_input_message/request_message.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_service_input_message; + +message RequestMessage { + int32 argument = 1; +}
\ No newline at end of file diff --git a/tests/inputs/import_service_input_message/test_import_service_input_message.py b/tests/inputs/import_service_input_message/test_import_service_input_message.py new file mode 100644 index 0000000..66c654b --- /dev/null +++ b/tests/inputs/import_service_input_message/test_import_service_input_message.py @@ -0,0 +1,36 @@ +import pytest + +from tests.mocks import MockChannel +from tests.output_aristaproto.import_service_input_message import ( + NestedRequestMessage, + RequestMessage, + RequestResponse, + TestStub, +) +from tests.output_aristaproto.import_service_input_message.child import ( + ChildRequestMessage, +) + + +@pytest.mark.asyncio +async def test_service_correctly_imports_reference_message(): + mock_response = RequestResponse(value=10) + service = TestStub(MockChannel([mock_response])) + response = await service.do_thing(RequestMessage(1)) + assert mock_response == response + + +@pytest.mark.asyncio +async def test_service_correctly_imports_reference_message_from_child_package(): + mock_response = RequestResponse(value=10) + service = TestStub(MockChannel([mock_response])) + response = await service.do_thing2(ChildRequestMessage(1)) + assert mock_response == response + + +@pytest.mark.asyncio +async def test_service_correctly_imports_nested_reference(): + mock_response = RequestResponse(value=10) + service = TestStub(MockChannel([mock_response])) + response = await service.do_thing3(NestedRequestMessage(1)) + assert mock_response == response diff --git a/tests/inputs/int32/int32.json b/tests/inputs/int32/int32.json new file mode 100644 index 0000000..34d4111 --- /dev/null +++ b/tests/inputs/int32/int32.json @@ -0,0 +1,4 @@ +{ + "positive": 150, + "negative": -150 +} diff --git a/tests/inputs/int32/int32.proto b/tests/inputs/int32/int32.proto new file mode 100644 index 0000000..4721c23 --- /dev/null +++ b/tests/inputs/int32/int32.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package int32; + +// Some documentation about the Test message. +message Test { + // Some documentation about the count. + int32 positive = 1; + int32 negative = 2; +} diff --git a/tests/inputs/map/map.json b/tests/inputs/map/map.json new file mode 100644 index 0000000..6a1e853 --- /dev/null +++ b/tests/inputs/map/map.json @@ -0,0 +1,7 @@ +{ + "counts": { + "item1": 1, + "item2": 2, + "item3": 3 + } +} diff --git a/tests/inputs/map/map.proto b/tests/inputs/map/map.proto new file mode 100644 index 0000000..ecef3cc --- /dev/null +++ b/tests/inputs/map/map.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package map; + +message Test { + map<string, int32> counts = 1; +} diff --git a/tests/inputs/mapmessage/mapmessage.json b/tests/inputs/mapmessage/mapmessage.json new file mode 100644 index 0000000..a944ddd --- /dev/null +++ b/tests/inputs/mapmessage/mapmessage.json @@ -0,0 +1,10 @@ +{ + "items": { + "foo": { + "count": 1 + }, + "bar": { + "count": 2 + } + } +} diff --git a/tests/inputs/mapmessage/mapmessage.proto b/tests/inputs/mapmessage/mapmessage.proto new file mode 100644 index 0000000..2c704a4 --- /dev/null +++ b/tests/inputs/mapmessage/mapmessage.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package mapmessage; + +message Test { + map<string, Nested> items = 1; +} + +message Nested { + int32 count = 1; +}
\ No newline at end of file diff --git a/tests/inputs/namespace_builtin_types/namespace_builtin_types.json b/tests/inputs/namespace_builtin_types/namespace_builtin_types.json new file mode 100644 index 0000000..8200032 --- /dev/null +++ b/tests/inputs/namespace_builtin_types/namespace_builtin_types.json @@ -0,0 +1,16 @@ +{ + "int": "value-for-int", + "float": "value-for-float", + "complex": "value-for-complex", + "list": "value-for-list", + "tuple": "value-for-tuple", + "range": "value-for-range", + "str": "value-for-str", + "bytearray": "value-for-bytearray", + "bytes": "value-for-bytes", + "memoryview": "value-for-memoryview", + "set": "value-for-set", + "frozenset": "value-for-frozenset", + "map": "value-for-map", + "bool": "value-for-bool" +}
\ No newline at end of file diff --git a/tests/inputs/namespace_builtin_types/namespace_builtin_types.proto b/tests/inputs/namespace_builtin_types/namespace_builtin_types.proto new file mode 100644 index 0000000..71cb029 --- /dev/null +++ b/tests/inputs/namespace_builtin_types/namespace_builtin_types.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package namespace_builtin_types; + +// Tests that messages may contain fields with names that are python types + +message Test { + // https://docs.python.org/2/library/stdtypes.html#numeric-types-int-float-long-complex + string int = 1; + string float = 2; + string complex = 3; + + // https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range + string list = 4; + string tuple = 5; + string range = 6; + + // https://docs.python.org/3/library/stdtypes.html#str + string str = 7; + + // https://docs.python.org/3/library/stdtypes.html#bytearray-objects + string bytearray = 8; + + // https://docs.python.org/3/library/stdtypes.html#bytes-and-bytearray-operations + string bytes = 9; + + // https://docs.python.org/3/library/stdtypes.html#memory-views + string memoryview = 10; + + // https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset + string set = 11; + string frozenset = 12; + + // https://docs.python.org/3/library/stdtypes.html#dict + string map = 13; + string dict = 14; + + // https://docs.python.org/3/library/stdtypes.html#boolean-values + string bool = 15; +}
\ No newline at end of file diff --git a/tests/inputs/namespace_keywords/namespace_keywords.json b/tests/inputs/namespace_keywords/namespace_keywords.json new file mode 100644 index 0000000..4f11b60 --- /dev/null +++ b/tests/inputs/namespace_keywords/namespace_keywords.json @@ -0,0 +1,37 @@ +{ + "False": 1, + "None": 2, + "True": 3, + "and": 4, + "as": 5, + "assert": 6, + "async": 7, + "await": 8, + "break": 9, + "class": 10, + "continue": 11, + "def": 12, + "del": 13, + "elif": 14, + "else": 15, + "except": 16, + "finally": 17, + "for": 18, + "from": 19, + "global": 20, + "if": 21, + "import": 22, + "in": 23, + "is": 24, + "lambda": 25, + "nonlocal": 26, + "not": 27, + "or": 28, + "pass": 29, + "raise": 30, + "return": 31, + "try": 32, + "while": 33, + "with": 34, + "yield": 35 +} diff --git a/tests/inputs/namespace_keywords/namespace_keywords.proto b/tests/inputs/namespace_keywords/namespace_keywords.proto new file mode 100644 index 0000000..ac3e5c5 --- /dev/null +++ b/tests/inputs/namespace_keywords/namespace_keywords.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package namespace_keywords; + +// Tests that messages may contain fields that are Python keywords +// +// Generated with Python 3.7.6 +// print('\n'.join(f'string {k} = {i+1};' for i,k in enumerate(keyword.kwlist))) + +message Test { + string False = 1; + string None = 2; + string True = 3; + string and = 4; + string as = 5; + string assert = 6; + string async = 7; + string await = 8; + string break = 9; + string class = 10; + string continue = 11; + string def = 12; + string del = 13; + string elif = 14; + string else = 15; + string except = 16; + string finally = 17; + string for = 18; + string from = 19; + string global = 20; + string if = 21; + string import = 22; + string in = 23; + string is = 24; + string lambda = 25; + string nonlocal = 26; + string not = 27; + string or = 28; + string pass = 29; + string raise = 30; + string return = 31; + string try = 32; + string while = 33; + string with = 34; + string yield = 35; +}
\ No newline at end of file diff --git a/tests/inputs/nested/nested.json b/tests/inputs/nested/nested.json new file mode 100644 index 0000000..f460cad --- /dev/null +++ b/tests/inputs/nested/nested.json @@ -0,0 +1,7 @@ +{ + "nested": { + "count": 150 + }, + "sibling": {}, + "msg": "THIS" +} diff --git a/tests/inputs/nested/nested.proto b/tests/inputs/nested/nested.proto new file mode 100644 index 0000000..619c721 --- /dev/null +++ b/tests/inputs/nested/nested.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package nested; + +// A test message with a nested message inside of it. +message Test { + // This is the nested type. + message Nested { + // Stores a simple counter. + int32 count = 1; + } + // This is the nested enum. + enum Msg { + NONE = 0; + THIS = 1; + } + + Nested nested = 1; + Sibling sibling = 2; + Sibling sibling2 = 3; + Msg msg = 4; +} + +message Sibling { + int32 foo = 1; +}
\ No newline at end of file diff --git a/tests/inputs/nested2/nested2.proto b/tests/inputs/nested2/nested2.proto new file mode 100644 index 0000000..cd6510c --- /dev/null +++ b/tests/inputs/nested2/nested2.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package nested2; + +import "package.proto"; + +message Game { + message Player { + enum Race { + human = 0; + orc = 1; + } + } +} + +message Test { + Game game = 1; + Game.Player GamePlayer = 2; + Game.Player.Race GamePlayerRace = 3; + equipment.Weapon Weapon = 4; +}
\ No newline at end of file diff --git a/tests/inputs/nested2/package.proto b/tests/inputs/nested2/package.proto new file mode 100644 index 0000000..e12abb1 --- /dev/null +++ b/tests/inputs/nested2/package.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package nested2.equipment; + +message Weapon { + +}
\ No newline at end of file diff --git a/tests/inputs/nestedtwice/nestedtwice.json b/tests/inputs/nestedtwice/nestedtwice.json new file mode 100644 index 0000000..c953132 --- /dev/null +++ b/tests/inputs/nestedtwice/nestedtwice.json @@ -0,0 +1,11 @@ +{ + "top": { + "name": "double-nested", + "middle": { + "bottom": [{"foo": "hello"}], + "enumBottom": ["A"], + "topMiddleBottom": [{"a": "hello"}], + "bar": true + } + } +} diff --git a/tests/inputs/nestedtwice/nestedtwice.proto b/tests/inputs/nestedtwice/nestedtwice.proto new file mode 100644 index 0000000..84d142a --- /dev/null +++ b/tests/inputs/nestedtwice/nestedtwice.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package nestedtwice; + +/* Test doc. */ +message Test { + /* Top doc. */ + message Top { + /* Middle doc. */ + message Middle { + /* TopMiddleBottom doc.*/ + message TopMiddleBottom { + // TopMiddleBottom.a doc. + string a = 1; + } + /* EnumBottom doc. */ + enum EnumBottom{ + /* EnumBottom.A doc. */ + A = 0; + B = 1; + } + /* Bottom doc. */ + message Bottom { + /* Bottom.foo doc. */ + string foo = 1; + } + reserved 1; + /* Middle.bottom doc. */ + repeated Bottom bottom = 2; + repeated EnumBottom enumBottom=3; + repeated TopMiddleBottom topMiddleBottom=4; + bool bar = 5; + } + /* Top.name doc. */ + string name = 1; + Middle middle = 2; + } + /* Test.top doc. */ + Top top = 1; +} diff --git a/tests/inputs/nestedtwice/test_nestedtwice.py b/tests/inputs/nestedtwice/test_nestedtwice.py new file mode 100644 index 0000000..502e710 --- /dev/null +++ b/tests/inputs/nestedtwice/test_nestedtwice.py @@ -0,0 +1,25 @@ +import pytest + +from tests.output_aristaproto.nestedtwice import ( + Test, + TestTop, + TestTopMiddle, + TestTopMiddleBottom, + TestTopMiddleEnumBottom, + TestTopMiddleTopMiddleBottom, +) + + +@pytest.mark.parametrize( + ("cls", "expected_comment"), + [ + (Test, "Test doc."), + (TestTopMiddleEnumBottom, "EnumBottom doc."), + (TestTop, "Top doc."), + (TestTopMiddle, "Middle doc."), + (TestTopMiddleTopMiddleBottom, "TopMiddleBottom doc."), + (TestTopMiddleBottom, "Bottom doc."), + ], +) +def test_comment(cls, expected_comment): + assert cls.__doc__ == expected_comment diff --git a/tests/inputs/oneof/oneof-name.json b/tests/inputs/oneof/oneof-name.json new file mode 100644 index 0000000..605484b --- /dev/null +++ b/tests/inputs/oneof/oneof-name.json @@ -0,0 +1,3 @@ +{ + "pitier": "Mr. T" +} diff --git a/tests/inputs/oneof/oneof.json b/tests/inputs/oneof/oneof.json new file mode 100644 index 0000000..65cafc5 --- /dev/null +++ b/tests/inputs/oneof/oneof.json @@ -0,0 +1,3 @@ +{ + "pitied": 100 +} diff --git a/tests/inputs/oneof/oneof.proto b/tests/inputs/oneof/oneof.proto new file mode 100644 index 0000000..41f93b0 --- /dev/null +++ b/tests/inputs/oneof/oneof.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package oneof; + +message MixedDrink { + int32 shots = 1; +} + +message Test { + oneof foo { + int32 pitied = 1; + string pitier = 2; + } + + int32 just_a_regular_field = 3; + + oneof bar { + int32 drinks = 11; + string bar_name = 12; + MixedDrink mixed_drink = 13; + } +} + diff --git a/tests/inputs/oneof/oneof_name.json b/tests/inputs/oneof/oneof_name.json new file mode 100644 index 0000000..605484b --- /dev/null +++ b/tests/inputs/oneof/oneof_name.json @@ -0,0 +1,3 @@ +{ + "pitier": "Mr. T" +} diff --git a/tests/inputs/oneof/test_oneof.py b/tests/inputs/oneof/test_oneof.py new file mode 100644 index 0000000..8a38496 --- /dev/null +++ b/tests/inputs/oneof/test_oneof.py @@ -0,0 +1,43 @@ +import pytest + +import aristaproto +from tests.output_aristaproto.oneof import ( + MixedDrink, + Test, +) +from tests.output_aristaproto_pydantic.oneof import Test as TestPyd +from tests.util import get_test_case_json_data + + +def test_which_count(): + message = Test() + message.from_json(get_test_case_json_data("oneof")[0].json) + assert aristaproto.which_one_of(message, "foo") == ("pitied", 100) + + +def test_which_name(): + message = Test() + message.from_json(get_test_case_json_data("oneof", "oneof_name.json")[0].json) + assert aristaproto.which_one_of(message, "foo") == ("pitier", "Mr. T") + + +def test_which_count_pyd(): + message = TestPyd(pitier="Mr. T", just_a_regular_field=2, bar_name="a_bar") + assert aristaproto.which_one_of(message, "foo") == ("pitier", "Mr. T") + + +def test_oneof_constructor_assign(): + message = Test(mixed_drink=MixedDrink(shots=42)) + field, value = aristaproto.which_one_of(message, "bar") + assert field == "mixed_drink" + assert value.shots == 42 + + +# Issue #305: +@pytest.mark.xfail +def test_oneof_nested_assign(): + message = Test() + message.mixed_drink.shots = 42 + field, value = aristaproto.which_one_of(message, "bar") + assert field == "mixed_drink" + assert value.shots == 42 diff --git a/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto b/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto new file mode 100644 index 0000000..f7ac6fe --- /dev/null +++ b/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package oneof_default_value_serialization; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +message Message{ + int64 value = 1; +} + +message NestedMessage{ + int64 id = 1; + oneof value_type{ + Message wrapped_message_value = 2; + } +} + +message Test{ + oneof value_type { + bool bool_value = 1; + int64 int64_value = 2; + google.protobuf.Timestamp timestamp_value = 3; + google.protobuf.Duration duration_value = 4; + Message wrapped_message_value = 5; + NestedMessage wrapped_nested_message_value = 6; + google.protobuf.BoolValue wrapped_bool_value = 7; + } +}
\ No newline at end of file diff --git a/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py b/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py new file mode 100644 index 0000000..0fad3d6 --- /dev/null +++ b/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py @@ -0,0 +1,75 @@ +import datetime + +import pytest + +import aristaproto +from tests.output_aristaproto.oneof_default_value_serialization import ( + Message, + NestedMessage, + Test, +) + + +def assert_round_trip_serialization_works(message: Test) -> None: + assert aristaproto.which_one_of(message, "value_type") == aristaproto.which_one_of( + Test().from_json(message.to_json()), "value_type" + ) + + +def test_oneof_default_value_serialization_works_for_all_values(): + """ + Serialization from message with oneof set to default -> JSON -> message should keep + default value field intact. + """ + + test_cases = [ + Test(bool_value=False), + Test(int64_value=0), + Test( + timestamp_value=datetime.datetime( + year=1970, + month=1, + day=1, + hour=0, + minute=0, + tzinfo=datetime.timezone.utc, + ) + ), + Test(duration_value=datetime.timedelta(0)), + Test(wrapped_message_value=Message(value=0)), + # NOTE: Do NOT use aristaproto.BoolValue here, it will cause JSON serialization + # errors. + # TODO: Do we want to allow use of BoolValue directly within a wrapped field or + # should we simply hard fail here? + Test(wrapped_bool_value=False), + ] + for message in test_cases: + assert_round_trip_serialization_works(message) + + +def test_oneof_no_default_values_passed(): + message = Test() + assert ( + aristaproto.which_one_of(message, "value_type") + == aristaproto.which_one_of(Test().from_json(message.to_json()), "value_type") + == ("", None) + ) + + +def test_oneof_nested_oneof_messages_are_serialized_with_defaults(): + """ + Nested messages with oneofs should also be handled + """ + message = Test( + wrapped_nested_message_value=NestedMessage( + id=0, wrapped_message_value=Message(value=0) + ) + ) + assert ( + aristaproto.which_one_of(message, "value_type") + == aristaproto.which_one_of(Test().from_json(message.to_json()), "value_type") + == ( + "wrapped_nested_message_value", + NestedMessage(id=0, wrapped_message_value=Message(value=0)), + ) + ) diff --git a/tests/inputs/oneof_empty/oneof_empty.json b/tests/inputs/oneof_empty/oneof_empty.json new file mode 100644 index 0000000..9d21c89 --- /dev/null +++ b/tests/inputs/oneof_empty/oneof_empty.json @@ -0,0 +1,3 @@ +{ + "nothing": {} +} diff --git a/tests/inputs/oneof_empty/oneof_empty.proto b/tests/inputs/oneof_empty/oneof_empty.proto new file mode 100644 index 0000000..ca51d5a --- /dev/null +++ b/tests/inputs/oneof_empty/oneof_empty.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package oneof_empty; + +message Nothing {} + +message MaybeNothing { + string sometimes = 42; +} + +message Test { + oneof empty { + Nothing nothing = 1; + MaybeNothing maybe1 = 2; + MaybeNothing maybe2 = 3; + } +} diff --git a/tests/inputs/oneof_empty/oneof_empty_maybe1.json b/tests/inputs/oneof_empty/oneof_empty_maybe1.json new file mode 100644 index 0000000..f7a2d27 --- /dev/null +++ b/tests/inputs/oneof_empty/oneof_empty_maybe1.json @@ -0,0 +1,3 @@ +{ + "maybe1": {} +} diff --git a/tests/inputs/oneof_empty/oneof_empty_maybe2.json b/tests/inputs/oneof_empty/oneof_empty_maybe2.json new file mode 100644 index 0000000..bc2b385 --- /dev/null +++ b/tests/inputs/oneof_empty/oneof_empty_maybe2.json @@ -0,0 +1,5 @@ +{ + "maybe2": { + "sometimes": "now" + } +} diff --git a/tests/inputs/oneof_empty/test_oneof_empty.py b/tests/inputs/oneof_empty/test_oneof_empty.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/inputs/oneof_empty/test_oneof_empty.py diff --git a/tests/inputs/oneof_enum/oneof_enum-enum-0.json b/tests/inputs/oneof_enum/oneof_enum-enum-0.json new file mode 100644 index 0000000..be30cf0 --- /dev/null +++ b/tests/inputs/oneof_enum/oneof_enum-enum-0.json @@ -0,0 +1,3 @@ +{ + "signal": "PASS" +} diff --git a/tests/inputs/oneof_enum/oneof_enum-enum-1.json b/tests/inputs/oneof_enum/oneof_enum-enum-1.json new file mode 100644 index 0000000..cb63873 --- /dev/null +++ b/tests/inputs/oneof_enum/oneof_enum-enum-1.json @@ -0,0 +1,3 @@ +{ + "signal": "RESIGN" +} diff --git a/tests/inputs/oneof_enum/oneof_enum.json b/tests/inputs/oneof_enum/oneof_enum.json new file mode 100644 index 0000000..3220b70 --- /dev/null +++ b/tests/inputs/oneof_enum/oneof_enum.json @@ -0,0 +1,6 @@ +{ + "move": { + "x": 2, + "y": 3 + } +} diff --git a/tests/inputs/oneof_enum/oneof_enum.proto b/tests/inputs/oneof_enum/oneof_enum.proto new file mode 100644 index 0000000..906abcb --- /dev/null +++ b/tests/inputs/oneof_enum/oneof_enum.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package oneof_enum; + +message Test { + oneof action { + Signal signal = 1; + Move move = 2; + } +} + +enum Signal { + PASS = 0; + RESIGN = 1; +} + +message Move { + int32 x = 1; + int32 y = 2; +}
\ No newline at end of file diff --git a/tests/inputs/oneof_enum/test_oneof_enum.py b/tests/inputs/oneof_enum/test_oneof_enum.py new file mode 100644 index 0000000..98de22a --- /dev/null +++ b/tests/inputs/oneof_enum/test_oneof_enum.py @@ -0,0 +1,47 @@ +import pytest + +import aristaproto +from tests.output_aristaproto.oneof_enum import ( + Move, + Signal, + Test, +) +from tests.util import get_test_case_json_data + + +def test_which_one_of_returns_enum_with_default_value(): + """ + returns first field when it is enum and set with default value + """ + message = Test() + message.from_json( + get_test_case_json_data("oneof_enum", "oneof_enum-enum-0.json")[0].json + ) + + assert not hasattr(message, "move") + assert object.__getattribute__(message, "move") == aristaproto.PLACEHOLDER + assert message.signal == Signal.PASS + assert aristaproto.which_one_of(message, "action") == ("signal", Signal.PASS) + + +def test_which_one_of_returns_enum_with_non_default_value(): + """ + returns first field when it is enum and set with non default value + """ + message = Test() + message.from_json( + get_test_case_json_data("oneof_enum", "oneof_enum-enum-1.json")[0].json + ) + assert not hasattr(message, "move") + assert object.__getattribute__(message, "move") == aristaproto.PLACEHOLDER + assert message.signal == Signal.RESIGN + assert aristaproto.which_one_of(message, "action") == ("signal", Signal.RESIGN) + + +def test_which_one_of_returns_second_field_when_set(): + message = Test() + message.from_json(get_test_case_json_data("oneof_enum")[0].json) + assert message.move == Move(x=2, y=3) + assert not hasattr(message, "signal") + assert object.__getattribute__(message, "signal") == aristaproto.PLACEHOLDER + assert aristaproto.which_one_of(message, "action") == ("move", Move(x=2, y=3)) diff --git a/tests/inputs/proto3_field_presence/proto3_field_presence.json b/tests/inputs/proto3_field_presence/proto3_field_presence.json new file mode 100644 index 0000000..988df8e --- /dev/null +++ b/tests/inputs/proto3_field_presence/proto3_field_presence.json @@ -0,0 +1,13 @@ +{ + "test1": 128, + "test2": true, + "test3": "A value", + "test4": "aGVsbG8=", + "test5": { + "test": "Hello" + }, + "test6": "B", + "test7": "8589934592", + "test8": 2.5, + "test9": "2022-01-24T12:12:42Z" +} diff --git a/tests/inputs/proto3_field_presence/proto3_field_presence.proto b/tests/inputs/proto3_field_presence/proto3_field_presence.proto new file mode 100644 index 0000000..f28123d --- /dev/null +++ b/tests/inputs/proto3_field_presence/proto3_field_presence.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package proto3_field_presence; + +import "google/protobuf/timestamp.proto"; + +message InnerTest { + string test = 1; +} + +message Test { + optional uint32 test1 = 1; + optional bool test2 = 2; + optional string test3 = 3; + optional bytes test4 = 4; + optional InnerTest test5 = 5; + optional TestEnum test6 = 6; + optional uint64 test7 = 7; + optional float test8 = 8; + optional google.protobuf.Timestamp test9 = 9; +} + +enum TestEnum { + A = 0; + B = 1; +} diff --git a/tests/inputs/proto3_field_presence/proto3_field_presence_default.json b/tests/inputs/proto3_field_presence/proto3_field_presence_default.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/inputs/proto3_field_presence/proto3_field_presence_default.json @@ -0,0 +1 @@ +{} diff --git a/tests/inputs/proto3_field_presence/proto3_field_presence_missing.json b/tests/inputs/proto3_field_presence/proto3_field_presence_missing.json new file mode 100644 index 0000000..b19ae98 --- /dev/null +++ b/tests/inputs/proto3_field_presence/proto3_field_presence_missing.json @@ -0,0 +1,9 @@ +{ + "test1": 0, + "test2": false, + "test3": "", + "test4": "", + "test6": "A", + "test7": "0", + "test8": 0 +} diff --git a/tests/inputs/proto3_field_presence/test_proto3_field_presence.py b/tests/inputs/proto3_field_presence/test_proto3_field_presence.py new file mode 100644 index 0000000..80696b2 --- /dev/null +++ b/tests/inputs/proto3_field_presence/test_proto3_field_presence.py @@ -0,0 +1,48 @@ +import json + +from tests.output_aristaproto.proto3_field_presence import ( + InnerTest, + Test, + TestEnum, +) + + +def test_null_fields_json(): + """Ensure that using "null" in JSON is equivalent to not specifying a + field, for fields with explicit presence""" + + def test_json(ref_json: str, obj_json: str) -> None: + """`ref_json` and `obj_json` are JSON strings describing a `Test` object. + Test that deserializing both leads to the same object, and that + `ref_json` is the normalized format.""" + ref_obj = Test().from_json(ref_json) + obj = Test().from_json(obj_json) + + assert obj == ref_obj + assert json.loads(obj.to_json(0)) == json.loads(ref_json) + + test_json("{}", '{ "test1": null, "test2": null, "test3": null }') + test_json("{}", '{ "test4": null, "test5": null, "test6": null }') + test_json("{}", '{ "test7": null, "test8": null }') + test_json('{ "test5": {} }', '{ "test3": null, "test5": {} }') + + # Make sure that if include_default_values is set, None values are + # exported. + obj = Test() + assert obj.to_dict() == {} + assert obj.to_dict(include_default_values=True) == { + "test1": None, + "test2": None, + "test3": None, + "test4": None, + "test5": None, + "test6": None, + "test7": None, + "test8": None, + "test9": None, + } + + +def test_unset_access(): # see #523 + assert Test().test1 is None + assert Test(test1=None).test1 is None diff --git a/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.json b/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.json new file mode 100644 index 0000000..da08192 --- /dev/null +++ b/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.json @@ -0,0 +1,3 @@ +{ + "nested": {} +} diff --git a/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.proto b/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.proto new file mode 100644 index 0000000..caa76ec --- /dev/null +++ b/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package proto3_field_presence_oneof; + +message Test { + oneof kind { + Nested nested = 1; + WithOptional with_optional = 2; + } +} + +message InnerNested { + optional bool a = 1; +} + +message Nested { + InnerNested inner = 1; +} + +message WithOptional { + optional bool b = 2; +} diff --git a/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py b/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py new file mode 100644 index 0000000..f13c973 --- /dev/null +++ b/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py @@ -0,0 +1,29 @@ +from tests.output_aristaproto.proto3_field_presence_oneof import ( + InnerNested, + Nested, + Test, + WithOptional, +) + + +def test_serialization(): + """Ensure that serialization of fields unset but with explicit field + presence do not bloat the serialized payload with length-delimited fields + with length 0""" + + def test_empty_nested(message: Test) -> None: + # '0a' => tag 1, length delimited + # '00' => length: 0 + assert bytes(message) == bytearray.fromhex("0a 00") + + test_empty_nested(Test(nested=Nested())) + test_empty_nested(Test(nested=Nested(inner=None))) + test_empty_nested(Test(nested=Nested(inner=InnerNested(a=None)))) + + def test_empty_with_optional(message: Test) -> None: + # '12' => tag 2, length delimited + # '00' => length: 0 + assert bytes(message) == bytearray.fromhex("12 00") + + test_empty_with_optional(Test(with_optional=WithOptional())) + test_empty_with_optional(Test(with_optional=WithOptional(b=None))) diff --git a/tests/inputs/recursivemessage/recursivemessage.json b/tests/inputs/recursivemessage/recursivemessage.json new file mode 100644 index 0000000..e92c3fb --- /dev/null +++ b/tests/inputs/recursivemessage/recursivemessage.json @@ -0,0 +1,12 @@ +{ + "name": "Zues", + "child": { + "name": "Hercules" + }, + "intermediate": { + "child": { + "name": "Douglas Adams" + }, + "number": 42 + } +} diff --git a/tests/inputs/recursivemessage/recursivemessage.proto b/tests/inputs/recursivemessage/recursivemessage.proto new file mode 100644 index 0000000..1da2b57 --- /dev/null +++ b/tests/inputs/recursivemessage/recursivemessage.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package recursivemessage; + +message Test { + string name = 1; + Test child = 2; + Intermediate intermediate = 3; +} + + +message Intermediate { + int32 number = 1; + Test child = 2; +} diff --git a/tests/inputs/ref/ref.json b/tests/inputs/ref/ref.json new file mode 100644 index 0000000..2c6bdc1 --- /dev/null +++ b/tests/inputs/ref/ref.json @@ -0,0 +1,5 @@ +{ + "greeting": { + "greeting": "hello" + } +} diff --git a/tests/inputs/ref/ref.proto b/tests/inputs/ref/ref.proto new file mode 100644 index 0000000..6945590 --- /dev/null +++ b/tests/inputs/ref/ref.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package ref; + +import "repeatedmessage.proto"; + +message Test { + repeatedmessage.Sub greeting = 1; +} diff --git a/tests/inputs/ref/repeatedmessage.proto b/tests/inputs/ref/repeatedmessage.proto new file mode 100644 index 0000000..0ffacaf --- /dev/null +++ b/tests/inputs/ref/repeatedmessage.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package repeatedmessage; + +message Test { + repeated Sub greetings = 1; +} + +message Sub { + string greeting = 1; +}
\ No newline at end of file diff --git a/tests/inputs/regression_387/regression_387.proto b/tests/inputs/regression_387/regression_387.proto new file mode 100644 index 0000000..57bd954 --- /dev/null +++ b/tests/inputs/regression_387/regression_387.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package regression_387; + +message Test { + uint64 id = 1; +} + +message ParentElement { + string name = 1; + repeated Test elems = 2; +}
\ No newline at end of file diff --git a/tests/inputs/regression_387/test_regression_387.py b/tests/inputs/regression_387/test_regression_387.py new file mode 100644 index 0000000..92d96ba --- /dev/null +++ b/tests/inputs/regression_387/test_regression_387.py @@ -0,0 +1,12 @@ +from tests.output_aristaproto.regression_387 import ( + ParentElement, + Test, +) + + +def test_regression_387(): + el = ParentElement(name="test", elems=[Test(id=0), Test(id=42)]) + binary = bytes(el) + decoded = ParentElement().parse(binary) + assert decoded == el + assert decoded.elems == [Test(id=0), Test(id=42)] diff --git a/tests/inputs/regression_414/regression_414.proto b/tests/inputs/regression_414/regression_414.proto new file mode 100644 index 0000000..d20ddda --- /dev/null +++ b/tests/inputs/regression_414/regression_414.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package regression_414; + +message Test { + bytes body = 1; + bytes auth = 2; + repeated bytes signatures = 3; +}
\ No newline at end of file diff --git a/tests/inputs/regression_414/test_regression_414.py b/tests/inputs/regression_414/test_regression_414.py new file mode 100644 index 0000000..9441470 --- /dev/null +++ b/tests/inputs/regression_414/test_regression_414.py @@ -0,0 +1,15 @@ +from tests.output_aristaproto.regression_414 import Test + + +def test_full_cycle(): + body = bytes([0, 1]) + auth = bytes([2, 3]) + sig = [b""] + + obj = Test(body=body, auth=auth, signatures=sig) + + decoded = Test().parse(bytes(obj)) + assert decoded == obj + assert decoded.body == body + assert decoded.auth == auth + assert decoded.signatures == sig diff --git a/tests/inputs/repeated/repeated.json b/tests/inputs/repeated/repeated.json new file mode 100644 index 0000000..b8a7c4e --- /dev/null +++ b/tests/inputs/repeated/repeated.json @@ -0,0 +1,3 @@ +{ + "names": ["one", "two", "three"] +} diff --git a/tests/inputs/repeated/repeated.proto b/tests/inputs/repeated/repeated.proto new file mode 100644 index 0000000..4f3c788 --- /dev/null +++ b/tests/inputs/repeated/repeated.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package repeated; + +message Test { + repeated string names = 1; +} diff --git a/tests/inputs/repeated_duration_timestamp/repeated_duration_timestamp.json b/tests/inputs/repeated_duration_timestamp/repeated_duration_timestamp.json new file mode 100644 index 0000000..6ce7b34 --- /dev/null +++ b/tests/inputs/repeated_duration_timestamp/repeated_duration_timestamp.json @@ -0,0 +1,4 @@ +{ + "times": ["1972-01-01T10:00:20.021Z", "1972-01-01T10:00:20.021Z"], + "durations": ["1.200s", "1.200s"] +} diff --git a/tests/inputs/repeated_duration_timestamp/repeated_duration_timestamp.proto b/tests/inputs/repeated_duration_timestamp/repeated_duration_timestamp.proto new file mode 100644 index 0000000..38f1eaa --- /dev/null +++ b/tests/inputs/repeated_duration_timestamp/repeated_duration_timestamp.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package repeated_duration_timestamp; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + + +message Test { + repeated google.protobuf.Timestamp times = 1; + repeated google.protobuf.Duration durations = 2; +} diff --git a/tests/inputs/repeated_duration_timestamp/test_repeated_duration_timestamp.py b/tests/inputs/repeated_duration_timestamp/test_repeated_duration_timestamp.py new file mode 100644 index 0000000..aafc951 --- /dev/null +++ b/tests/inputs/repeated_duration_timestamp/test_repeated_duration_timestamp.py @@ -0,0 +1,12 @@ +from datetime import ( + datetime, + timedelta, +) + +from tests.output_aristaproto.repeated_duration_timestamp import Test + + +def test_roundtrip(): + message = Test() + message.times = [datetime.now(), datetime.now()] + message.durations = [timedelta(), timedelta()] diff --git a/tests/inputs/repeatedmessage/repeatedmessage.json b/tests/inputs/repeatedmessage/repeatedmessage.json new file mode 100644 index 0000000..90ec596 --- /dev/null +++ b/tests/inputs/repeatedmessage/repeatedmessage.json @@ -0,0 +1,10 @@ +{ + "greetings": [ + { + "greeting": "hello" + }, + { + "greeting": "hi" + } + ] +} diff --git a/tests/inputs/repeatedmessage/repeatedmessage.proto b/tests/inputs/repeatedmessage/repeatedmessage.proto new file mode 100644 index 0000000..0ffacaf --- /dev/null +++ b/tests/inputs/repeatedmessage/repeatedmessage.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package repeatedmessage; + +message Test { + repeated Sub greetings = 1; +} + +message Sub { + string greeting = 1; +}
\ No newline at end of file diff --git a/tests/inputs/repeatedpacked/repeatedpacked.json b/tests/inputs/repeatedpacked/repeatedpacked.json new file mode 100644 index 0000000..106fd90 --- /dev/null +++ b/tests/inputs/repeatedpacked/repeatedpacked.json @@ -0,0 +1,5 @@ +{ + "counts": [1, 2, -1, -2], + "signed": ["1", "2", "-1", "-2"], + "fixed": [1.0, 2.7, 3.4] +} diff --git a/tests/inputs/repeatedpacked/repeatedpacked.proto b/tests/inputs/repeatedpacked/repeatedpacked.proto new file mode 100644 index 0000000..a037d1b --- /dev/null +++ b/tests/inputs/repeatedpacked/repeatedpacked.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package repeatedpacked; + +message Test { + repeated int32 counts = 1; + repeated sint64 signed = 2; + repeated double fixed = 3; +} diff --git a/tests/inputs/service/service.proto b/tests/inputs/service/service.proto new file mode 100644 index 0000000..53d84fb --- /dev/null +++ b/tests/inputs/service/service.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package service; + +enum ThingType { + UNKNOWN = 0; + LIVING = 1; + DEAD = 2; +} + +message DoThingRequest { + string name = 1; + repeated string comments = 2; + ThingType type = 3; +} + +message DoThingResponse { + repeated string names = 1; +} + +message GetThingRequest { + string name = 1; +} + +message GetThingResponse { + string name = 1; + int32 version = 2; +} + +service Test { + rpc DoThing (DoThingRequest) returns (DoThingResponse); + rpc DoManyThings (stream DoThingRequest) returns (DoThingResponse); + rpc GetThingVersions (GetThingRequest) returns (stream GetThingResponse); + rpc GetDifferentThings (stream GetThingRequest) returns (stream GetThingResponse); +} diff --git a/tests/inputs/service_separate_packages/messages.proto b/tests/inputs/service_separate_packages/messages.proto new file mode 100644 index 0000000..270b188 --- /dev/null +++ b/tests/inputs/service_separate_packages/messages.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +package service_separate_packages.things.messages; + +message DoThingRequest { + string name = 1; + + // use `repeated` so we can check if `List` is correctly imported + repeated string comments = 2; + + // use google types `timestamp` and `duration` so we can check + // if everything from `datetime` is correctly imported + google.protobuf.Timestamp when = 3; + google.protobuf.Duration duration = 4; +} + +message DoThingResponse { + repeated string names = 1; +} + +message GetThingRequest { + string name = 1; +} + +message GetThingResponse { + string name = 1; + int32 version = 2; +} diff --git a/tests/inputs/service_separate_packages/service.proto b/tests/inputs/service_separate_packages/service.proto new file mode 100644 index 0000000..950eab4 --- /dev/null +++ b/tests/inputs/service_separate_packages/service.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +import "messages.proto"; + +package service_separate_packages.things.service; + +service Test { + rpc DoThing (things.messages.DoThingRequest) returns (things.messages.DoThingResponse); + rpc DoManyThings (stream things.messages.DoThingRequest) returns (things.messages.DoThingResponse); + rpc GetThingVersions (things.messages.GetThingRequest) returns (stream things.messages.GetThingResponse); + rpc GetDifferentThings (stream things.messages.GetThingRequest) returns (stream things.messages.GetThingResponse); +} diff --git a/tests/inputs/service_uppercase/service.proto b/tests/inputs/service_uppercase/service.proto new file mode 100644 index 0000000..786eec2 --- /dev/null +++ b/tests/inputs/service_uppercase/service.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package service_uppercase; + +message DoTHINGRequest { + string name = 1; + repeated string comments = 2; +} + +message DoTHINGResponse { + repeated string names = 1; +} + +service Test { + rpc DoThing (DoTHINGRequest) returns (DoTHINGResponse); +} diff --git a/tests/inputs/service_uppercase/test_service.py b/tests/inputs/service_uppercase/test_service.py new file mode 100644 index 0000000..d10fccf --- /dev/null +++ b/tests/inputs/service_uppercase/test_service.py @@ -0,0 +1,8 @@ +import inspect + +from tests.output_aristaproto.service_uppercase import TestStub + + +def test_parameters(): + sig = inspect.signature(TestStub.do_thing) + assert len(sig.parameters) == 5, "Expected 5 parameters" diff --git a/tests/inputs/signed/signed.json b/tests/inputs/signed/signed.json new file mode 100644 index 0000000..b171e15 --- /dev/null +++ b/tests/inputs/signed/signed.json @@ -0,0 +1,6 @@ +{ + "signed32": 150, + "negative32": -150, + "string64": "150", + "negative64": "-150" +} diff --git a/tests/inputs/signed/signed.proto b/tests/inputs/signed/signed.proto new file mode 100644 index 0000000..b40aad4 --- /dev/null +++ b/tests/inputs/signed/signed.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package signed; + +message Test { + // todo: rename fields after fixing bug where 'signed_32_positive' will map to 'signed_32Positive' as output json + sint32 signed32 = 1; // signed_32_positive + sint32 negative32 = 2; // signed_32_negative + sint64 string64 = 3; // signed_64_positive + sint64 negative64 = 4; // signed_64_negative +} diff --git a/tests/inputs/timestamp_dict_encode/test_timestamp_dict_encode.py b/tests/inputs/timestamp_dict_encode/test_timestamp_dict_encode.py new file mode 100644 index 0000000..59be3d1 --- /dev/null +++ b/tests/inputs/timestamp_dict_encode/test_timestamp_dict_encode.py @@ -0,0 +1,82 @@ +from datetime import ( + datetime, + timedelta, + timezone, +) + +import pytest + +from tests.output_aristaproto.timestamp_dict_encode import Test + + +# Current World Timezone range (UTC-12 to UTC+14) +MIN_UTC_OFFSET_MIN = -12 * 60 +MAX_UTC_OFFSET_MIN = 14 * 60 + +# Generate all timezones in range in 15 min increments +timezones = [ + timezone(timedelta(minutes=x)) + for x in range(MIN_UTC_OFFSET_MIN, MAX_UTC_OFFSET_MIN + 1, 15) +] + + +@pytest.mark.parametrize("tz", timezones) +def test_timezone_aware_datetime_dict_encode(tz: timezone): + original_time = datetime.now(tz=tz) + original_message = Test() + original_message.ts = original_time + encoded = original_message.to_dict() + decoded_message = Test() + decoded_message.from_dict(encoded) + + # check that the timestamps are equal after decoding from dict + assert original_message.ts.tzinfo is not None + assert decoded_message.ts.tzinfo is not None + assert original_message.ts == decoded_message.ts + + +def test_naive_datetime_dict_encode(): + # make suer naive datetime objects are still treated as utc + original_time = datetime.now() + assert original_time.tzinfo is None + original_message = Test() + original_message.ts = original_time + original_time_utc = original_time.replace(tzinfo=timezone.utc) + encoded = original_message.to_dict() + decoded_message = Test() + decoded_message.from_dict(encoded) + + # check that the timestamps are equal after decoding from dict + assert decoded_message.ts.tzinfo is not None + assert original_time_utc == decoded_message.ts + + +@pytest.mark.parametrize("tz", timezones) +def test_timezone_aware_json_serialize(tz: timezone): + original_time = datetime.now(tz=tz) + original_message = Test() + original_message.ts = original_time + json_serialized = original_message.to_json() + decoded_message = Test() + decoded_message.from_json(json_serialized) + + # check that the timestamps are equal after decoding from dict + assert original_message.ts.tzinfo is not None + assert decoded_message.ts.tzinfo is not None + assert original_message.ts == decoded_message.ts + + +def test_naive_datetime_json_serialize(): + # make suer naive datetime objects are still treated as utc + original_time = datetime.now() + assert original_time.tzinfo is None + original_message = Test() + original_message.ts = original_time + original_time_utc = original_time.replace(tzinfo=timezone.utc) + json_serialized = original_message.to_json() + decoded_message = Test() + decoded_message.from_json(json_serialized) + + # check that the timestamps are equal after decoding from dict + assert decoded_message.ts.tzinfo is not None + assert original_time_utc == decoded_message.ts diff --git a/tests/inputs/timestamp_dict_encode/timestamp_dict_encode.json b/tests/inputs/timestamp_dict_encode/timestamp_dict_encode.json new file mode 100644 index 0000000..3f45558 --- /dev/null +++ b/tests/inputs/timestamp_dict_encode/timestamp_dict_encode.json @@ -0,0 +1,3 @@ +{ + "ts" : "2023-03-15T22:35:51.253277Z" +}
\ No newline at end of file diff --git a/tests/inputs/timestamp_dict_encode/timestamp_dict_encode.proto b/tests/inputs/timestamp_dict_encode/timestamp_dict_encode.proto new file mode 100644 index 0000000..9c4081a --- /dev/null +++ b/tests/inputs/timestamp_dict_encode/timestamp_dict_encode.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package timestamp_dict_encode; + +import "google/protobuf/timestamp.proto"; + +message Test { + google.protobuf.Timestamp ts = 1; +}
\ No newline at end of file |