summaryrefslogtreecommitdiffstats
path: root/tests/inputs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/inputs/bool/bool.json3
-rw-r--r--tests/inputs/bool/bool.proto7
-rw-r--r--tests/inputs/bool/test_bool.py19
-rw-r--r--tests/inputs/bytes/bytes.json3
-rw-r--r--tests/inputs/bytes/bytes.proto7
-rw-r--r--tests/inputs/casing/casing.json4
-rw-r--r--tests/inputs/casing/casing.proto20
-rw-r--r--tests/inputs/casing/test_casing.py23
-rw-r--r--tests/inputs/casing_inner_class/casing_inner_class.proto10
-rw-r--r--tests/inputs/casing_inner_class/test_casing_inner_class.py14
-rw-r--r--tests/inputs/casing_message_field_uppercase/casing_message_field_uppercase.proto9
-rw-r--r--tests/inputs/casing_message_field_uppercase/casing_message_field_uppercase.py14
-rw-r--r--tests/inputs/config.py30
-rw-r--r--tests/inputs/deprecated/deprecated.json6
-rw-r--r--tests/inputs/deprecated/deprecated.proto14
-rw-r--r--tests/inputs/double/double-negative.json3
-rw-r--r--tests/inputs/double/double.json3
-rw-r--r--tests/inputs/double/double.proto7
-rw-r--r--tests/inputs/empty_repeated/empty_repeated.json3
-rw-r--r--tests/inputs/empty_repeated/empty_repeated.proto11
-rw-r--r--tests/inputs/empty_service/empty_service.proto7
-rw-r--r--tests/inputs/entry/entry.proto20
-rw-r--r--tests/inputs/enum/enum.json9
-rw-r--r--tests/inputs/enum/enum.proto25
-rw-r--r--tests/inputs/enum/test_enum.py114
-rw-r--r--tests/inputs/example/example.proto911
-rw-r--r--tests/inputs/example_service/example_service.proto20
-rw-r--r--tests/inputs/example_service/test_example_service.py86
-rw-r--r--tests/inputs/field_name_identical_to_type/field_name_identical_to_type.json7
-rw-r--r--tests/inputs/field_name_identical_to_type/field_name_identical_to_type.proto13
-rw-r--r--tests/inputs/fixed/fixed.json6
-rw-r--r--tests/inputs/fixed/fixed.proto10
-rw-r--r--tests/inputs/float/float.json9
-rw-r--r--tests/inputs/float/float.proto14
-rw-r--r--tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto22
-rw-r--r--tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py93
-rw-r--r--tests/inputs/googletypes/googletypes-missing.json1
-rw-r--r--tests/inputs/googletypes/googletypes.json7
-rw-r--r--tests/inputs/googletypes/googletypes.proto16
-rw-r--r--tests/inputs/googletypes_request/googletypes_request.proto29
-rw-r--r--tests/inputs/googletypes_request/test_googletypes_request.py47
-rw-r--r--tests/inputs/googletypes_response/googletypes_response.proto23
-rw-r--r--tests/inputs/googletypes_response/test_googletypes_response.py64
-rw-r--r--tests/inputs/googletypes_response_embedded/googletypes_response_embedded.proto26
-rw-r--r--tests/inputs/googletypes_response_embedded/test_googletypes_response_embedded.py40
-rw-r--r--tests/inputs/googletypes_service_returns_empty/googletypes_service_returns_empty.proto13
-rw-r--r--tests/inputs/googletypes_service_returns_googletype/googletypes_service_returns_googletype.proto18
-rw-r--r--tests/inputs/googletypes_struct/googletypes_struct.json5
-rw-r--r--tests/inputs/googletypes_struct/googletypes_struct.proto9
-rw-r--r--tests/inputs/googletypes_value/googletypes_value.json11
-rw-r--r--tests/inputs/googletypes_value/googletypes_value.proto15
-rw-r--r--tests/inputs/import_capitalized_package/capitalized.proto8
-rw-r--r--tests/inputs/import_capitalized_package/test.proto11
-rw-r--r--tests/inputs/import_child_package_from_package/child.proto7
-rw-r--r--tests/inputs/import_child_package_from_package/import_child_package_from_package.proto11
-rw-r--r--tests/inputs/import_child_package_from_package/package_message.proto9
-rw-r--r--tests/inputs/import_child_package_from_root/child.proto7
-rw-r--r--tests/inputs/import_child_package_from_root/import_child_package_from_root.proto11
-rw-r--r--tests/inputs/import_circular_dependency/import_circular_dependency.proto30
-rw-r--r--tests/inputs/import_circular_dependency/other.proto8
-rw-r--r--tests/inputs/import_circular_dependency/root.proto7
-rw-r--r--tests/inputs/import_cousin_package/cousin.proto6
-rw-r--r--tests/inputs/import_cousin_package/test.proto11
-rw-r--r--tests/inputs/import_cousin_package_same_name/cousin.proto6
-rw-r--r--tests/inputs/import_cousin_package_same_name/test.proto11
-rw-r--r--tests/inputs/import_packages_same_name/import_packages_same_name.proto13
-rw-r--r--tests/inputs/import_packages_same_name/posts_v1.proto7
-rw-r--r--tests/inputs/import_packages_same_name/users_v1.proto7
-rw-r--r--tests/inputs/import_parent_package_from_child/import_parent_package_from_child.proto12
-rw-r--r--tests/inputs/import_parent_package_from_child/parent_package_message.proto6
-rw-r--r--tests/inputs/import_root_package_from_child/child.proto11
-rw-r--r--tests/inputs/import_root_package_from_child/root.proto7
-rw-r--r--tests/inputs/import_root_sibling/import_root_sibling.proto11
-rw-r--r--tests/inputs/import_root_sibling/sibling.proto7
-rw-r--r--tests/inputs/import_service_input_message/child_package_request_message.proto7
-rw-r--r--tests/inputs/import_service_input_message/import_service_input_message.proto25
-rw-r--r--tests/inputs/import_service_input_message/request_message.proto7
-rw-r--r--tests/inputs/import_service_input_message/test_import_service_input_message.py36
-rw-r--r--tests/inputs/int32/int32.json4
-rw-r--r--tests/inputs/int32/int32.proto10
-rw-r--r--tests/inputs/map/map.json7
-rw-r--r--tests/inputs/map/map.proto7
-rw-r--r--tests/inputs/mapmessage/mapmessage.json10
-rw-r--r--tests/inputs/mapmessage/mapmessage.proto11
-rw-r--r--tests/inputs/namespace_builtin_types/namespace_builtin_types.json16
-rw-r--r--tests/inputs/namespace_builtin_types/namespace_builtin_types.proto40
-rw-r--r--tests/inputs/namespace_keywords/namespace_keywords.json37
-rw-r--r--tests/inputs/namespace_keywords/namespace_keywords.proto46
-rw-r--r--tests/inputs/nested/nested.json7
-rw-r--r--tests/inputs/nested/nested.proto26
-rw-r--r--tests/inputs/nested2/nested2.proto21
-rw-r--r--tests/inputs/nested2/package.proto7
-rw-r--r--tests/inputs/nestedtwice/nestedtwice.json11
-rw-r--r--tests/inputs/nestedtwice/nestedtwice.proto40
-rw-r--r--tests/inputs/nestedtwice/test_nestedtwice.py25
-rw-r--r--tests/inputs/oneof/oneof-name.json3
-rw-r--r--tests/inputs/oneof/oneof.json3
-rw-r--r--tests/inputs/oneof/oneof.proto23
-rw-r--r--tests/inputs/oneof/oneof_name.json3
-rw-r--r--tests/inputs/oneof/test_oneof.py43
-rw-r--r--tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto30
-rw-r--r--tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py75
-rw-r--r--tests/inputs/oneof_empty/oneof_empty.json3
-rw-r--r--tests/inputs/oneof_empty/oneof_empty.proto17
-rw-r--r--tests/inputs/oneof_empty/oneof_empty_maybe1.json3
-rw-r--r--tests/inputs/oneof_empty/oneof_empty_maybe2.json5
-rw-r--r--tests/inputs/oneof_empty/test_oneof_empty.py0
-rw-r--r--tests/inputs/oneof_enum/oneof_enum-enum-0.json3
-rw-r--r--tests/inputs/oneof_enum/oneof_enum-enum-1.json3
-rw-r--r--tests/inputs/oneof_enum/oneof_enum.json6
-rw-r--r--tests/inputs/oneof_enum/oneof_enum.proto20
-rw-r--r--tests/inputs/oneof_enum/test_oneof_enum.py47
-rw-r--r--tests/inputs/proto3_field_presence/proto3_field_presence.json13
-rw-r--r--tests/inputs/proto3_field_presence/proto3_field_presence.proto26
-rw-r--r--tests/inputs/proto3_field_presence/proto3_field_presence_default.json1
-rw-r--r--tests/inputs/proto3_field_presence/proto3_field_presence_missing.json9
-rw-r--r--tests/inputs/proto3_field_presence/test_proto3_field_presence.py48
-rw-r--r--tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.json3
-rw-r--r--tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.proto22
-rw-r--r--tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py29
-rw-r--r--tests/inputs/recursivemessage/recursivemessage.json12
-rw-r--r--tests/inputs/recursivemessage/recursivemessage.proto15
-rw-r--r--tests/inputs/ref/ref.json5
-rw-r--r--tests/inputs/ref/ref.proto9
-rw-r--r--tests/inputs/ref/repeatedmessage.proto11
-rw-r--r--tests/inputs/regression_387/regression_387.proto12
-rw-r--r--tests/inputs/regression_387/test_regression_387.py12
-rw-r--r--tests/inputs/regression_414/regression_414.proto9
-rw-r--r--tests/inputs/regression_414/test_regression_414.py15
-rw-r--r--tests/inputs/repeated/repeated.json3
-rw-r--r--tests/inputs/repeated/repeated.proto7
-rw-r--r--tests/inputs/repeated_duration_timestamp/repeated_duration_timestamp.json4
-rw-r--r--tests/inputs/repeated_duration_timestamp/repeated_duration_timestamp.proto12
-rw-r--r--tests/inputs/repeated_duration_timestamp/test_repeated_duration_timestamp.py12
-rw-r--r--tests/inputs/repeatedmessage/repeatedmessage.json10
-rw-r--r--tests/inputs/repeatedmessage/repeatedmessage.proto11
-rw-r--r--tests/inputs/repeatedpacked/repeatedpacked.json5
-rw-r--r--tests/inputs/repeatedpacked/repeatedpacked.proto9
-rw-r--r--tests/inputs/service/service.proto35
-rw-r--r--tests/inputs/service_separate_packages/messages.proto31
-rw-r--r--tests/inputs/service_separate_packages/service.proto12
-rw-r--r--tests/inputs/service_uppercase/service.proto16
-rw-r--r--tests/inputs/service_uppercase/test_service.py8
-rw-r--r--tests/inputs/signed/signed.json6
-rw-r--r--tests/inputs/signed/signed.proto11
-rw-r--r--tests/inputs/timestamp_dict_encode/test_timestamp_dict_encode.py82
-rw-r--r--tests/inputs/timestamp_dict_encode/timestamp_dict_encode.json3
-rw-r--r--tests/inputs/timestamp_dict_encode/timestamp_dict_encode.proto9
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