def test_resource_reference_removal_non_breaking1(self):
        # Removed resource reference is defined by type, and it is
        # added back to the message options.
        # Original field has resource reference `example.v1/Foo`.
        field_options = make_field_annotation_resource_reference(
            resource_type="example.v1/Foo", is_child_type=False
        )
        field_with_reference = make_field(name="Test", options=field_options)
        # Update field has no resource reference. But the message has
        # resource options `example.v1/Foo`.
        message_resource = make_resource_descriptor(
            resource_type="example.v1/Foo", resource_patterns=["bar/{bar}"]
        )

        field_without_reference = make_field(
            name="Test", message_resource=message_resource
        )
        FieldComparator(
            field_with_reference,
            field_without_reference,
            self.finding_container,
            context="ctx",
        ).compare()
        finding = self.finding_container.get_all_findings()[0]
        self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_MOVED")
        self.assertEqual(finding.change_type.name, "MINOR")
 def test_resource_reference_removal_non_breaking2(self):
     # Removed resource reference is defined by child type, and it
     # can be resolved to the same resource with the message options.
     # Original field has resource reference `example.v1/Foo`.
     field_options = make_field_annotation_resource_reference(
         resource_type="example.v1/Foo", is_child_type=False
     )
     field_with_reference = make_field(name="Test", options=field_options)
     # Update field has no resource reference. But the message has
     # resource options `example.v1/Foo`.
     message_resource = make_resource_descriptor(
         resource_type="example.v1/Foo", resource_patterns=["bar/{bar}"]
     )
     field_resource = make_resource_descriptor(
         resource_type="example.v1/Foo", resource_patterns=["bar/{bar}/foo/{foo}"]
     )
     # Register the two resources in the database.
     resource_database = make_resource_database(
         resources=[message_resource, field_resource]
     )
     field_without_reference = make_field(
         name="Test",
         message_resource=message_resource,
         resource_database=resource_database,
     )
     # `bar/{bar}` is the parent resource of `bar/{bar}/foo/{foo}`.
     FieldComparator(
         field_with_reference,
         field_without_reference,
         self.finding_container,
         context="ctx",
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_MOVED")
     self.assertEqual(finding.change_type.name, "MINOR")
示例#3
0
 def test_nested_map_entries(self):
     # Create the inner message.
     inner_msg = [
         make_message(
             "FieldEntry",
             fields=(
                 make_field(
                     name="key",
                     number=1,
                 ),
                 make_field(
                     name="value",
                     number=2,
                 ),
             ),
             map_entry=True,
         ),
     ]
     field = make_field("field",
                        type_name="FieldEntry",
                        repeated=True,
                        number=1)
     outer_msg = make_message(fields=[field], nested_messages=inner_msg)
     self.assertEqual(list(outer_msg.map_entries.keys()), ["FieldEntry"])
     self.assertTrue(outer_msg.map_entries["FieldEntry"]["key"])
     self.assertTrue(outer_msg.map_entries["FieldEntry"]["value"])
     self.assertFalse(outer_msg.nested_messages)
     self.assertTrue(outer_msg.fields[1].map_entry)
示例#4
0
    def test_nested_types(self):
        # Create the inner message.
        inner_msg = [
            make_message(
                "InnerMessage",
                fields=(make_field(
                    name="hidden_message",
                    number=1,
                ), ),
            )
        ]
        inner_enums = [make_enum(name="InnerEnum")]
        inner_fields = [make_field("not_interesting")]

        # Create the outer message, which contains inner fields, enums, messages.
        outer_msg = make_message(
            "Outer",
            fields=inner_fields,
            nested_enums=inner_enums,
            nested_messages=inner_msg,
        )

        self.assertEqual(len(outer_msg.fields.keys()), 1)
        self.assertEqual(outer_msg.fields[1].name, "not_interesting")
        self.assertEqual(len(outer_msg.nested_enums.keys()), 1)
        self.assertEqual(
            outer_msg.nested_messages["InnerMessage"].fields[1].name,
            "hidden_message")
 def test_resource_reference_child_type_addition_non_breaking(self):
     # The added resource reference is in the database. Non-breaking change.
     # The original field is without resource reference.
     field_without_reference = make_field(name="Test")
     # Create a database with resource `example.v1/Foo` registered.
     resource = make_resource_descriptor(
         resource_type="example.v1/Foo", resource_patterns=["foo/{foo}"]
     )
     resource_child = make_resource_descriptor(
         resource_type="example.v1/Bar",
         resource_patterns=["foo/{foo}/bar/{bar}"],
     )
     resource_database = make_resource_database(resources=[resource, resource_child])
     # The update field has resource reference of child_type `example.v1/Bar`.
     field_options = desc.FieldOptions()
     field_options.Extensions[
         resource_pb2.resource_reference
     ].child_type = "example.v1/Bar"
     field_with_reference = make_field(
         name="Test", options=field_options, resource_database=resource_database
     )
     FieldComparator(
         field_without_reference,
         field_with_reference,
         self.finding_container,
         context="ctx",
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_ADDITION")
     self.assertEqual(finding.change_type.name, "MINOR")
 def test_resource_reference_removal_breaking2(self):
     # Removed resource reference is defined by type, which is not identical
     # with the message options.
     # Original field has resource reference `example.v1/Foo`.
     field_options = make_field_annotation_resource_reference(
         resource_type="example.v1/Foo", is_child_type=False
     )
     field_with_reference = make_field(name="Test", options=field_options)
     # Update field has no resource reference, and the resource type
     # is different from the message options type.
     message_resource = make_resource_descriptor(
         resource_type="NotInteresting", resource_patterns=["NotInteresting"]
     )
     field_without_reference = make_field(
         name="Test", message_resource=message_resource
     )
     FieldComparator(
         field_with_reference,
         field_without_reference,
         self.finding_container,
         context="ctx",
     ).compare()
     finding = self.finding_container.get_actionable_findings()[0]
     self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_REMOVAL")
     self.assertEqual(finding.change_type.name, "MAJOR")
示例#7
0
 def test_method_no_page_field(self):
     # No repeated field in the response message.
     field_next_page_token = make_field(
         name="next_page_token",
         proto_type="TYPE_STRING",
         number=2,
     )
     response_message = make_message(
         name="ResponseMessage",
         fields=[field_next_page_token],
     )
     field_page_size = make_field(
         name="page_size",
         proto_type="TYPE_INT32",
         number=1,
     )
     field_page_token = make_field(
         name="page_token",
         proto_type="TYPE_STRING",
         number=2,
     )
     request_message = make_message(
         name="RequestMessage",
         fields=[field_page_size, field_page_token],
     )
     messages_map = {
         "ResponseMessage": response_message,
         "RequestMessage": request_message,
     }
     method = make_method(
         name="PagedMethod",
         input_message=request_message,
         output_message=response_message,
         messages_map=messages_map,
     )
     self.assertEqual(method.paged_result_field, None)
     # Missing `page_size` in request message.
     request_message_without_page_size = make_message(
         name="RequestMessage",
         fields=[field_page_size],
     )
     method = make_method(
         name="NoPagedMethod",
         input_message=request_message_without_page_size,
         output_message=response_message,
         messages_map=messages_map,
     )
     self.assertEqual(method.paged_result_field, None)
     # Missing `next_page_token` in response message.
     response_message_without_next_page_token = make_message(
         name="ResponseMessage",
     )
     method = make_method(
         name="NoPagedMethod",
         input_message=request_message,
         output_message=response_message_without_next_page_token,
         messages_map=messages_map,
     )
     self.assertEqual(method.paged_result_field, None)
 def test_primitive_type_change(self):
     field_int = make_field(proto_type="TYPE_INT32")
     field_string = make_field(proto_type="TYPE_STRING")
     FieldComparator(
         field_int, field_string, self.finding_container, context="ctx"
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "FIELD_TYPE_CHANGE")
 def test_repeated_label_change(self):
     field_repeated = make_field(repeated=True)
     field_non_repeated = make_field(repeated=False)
     FieldComparator(
         field_repeated, field_non_repeated, self.finding_container, context="ctx"
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "FIELD_REPEATED_CHANGE")
 def test_message_type_change(self):
     field_message = make_field(type_name=".example.v1.Enum")
     field_message_update = make_field(type_name=".example.v1beta1.EnumUpdate")
     FieldComparator(
         field_message, field_message_update, self.finding_container, context="ctx"
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "FIELD_TYPE_CHANGE")
 def test_name_change(self):
     field_foo = make_field("Foo", nested_path=["foo"])
     field_bar = make_field("Bar", nested_path=["bar"])
     FieldComparator(
         field_foo, field_bar, self.finding_container, context="ctx"
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "FIELD_NAME_CHANGE")
     self.assertEqual(finding.extra_info[0], "foo")
示例#12
0
 def test_get_field(self):
     fields = (
         make_field(name="field_one", number=1),
         make_field(name="field_two", number=2),
     )
     message = make_message("Message", fields=fields)
     field_one = message.fields[1]
     self.assertIsInstance(field_one, wrappers.Field)
     self.assertEqual(field_one.name, "field_one")
 def test_message_type_change_minor_version_update(self):
     field_message = make_field(type_name=".example.v1.Enum", api_version="v1")
     field_message_update = make_field(
         type_name=".example.v1beta1.Enum", api_version="v1beta1"
     )
     FieldComparator(
         field_message, field_message_update, self.finding_container, context="ctx"
     ).compare()
     findings = self.finding_container.get_all_findings()
     self.assertFalse(findings)
    def test_resource_reference_change_type_conversion_non_breaking(self):
        child_resource = make_resource_descriptor(
            resource_type="example.v1/Foo",
            resource_patterns=["bar/{bar}/foo/{foo}", "bar/{bar}/foo"],
        )
        parent_resource = make_resource_descriptor(
            resource_type="example.v1/Bar", resource_patterns=["bar/{bar}"]
        )
        # Register two resources in database.
        resource_database = make_resource_database(
            resources=[child_resource, parent_resource]
        )
        # The original field is defined by child type.
        field_options_child = make_field_annotation_resource_reference(
            resource_type="example.v1/Foo", is_child_type=True
        )
        field_with_reference_child = make_field(
            name="Test",
            options=field_options_child,
            resource_database=resource_database,
        )

        # The update field is defined by parent type.
        field_options_parent = make_field_annotation_resource_reference(
            resource_type="example.v1/Bar", is_child_type=False
        )
        field_with_reference_parent = make_field(
            name="Test",
            options=field_options_parent,
            resource_database=resource_database,
        )
        # The two resources can be resolved to the identical resource.
        FieldComparator(
            field_with_reference_child,
            field_with_reference_parent,
            self.finding_container,
            context="ctx",
        ).compare()
        finding = self.finding_container.get_all_findings()
        # No breaking change should be detected.
        self.assertFalse(finding)

        # Reverse should be same since the two resources can
        # be resolved to the identical resource.
        FieldComparator(
            field_with_reference_parent,
            field_with_reference_child,
            self.finding_container,
            context="ctx",
        ).compare()
        finding = self.finding_container.get_all_findings()
        # No breaking change should be detected.
        self.assertFalse(finding)
 def test_into_oneof(self):
     field_oneof = make_field(name="Foo", oneof_index=0, oneof_name="oneof_field")
     field_not_oneof = make_field(name="Foo")
     FieldComparator(
         field_not_oneof, field_oneof, self.finding_container, context="ctx"
     ).compare()
     finding = next(
         f
         for f in self.finding_container.get_all_findings()
         if f.category.name == "FIELD_ONEOF_MOVE_IN"
     )
     self.assertTrue(finding)
 def test_proto3_required_to_optional(self):
     # Change required field to be proto3 optional. Non-breaking change.
     field_optional = make_field(
         name="Foo", oneof_index=0, oneof_name="oneof_field", proto3_optional=True
     )
     field_not_optional = make_field(
         name="Foo", oneof_index=0, oneof_name="oneof_field"
     )
     FieldComparator(
         field_not_optional, field_optional, self.finding_container, context="ctx"
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "FIELD_PROTO3_OPTIONAL_CHANGE")
 def test_nested_field_type_change(self):
     field_int = make_field(proto_type="TYPE_INT32")
     field_string = make_field(proto_type="TYPE_STRING")
     message1 = make_message(fields=[field_int])
     message2 = make_message(fields=[field_string])
     DescriptorComparator(message1,
                          message2,
                          self.finding_container,
                          context="ctx").compare()
     finding = next(f for f in self.finding_container.get_all_findings()
                    if f.category.name == "FIELD_TYPE_CHANGE")
     self.assertEqual(finding.change_type.name, "MAJOR")
     self.assertEqual(finding.location.proto_file_name, "foo")
示例#18
0
 def test_message_change_breaking(self):
     message_original = make_message(
         fields=(make_field(name="field_one", number=1), ))
     message_update = make_message(
         fields=(make_field(name="field_two", number=1), ))
     FileSetComparator(
         make_file_set(files=[make_file_pb2(messages=[message_original])]),
         make_file_set(files=[make_file_pb2(messages=[message_update])]),
         self.finding_container,
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.change_type.name, "MAJOR")
     self.assertEqual(finding.category.name, "FIELD_NAME_CHANGE")
     self.assertEqual(finding.location.proto_file_name, "my_proto.proto")
 def test_field_behavior_change(self):
     field_required = make_field(required=True)
     field_non_required = make_field(required=False)
     # Required to optional, non-breaking change.
     FieldComparator(
         field_required, field_non_required, self.finding_container, context="ctx"
     ).compare()
     findings = self.finding_container.get_all_findings()
     self.assertFalse(findings)
     # Required to optional, non-breaking change.
     FieldComparator(
         field_non_required, field_required, self.finding_container, context="ctx"
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "FIELD_BEHAVIOR_CHANGE")
 def test_field_addition(self):
     field_foo = make_field("Foo")
     FieldComparator(
         None, field_foo, self.finding_container, context="ctx"
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "FIELD_ADDITION")
 def test_nested_field_type_change(self):
     field_int = make_field(proto_type="TYPE_INT32")
     field_string = make_field(proto_type="TYPE_STRING")
     message1 = make_message(fields=[field_int])
     message2 = make_message(fields=[field_string])
     DescriptorComparator(message1, message2,
                          self.finding_container).compare()
     findings_map = {
         f.message: f
         for f in self.finding_container.getAllFindings()
     }
     finding = findings_map[
         "Type of an existing field `my_field` is changed from `int32` to `string`."]
     self.assertEqual(finding.category.name, "FIELD_TYPE_CHANGE")
     self.assertEqual(finding.change_type.name, "MAJOR")
     self.assertEqual(finding.location.proto_file_name, "foo")
示例#22
0
 def test_enum_in_dependency_change_breaking(self):
     # Enum "dep_message" is imported from dep.proto and referenced as a field type.
     field_type_original = make_enum(
         name="dep_enum",
         proto_file_name="dep.proto",
     )
     message_original = make_message(
         fields=[make_field(type_name=".test.import.dep_enum")], )
     # Message "test_enum" is defined in update.proto referenced as a field type.
     field_type_update = make_enum(name="test_enum", )
     message_update = make_message(
         fields=[make_field(type_name="test_enum")])
     FileSetComparator(
         make_file_set(files=[
             make_file_pb2(
                 name="orignal.proto",
                 messages=[message_original],
                 dependency="test/import/dep.proto",
                 package="example.v1",
             ),
             make_file_pb2(
                 name="dep.proto",
                 enums=[field_type_original],
                 package="test.import",
             ),
         ]),
         make_file_set(files=[
             make_file_pb2(
                 name="update.proto",
                 messages=[message_update],
                 enums=[field_type_update],
                 package="example.v1beta1",
             )
         ]),
         self.finding_container,
     ).compare()
     # The breaking change should be in field level, instead of message removal,
     # since the message is imported from dependency file.
     finding = self.finding_container.getAllFindings()[0]
     self.assertEqual(
         finding.message,
         "Type of an existing field `my_field` is changed from `.test.import.dep_enum` to `test_enum`.",
     )
     self.assertEqual(finding.change_type.name, "MAJOR")
     self.assertEqual(finding.category.name, "FIELD_TYPE_CHANGE")
     self.assertEqual(finding.location.proto_file_name, "update.proto")
 def test_field_removal(self):
     field_foo = make_field("Foo")
     FieldComparator(
         field_foo, None, self.finding_container, context="ctx"
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "FIELD_REMOVAL")
     self.assertEqual(finding.location.proto_file_name, "foo")
 def test_resource_reference_addition_breaking(self):
     # The added resource reference is not in the database. Breaking change.
     # The original field is without resource reference.
     field_without_reference = make_field(name="Test")
     # The update field has resource reference, but it does not exist
     # in the global database.
     field_options = make_field_annotation_resource_reference(
         resource_type="example.v1/Foo", is_child_type=False
     )
     field_with_reference = make_field(name="Test", options=field_options)
     FieldComparator(
         field_without_reference,
         field_with_reference,
         self.finding_container,
         context="ctx",
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_ADDITION")
 def test_nested_field_addition(self):
     field_int = make_field(proto_type="TYPE_INT32")
     message_update = make_message(fields=[field_int])
     DescriptorComparator(make_message(), message_update,
                          self.finding_container).compare()
     finding = self.finding_container.getAllFindings()[0]
     self.assertEqual(finding.category.name, "FIELD_ADDITION")
     self.assertEqual(finding.change_type.name, "MINOR")
     self.assertEqual(finding.location.proto_file_name, "foo")
示例#26
0
 def test_method_paged(self):
     field_next_page_token = make_field(
         name="next_page_token",
         proto_type="TYPE_STRING",
         number=2,
     )
     field_repeated = make_field(
         name="repeated_field",
         proto_type="TYPE_STRING",
         repeated=True,
         number=1,
     )
     response_message = make_message(
         name="ResponseMessage",
         fields=[field_repeated, field_next_page_token],
         full_name=".example.v1.ResponseMessage",
     )
     field_page_size = make_field(
         name="page_size",
         proto_type="TYPE_INT32",
         number=1,
     )
     field_page_token = make_field(
         name="page_token",
         proto_type="TYPE_STRING",
         number=2,
     )
     request_message = make_message(
         name="RequestMessage",
         fields=[field_page_size, field_page_token],
         full_name=".example.v1.RequestMessage",
     )
     messages_map = {
         ".example.v1.ResponseMessage": response_message,
         ".example.v1.RequestMessage": request_message,
     }
     method = make_method(
         name="PagedMethod",
         input_message=request_message,
         output_message=response_message,
         messages_map=messages_map,
     )
     self.assertEqual(method.paged_result_field.name, "repeated_field")
     self.assertEqual(method.longrunning, False)
 def test_resource_reference_removal_breaking1(self):
     # Removed resource reference is not added in message options. Breaking.
     # Original field has resource reference `example.v1/Foo`.
     field_options = make_field_annotation_resource_reference(
         resource_type="example.v1/Foo", is_child_type=False
     )
     field_with_reference = make_field(name="Test", options=field_options)
     # The update field has no resource reference, and no resource reference is
     # defined in the message.
     field_without_reference = make_field(name="Test")
     FieldComparator(
         field_with_reference,
         field_without_reference,
         self.finding_container,
         context="ctx",
     ).compare()
     finding = self.finding_container.get_actionable_findings()[0]
     self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_REMOVAL")
     self.assertEqual(finding.change_type.name, "MAJOR")
 def test_resource_reference_change_same_type_breaking(self):
     # Both fields have resource reference identified by type.
     # But the type value is different. Breaking change.
     field_options_foo = make_field_annotation_resource_reference(
         resource_type="example.v1/Foo", is_child_type=False
     )
     field_options_bar = make_field_annotation_resource_reference(
         resource_type="example.v1/Bar", is_child_type=False
     )
     field_with_reference_foo = make_field(name="Test", options=field_options_foo)
     field_with_reference_bar = make_field(name="Test", options=field_options_bar)
     FieldComparator(
         field_with_reference_foo,
         field_with_reference_bar,
         self.finding_container,
         context="ctx",
     ).compare()
     finding = self.finding_container.get_all_findings()[0]
     self.assertEqual(finding.change_type.name, "MAJOR")
示例#29
0
 def test_oneofs(self):
     # Oneof with only one field.
     oneof = make_oneof(name="not_interesting")
     oneof_field = make_field(name="first_field",
                              number=1,
                              oneof_index=0,
                              oneof_name="not_interesting")
     non_oneof_field = make_field(name="second_field", number=2)
     message = make_message(
         name="Message",
         fields=(
             oneof_field,
             non_oneof_field,
         ),
         oneofs=(oneof, ),
     )
     self.assertEqual(list(message.oneofs.keys()), ["not_interesting"])
     self.assertEqual({x.name
                       for x in message.fields.values() if x.oneof},
                      {"first_field"})
    def test_resource_reference_change_type_conversion_breaking(self):
        resource_bar = make_resource_descriptor(
            resource_type="example.v1/Bar",
            resource_patterns=["bar/{bar}/foo/{foo}", "bar/{bar}/foo"],
        )
        resource_foo = make_resource_descriptor(
            resource_type="example.v1/Foo", resource_patterns=["foo/{foo}"]
        )
        # Register two resources in database.
        resource_database = make_resource_database(
            resources=[resource_bar, resource_foo]
        )
        # The original field is defined by child type.
        field_options_child = make_field_annotation_resource_reference(
            resource_type="example.v1/Bar", is_child_type=True
        )
        field_with_reference_child = make_field(
            name="Test",
            options=field_options_child,
            resource_database=resource_database,
        )

        # The update field is defined by parent type.
        field_options_parent = make_field_annotation_resource_reference(
            resource_type="example.v1/Foo", is_child_type=False
        )
        field_with_reference_parent = make_field(
            name="Test",
            options=field_options_parent,
            resource_database=resource_database,
        )
        # The two resources can nnot be resolved to the identical resource.
        FieldComparator(
            field_with_reference_child,
            field_with_reference_parent,
            self.finding_container,
            context="ctx",
        ).compare()
        finding = self.finding_container.get_all_findings()[0]
        self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_CHANGE_CHILD_TYPE")
        self.assertEqual(finding.change_type.name, "MAJOR")