diff options
author | Huayu Ouyang <huayu.ouyang@mongodb.com> | 2021-12-09 15:57:04 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-12-09 17:42:47 +0000 |
commit | 498f8a7cffd652321d235b4684ffad6897cdd792 (patch) | |
tree | 2c06419c112a035ec88db54de929d0365a5959ec /buildscripts/idl | |
parent | 61652acf89b2d9e5e3f22cfe74c1de0979d40cd1 (diff) | |
download | mongo-498f8a7cffd652321d235b4684ffad6897cdd792.tar.gz |
SERVER-61551 IDL compatibility checker should check chained structs
Diffstat (limited to 'buildscripts/idl')
7 files changed, 504 insertions, 33 deletions
diff --git a/buildscripts/idl/idl_check_compatibility.py b/buildscripts/idl/idl_check_compatibility.py index b703d9fd0e8..d62fa30c3d4 100644 --- a/buildscripts/idl/idl_check_compatibility.py +++ b/buildscripts/idl/idl_check_compatibility.py @@ -148,7 +148,16 @@ ALLOW_ANY_TYPE_LIST: List[str] = [ 'delete-param-hint', 'findAndModify-param-hint', 'findAndModify-param-update', - 'findAndModify-reply-upserted' + 'findAndModify-reply-upserted', + 'insert-reply-opTime', + 'update-reply-opTime', + 'delete-reply-opTime', + 'aggregate-reply-partialResultsReturned', + 'aggregate-reply-invalidated', + 'find-reply-partialResultsReturned', + 'find-reply-invalidated', + 'getMore-reply-partialResultsReturned', + 'getMore-reply-invalidated', ] SKIPPED_FILES = ["unittest.idl"] @@ -162,6 +171,7 @@ class FieldCompatibility: idl_file: syntax.IDLParsedSpec idl_file_path: str unstable: Optional[bool] + optional: bool @dataclass @@ -227,6 +237,20 @@ def get_new_commands( return new_commands, new_command_file, new_command_file_path +def get_chained_type_or_struct( + chained_type_or_struct: Union[syntax.ChainedType, syntax.ChainedStruct], + idl_file: syntax.IDLParsedSpec, + idl_file_path: str) -> Optional[Union[syntax.Enum, syntax.Struct, syntax.Type]]: + """Resolve and get chained type or struct from the IDL file.""" + parser_ctxt = errors.ParserContext(idl_file_path, errors.ParserErrorCollection()) + resolved = idl_file.spec.symbols.resolve_type_from_name(parser_ctxt, chained_type_or_struct, + chained_type_or_struct.name, + chained_type_or_struct.name) + if parser_ctxt.errors.has_errors(): + parser_ctxt.errors.dump_errors() + return resolved + + def get_field_type(field: Union[syntax.Field, syntax.Command], idl_file: syntax.IDLParsedSpec, idl_file_path: str) -> Optional[Union[syntax.Enum, syntax.Struct, syntax.Type]]: """Resolve and get field type of a field from the IDL file.""" @@ -327,9 +351,11 @@ def check_reply_field_type_recursive(ctxt: IDLCompatibilityContext, if old_variant_type.name == new_variant_type.name: # Check that the old and new version of each variant type is also compatible. old = FieldCompatibility(old_variant_type, old_field.idl_file, - old_field.idl_file_path, old_field.unstable) + old_field.idl_file_path, old_field.unstable, + old_field.optional) new = FieldCompatibility(new_variant_type, new_field.idl_file, - new_field.idl_file_path, new_field.unstable) + new_field.idl_file_path, new_field.unstable, + new_field.optional) check_reply_field_type(ctxt, FieldCompatibilityPair(old, new, cmd_name, field_name)) break @@ -466,9 +492,9 @@ def check_reply_field(ctxt: IDLCompatibilityContext, old_field: syntax.Field, new_field_type = get_field_type(new_field, new_idl_file, new_idl_file_path) old_field_compatibility = FieldCompatibility(old_field_type, old_idl_file, old_idl_file_path, - old_field.unstable) + old_field.unstable, old_field.optional) new_field_compatibility = FieldCompatibility(new_field_type, new_idl_file, new_idl_file_path, - new_field.unstable) + new_field.unstable, new_field.optional) field_pair = FieldCompatibilityPair(old_field_compatibility, new_field_compatibility, cmd_name, old_field.name) @@ -480,10 +506,36 @@ def check_reply_fields(ctxt: IDLCompatibilityContext, old_reply: syntax.Struct, new_idl_file: syntax.IDLParsedSpec, old_idl_file_path: str, new_idl_file_path: str): """Check compatibility between old and new reply fields.""" - # pylint: disable=too-many-arguments - for old_field in old_reply.fields or []: + # pylint: disable=too-many-arguments,too-many-branches + for new_chained_type in new_reply.chained_types or []: + resolved_new_chained_type = get_chained_type_or_struct(new_chained_type, new_idl_file, + new_idl_file_path) + if resolved_new_chained_type is not None: + for old_chained_type in old_reply.chained_types or []: + resolved_old_chained_type = get_chained_type_or_struct( + old_chained_type, old_idl_file, old_idl_file_path) + if (resolved_old_chained_type is not None + and resolved_old_chained_type.name == resolved_new_chained_type.name): + # Check that the old and new version of each chained type is also compatible. + old = FieldCompatibility(resolved_old_chained_type, old_idl_file, + old_idl_file_path, unstable=False, optional=False) + new = FieldCompatibility(resolved_new_chained_type, new_idl_file, + new_idl_file_path, unstable=False, optional=False) + + check_reply_field_type( + ctxt, FieldCompatibilityPair(old, new, cmd_name, old_reply.name)) + break + + else: + # new chained type was not found in old chained types. + ctxt.add_new_reply_chained_type_not_subset_error( + cmd_name, new_reply.name, resolved_new_chained_type.name, new_idl_file_path) + + old_reply_fields = get_all_struct_fields(old_reply, old_idl_file, old_idl_file_path) + new_reply_fields = get_all_struct_fields(new_reply, new_idl_file, new_idl_file_path) + for old_field in old_reply_fields or []: new_field_exists = False - for new_field in new_reply.fields or []: + for new_field in new_reply_fields or []: if new_field.name == old_field.name: new_field_exists = True check_reply_field(ctxt, old_field, new_field, cmd_name, old_idl_file, new_idl_file, @@ -494,7 +546,7 @@ def check_reply_fields(ctxt: IDLCompatibilityContext, old_reply: syntax.Struct, if not new_field_exists and not old_field.unstable: ctxt.add_new_reply_field_missing_error(cmd_name, old_field.name, old_idl_file_path) - for new_field in new_reply.fields or []: + for new_field in new_reply_fields or []: # Check that all fields in the new IDL have specified the 'unstable' field. if new_field.unstable is None: ctxt.add_new_reply_field_requires_unstable_error(cmd_name, new_field.name, @@ -503,7 +555,7 @@ def check_reply_fields(ctxt: IDLCompatibilityContext, old_reply: syntax.Struct, # Check that newly added fields do not have an unallowed use of 'any' as the # bson_serialization_type. newly_added = True - for old_field in old_reply.fields or []: + for old_field in old_reply_fields or []: if new_field.name == old_field.name: newly_added = False @@ -547,10 +599,17 @@ def check_param_or_command_type_recursive(ctxt: IDLCompatibilityContext, is_command_parameter) return - if old_type.bson_serialization_type is None or new_type.bson_serialization_type is None: - print("here") + # If the type has changed from type: optionalBool to type: bool with optional: true we should + # allow it. + # pylint: disable=invalid-name + optionalBoolToBoolWithOptional: bool = (old_type.name == "optionalBool" + and new_type.name == "bool" and new_field.optional) + + allow_name: str = cmd_name + "-param-" + param_name if is_command_parameter else cmd_name + # If bson_serialization_type switches from 'any' to non-any type. - if "any" in old_type.bson_serialization_type and "any" not in new_type.bson_serialization_type: + if ("any" in old_type.bson_serialization_type and "any" not in new_type.bson_serialization_type + and not optionalBoolToBoolWithOptional): ctxt.add_old_command_or_param_type_bson_any_error( cmd_name, old_type.name, old_field.idl_file_path, param_name, is_command_parameter) return @@ -561,9 +620,7 @@ def check_param_or_command_type_recursive(ctxt: IDLCompatibilityContext, cmd_name, new_type.name, new_field.idl_file_path, param_name, is_command_parameter) return - allow_name: str = cmd_name + "-param-" + param_name if is_command_parameter else cmd_name - - if "any" in old_type.bson_serialization_type: + if ("any" in old_type.bson_serialization_type and not optionalBoolToBoolWithOptional): # If 'any' is not explicitly allowed as the bson_serialization_type. if allow_name not in ALLOW_ANY_TYPE_LIST: ctxt.add_command_or_param_type_bson_any_not_allowed_error( @@ -601,9 +658,11 @@ def check_param_or_command_type_recursive(ctxt: IDLCompatibilityContext, if old_variant_type.name == new_variant_type.name: # Check that the old and new version of each variant type is also compatible. old = FieldCompatibility(old_variant_type, old_field.idl_file, - old_field.idl_file_path, old_field.unstable) + old_field.idl_file_path, old_field.unstable, + old_field.optional) new = FieldCompatibility(new_variant_type, new_field.idl_file, - new_field.idl_file_path, new_field.unstable) + new_field.idl_file_path, new_field.unstable, + new_field.optional) check_param_or_command_type( ctxt, FieldCompatibilityPair(old, new, cmd_name, param_name), is_command_parameter) @@ -630,7 +689,7 @@ def check_param_or_command_type_recursive(ctxt: IDLCompatibilityContext, cmd_name, old_type.variant_struct_type.name, new_field.idl_file_path, param_name, is_command_parameter) - elif not old_field.unstable: + elif (not old_field.unstable and not optionalBoolToBoolWithOptional): check_superset(ctxt, cmd_name, new_type.name, new_type.bson_serialization_type, old_type.bson_serialization_type, new_field.idl_file_path, param_name, is_command_parameter) @@ -714,15 +773,63 @@ def check_param_or_type_validator(ctxt: IDLCompatibilityContext, old_field: synt cmd_name, new_field.name, new_idl_file_path, type_name, is_command_parameter) +def get_all_struct_fields(struct: syntax.Struct, idl_file: syntax.IDLParsedSpec, + idl_file_path: str): + """Get all the fields of a struct, including the chained struct fields.""" + all_fields = struct.fields or [] + for chained_struct in struct.chained_structs or []: + resolved_chained_struct = get_chained_type_or_struct(chained_struct, idl_file, + idl_file_path) + if resolved_chained_struct is not None: + for field in resolved_chained_struct.fields: + all_fields.append(field) + + return all_fields + + def check_command_params_or_type_struct_fields( ctxt: IDLCompatibilityContext, old_struct: syntax.Struct, new_struct: syntax.Struct, cmd_name: str, old_idl_file: syntax.IDLParsedSpec, new_idl_file: syntax.IDLParsedSpec, old_idl_file_path: str, new_idl_file_path: str, is_command_parameter: bool): """Check compatibility between old and new parameters or command type fields.""" - # pylint: disable=too-many-arguments - for old_field in old_struct.fields or []: + # pylint: disable=too-many-arguments,too-many-branches + # Check chained types. + for old_chained_type in old_struct.chained_types or []: + resolved_old_chained_type = get_chained_type_or_struct(old_chained_type, old_idl_file, + old_idl_file_path) + if resolved_old_chained_type is not None: + for new_chained_type in new_struct.chained_types or []: + resolved_new_chained_type = get_chained_type_or_struct( + new_chained_type, new_idl_file, new_idl_file_path) + if (resolved_new_chained_type is not None + and resolved_old_chained_type.name == resolved_new_chained_type.name): + # Check that the old and new version of each chained type is also compatible. + old = FieldCompatibility(resolved_old_chained_type, old_idl_file, + old_idl_file_path, unstable=False, optional=False) + new = FieldCompatibility(resolved_new_chained_type, new_idl_file, + new_idl_file_path, unstable=False, optional=False) + check_param_or_command_type( + ctxt, FieldCompatibilityPair(old, new, cmd_name, old_struct.name), + is_command_parameter=False) + break + + else: + # old chained type was not found in new chained types. + ctxt.add_new_command_or_param_chained_type_not_superset_error( + cmd_name, old_chained_type.name, new_idl_file_path, old_struct.name, + is_command_parameter) + + old_struct_fields = get_all_struct_fields(old_struct, old_idl_file, old_idl_file_path) + new_struct_fields = get_all_struct_fields(new_struct, new_idl_file, new_idl_file_path) + + # We need to special-case the stmtId parameter because it was removed. However, it's not a + # breaking change to the API because it was added and removed behind a feature flag, so it was + # never officially released. + allow_list = ["endSessions-param-stmtId", "refreshSessions-param-stmtId"] + + for old_field in old_struct_fields or []: new_field_exists = False - for new_field in new_struct.fields or []: + for new_field in new_struct_fields or []: if new_field.name == old_field.name: new_field_exists = True check_command_param_or_type_struct_field( @@ -730,21 +837,21 @@ def check_command_params_or_type_struct_fields( old_idl_file_path, new_idl_file_path, old_struct.name, is_command_parameter) break - - if not new_field_exists and not old_field.unstable: + allow_name: str = cmd_name + "-param-" + old_field.name + if not new_field_exists and not old_field.unstable and allow_name not in allow_list: ctxt.add_new_param_or_command_type_field_missing_error( cmd_name, old_field.name, old_idl_file_path, old_struct.name, is_command_parameter) # Check if a new field has been added to the parameters or type struct. # If so, it must be optional. - for new_field in new_struct.fields or []: + for new_field in new_struct_fields or []: # Check that all fields in the new IDL have specified the 'unstable' field. if new_field.unstable is None: ctxt.add_new_param_or_command_type_field_requires_unstable_error( cmd_name, new_field.name, new_idl_file_path, is_command_parameter) newly_added = True - for old_field in old_struct.fields or []: + for old_field in old_struct_fields or []: if new_field.name == old_field.name: newly_added = False @@ -754,12 +861,13 @@ def check_command_params_or_type_struct_fields( # Check that a new field does not have an unallowed use of 'any' as the bson_serialization_type. if newly_added: - allow_name: str = cmd_name + "-param-" + new_field.name if is_command_parameter else cmd_name + any_allow_name: str = (cmd_name + "-param-" + new_field.name + if is_command_parameter else cmd_name) new_field_type = get_field_type(new_field, new_idl_file, new_idl_file_path) if isinstance(new_field_type, syntax.Type) and "any" in new_field_type.bson_serialization_type: # If 'any' is not explicitly allowed as the bson_serialization_type. - if allow_name not in ALLOW_ANY_TYPE_LIST: + if any_allow_name not in ALLOW_ANY_TYPE_LIST: ctxt.add_command_or_param_type_bson_any_not_allowed_error( cmd_name, new_field_type.name, old_idl_file_path, new_field.name, is_command_parameter) @@ -793,9 +901,9 @@ def check_command_param_or_type_struct_field( new_field_type = get_field_type(new_field, new_idl_file, new_idl_file_path) old_field_compatibility = FieldCompatibility(old_field_type, old_idl_file, old_idl_file_path, - old_field.unstable) + old_field.unstable, old_field.optional) new_field_compatibility = FieldCompatibility(new_field_type, new_idl_file, new_idl_file_path, - new_field.unstable) + new_field.unstable, new_field.optional) field_pair = FieldCompatibilityPair(old_field_compatibility, new_field_compatibility, cmd_name, old_field.name) @@ -828,8 +936,10 @@ def check_namespace(ctxt: IDLCompatibilityContext, old_cmd: syntax.Command, new_ old_type = get_field_type(old_cmd, old_idl_file, old_idl_file_path) if new_namespace == common.COMMAND_NAMESPACE_TYPE: new_type = get_field_type(new_cmd, new_idl_file, new_idl_file_path) - old = FieldCompatibility(old_type, old_idl_file, old_idl_file_path, unstable=False) - new = FieldCompatibility(new_type, new_idl_file, new_idl_file_path, unstable=False) + old = FieldCompatibility(old_type, old_idl_file, old_idl_file_path, unstable=False, + optional=False) + new = FieldCompatibility(new_type, new_idl_file, new_idl_file_path, unstable=False, + optional=False) check_param_or_command_type(ctxt, FieldCompatibilityPair(old, new, old_cmd.command_name, ""), diff --git a/buildscripts/idl/idl_compatibility_errors.py b/buildscripts/idl/idl_compatibility_errors.py index 13eac6a9318..9369f964d8b 100644 --- a/buildscripts/idl/idl_compatibility_errors.py +++ b/buildscripts/idl/idl_compatibility_errors.py @@ -121,6 +121,9 @@ ERROR_ID_REPLY_FIELD_DESERIALIZER_NOT_EQUAL = "ID0076" ERROR_ID_NEW_REPLY_FIELD_REQUIRES_UNSTABLE = "ID0077" ERROR_ID_NEW_PARAMETER_REQUIRES_UNSTABLE = "ID0078" ERROR_ID_NEW_COMMAND_TYPE_FIELD_REQUIRES_UNSTABLE = "ID0079" +ERROR_ID_NEW_REPLY_CHAINED_TYPE_NOT_SUBSET = "ID0080" +ERROR_ID_NEW_COMMAND_PARAMETER_CHAINED_TYPE_NOT_SUPERSET = "ID0081" +ERROR_ID_NEW_COMMAND_CHAINED_TYPE_NOT_SUPERSET = "ID0082" class IDLCompatibilityCheckerError(Exception): @@ -612,6 +615,31 @@ class IDLCompatibilityContext(object): "is in the old command types but not the new command types.") % (command_name, variant_type_name), file) + def add_new_command_or_param_chained_type_not_superset_error( + self, command_name: str, chained_type_name: str, file: str, field_name: Optional[str], + is_command_parameter: bool) -> None: + # pylint: disable=too-many-arguments,invalid-name + """ + Add an error about the new chained types not being a superset. + + Add an error about the new command or parameter chained types not being a superset + of the old chained types. + """ + if is_command_parameter: + self._add_error( + ERROR_ID_NEW_COMMAND_PARAMETER_CHAINED_TYPE_NOT_SUPERSET, command_name, + ("The '%s' command has field or sub-field '%s' of chained types that is not " + "a superset of the corresponding old field chained types: " + "The type '%s' is in the old field types but not the new field types.") % + (command_name, field_name, chained_type_name), file) + else: + self._add_error(ERROR_ID_NEW_COMMAND_CHAINED_TYPE_NOT_SUPERSET, command_name, + ("'%s' or its sub-struct has chained types that is not a supserset " + "of the corresponding" + " old command chained types: The type '%s' " + "is in the old command types but not the new command types.") % + (command_name, chained_type_name), file) + def add_new_namespace_incompatible_error(self, command_name: str, old_namespace: str, new_namespace: str, file: str) -> None: """Add an error about the new namespace being incompatible with the old namespace.""" @@ -759,6 +787,22 @@ class IDLCompatibilityContext(object): "old reply field types: The type '%s' is not in the old reply field types.") % (command_name, field_name, variant_type_name), file) + def add_new_reply_chained_type_not_subset_error(self, command_name: str, reply_name: str, + chained_type_name: str, file: str) -> None: + # pylint: disable=too-many-arguments + """ + Add an error about the reply chained types not being a subset. + + Add an error about the new reply chained types + not being a subset of the old chained types. + """ + self._add_error( + ERROR_ID_NEW_REPLY_CHAINED_TYPE_NOT_SUBSET, command_name, + ("'%s' has a reply '%s' with chained types that is " + "not a subset of the corresponding " + "old reply chained types: The type '%s' is not in the old reply chained types.") % + (command_name, reply_name, chained_type_name), file) + def add_old_command_or_param_type_bson_any_error(self, command_name: str, old_type: str, file: str, field_name: Optional[str], is_command_parameter: bool) -> None: diff --git a/buildscripts/idl/tests/compatibility_test_fail/new/compatibility_test_fail_new.idl b/buildscripts/idl/tests/compatibility_test_fail/new/compatibility_test_fail_new.idl index 9de05532a46..c7679c754ff 100644 --- a/buildscripts/idl/tests/compatibility_test_fail/new/compatibility_test_fail_new.idl +++ b/buildscripts/idl/tests/compatibility_test_fail/new/compatibility_test_fail_new.idl @@ -736,6 +736,24 @@ structs: type: string optional: true + IncompatibleChainedStructReply: + description: "This reply contains an incompatible chained struct" + chained_structs: + NewVariantNotSubsetReply: NewVariantNotSubsetReply + + IncompatibleChainedStructType: + description: "This struct type contains an incompatible chained struct" + chained_structs: + NewBsonSerializationTypeAnyUnstableStruct: NewBsonSerializationTypeAnyUnstableStruct + + NewAddedChainedTypeReply: + description: "This reply contains an added chained type in the new version" + chained_types: + intToIntString: intToIntString + + NewRemovedChainedTypeStruct: + description: "This reply contains an added chained type in the new version" + commands: invalidAPIVersionNew: description: "new command fails because of invalid API version" @@ -2763,3 +2781,69 @@ commands: strict: true api_version: "1" reply_type: OkReply + + chainedStructIncompatible: + description: "new command fails because it contains a chained struct that has incompatible + fields" + command_name: chainedStructIncompatible + namespace: ignored + cpp_name: chainedStructIncompatible + strict: true + api_version: "1" + reply_type: OkReply + chained_structs: + VariantStructRecursiveStructWithArray: VariantStructRecursiveStructWithArray + + replyWithIncompatibleChainedStruct: + description: "new command fails because its reply contains a chained struct that has + incompatible fields" + command_name: replyWithIncompatibleChainedStruct + namespace: ignored + cpp_name: replyWithIncompatibleChainedStruct + strict: true + api_version: "1" + reply_type: IncompatibleChainedStructReply + + typeWithIncompatibleChainedStruct: + description: "new command fails because its type contains a chained struct that has + incompatible fields" + command_name: typeWithIncompatibleChainedStruct + namespace: type + type: IncompatibleChainedStructType + cpp_name: typeWithIncompatibleChainedStruct + strict: true + api_version: "1" + reply_type: OkReply + + incompatibleChainedType: + description: "new command fails because its chained types are incompatible" + command_name: incompatibleChainedType + namespace: ignored + cpp_name: incompatibleChainedType + strict: true + api_version: "1" + reply_type: OkReply + chained_types: + intStringToInt: intStringToInt + + newParameterRemovedChainedType: + description: "new command fails because its parameter has removed a chained type" + command_name: newParameterRemovedChainedType + namespace: ignored + cpp_name: newParameterRemovedChainedType + strict: true + api_version: "1" + reply_type: OkReply + fields: + NewRemovedChainedTypeParameter: + unstable: false + type: NewRemovedChainedTypeStruct + + newReplyAddedChainedType: + description: "new command fails because its reply has added a chained type" + command_name: newReplyAddedChainedType + namespace: ignored + cpp_name: newReplyAddedChainedType + strict: true + api_version: "1" + reply_type: NewAddedChainedTypeReply diff --git a/buildscripts/idl/tests/compatibility_test_fail/old/compatibility_test_fail_old.idl b/buildscripts/idl/tests/compatibility_test_fail/old/compatibility_test_fail_old.idl index 6661fe62dbd..aa6be9dc90a 100644 --- a/buildscripts/idl/tests/compatibility_test_fail/old/compatibility_test_fail_old.idl +++ b/buildscripts/idl/tests/compatibility_test_fail/old/compatibility_test_fail_old.idl @@ -722,6 +722,24 @@ structs: description: "This struct contains an added field in the new command that is missing the 'unstable' field." + IncompatibleChainedStructReply: + description: "This reply contains an incompatible chained struct" + chained_structs: + NewVariantNotSubsetReply: NewVariantNotSubsetReply + + IncompatibleChainedStructType: + description: "This struct type contains an incompatible chained struct" + chained_structs: + NewBsonSerializationTypeAnyUnstableStruct: NewBsonSerializationTypeAnyUnstableStruct + + NewAddedChainedTypeReply: + description: "This reply contains an added chained type in the new version" + + NewRemovedChainedTypeStruct: + description: "This reply contains an added chained type in the new version" + chained_types: + intStringToInt: intStringToInt + commands: invalidAPIVersionOld: description: "old command fails because of invalid API version" @@ -2721,3 +2739,69 @@ commands: strict: true api_version: "1" reply_type: OkReply + + chainedStructIncompatible: + description: "new command fails because it contains a chained struct that has incompatible + fields" + command_name: chainedStructIncompatible + namespace: ignored + cpp_name: chainedStructIncompatible + strict: true + api_version: "1" + reply_type: OkReply + chained_structs: + VariantStructRecursiveStructWithArray: VariantStructRecursiveStructWithArray + + replyWithIncompatibleChainedStruct: + description: "new command fails because its reply contains a chained struct that has + incompatible fields" + command_name: replyWithIncompatibleChainedStruct + namespace: ignored + cpp_name: replyWithIncompatibleChainedStruct + strict: true + api_version: "1" + reply_type: IncompatibleChainedStructReply + + typeWithIncompatibleChainedStruct: + description: "new command fails because its type contains a chained struct that has + incompatible fields" + command_name: typeWithIncompatibleChainedStruct + namespace: type + type: IncompatibleChainedStructType + cpp_name: typeWithIncompatibleChainedStruct + strict: true + api_version: "1" + reply_type: OkReply + + incompatibleChainedType: + description: "new command fails because its chained types are incompatible" + command_name: incompatibleChainedType + namespace: ignored + cpp_name: incompatibleChainedType + strict: true + api_version: "1" + reply_type: OkReply + chained_types: + intStringToInt: intStringToInt + + newParameterRemovedChainedType: + description: "new command fails because its parameter has removed a chained type" + command_name: newParameterRemovedChainedType + namespace: ignored + cpp_name: newParameterRemovedChainedType + strict: true + api_version: "1" + reply_type: OkReply + fields: + NewRemovedChainedTypeParameter: + unstable: false + type: NewRemovedChainedTypeStruct + + newReplyAddedChainedType: + description: "new command fails because its reply has added a chained type" + command_name: newReplyAddedChainedType + namespace: ignored + cpp_name: newReplyAddedChainedType + strict: true + api_version: "1" + reply_type: NewAddedChainedTypeReply diff --git a/buildscripts/idl/tests/compatibility_test_pass/new/compatibility_test_pass_new.idl b/buildscripts/idl/tests/compatibility_test_pass/new/compatibility_test_pass_new.idl index 8eb92f68208..2bffaae0a2d 100644 --- a/buildscripts/idl/tests/compatibility_test_pass/new/compatibility_test_pass_new.idl +++ b/buildscripts/idl/tests/compatibility_test_pass/new/compatibility_test_pass_new.idl @@ -464,6 +464,16 @@ structs: type: string unstable: false + CompatibleChainedStructReply: + description: "This reply contains an compatible chained struct" + chained_structs: + StructFieldTypeRecursiveReplyTwo: StructFieldTypeRecursiveReplyTwo + + CompatibleChainedStructType: + description: "This struct type contains an compatible chained struct" + chained_structs: + StructCommandParameterTypeRecursive: StructCommandParameterTypeRecursive + commands: testCommand: description: "new passing test command, there was no change from the old version" @@ -1523,3 +1533,47 @@ commands: type: int unstable: false reply_type: OkReply + + chainedStructCompatible: + description: "new command passes because it contains a chained struct that has compatible + fields" + command_name: chainedStructCompatible + namespace: ignored + cpp_name: chainedStructCompatible + strict: true + api_version: "1" + reply_type: OkReply + chained_structs: + VariantStructRecursiveStruct: VariantStructRecursiveStruct + + replyWithCompatibleChainedStruct: + description: "new command passes because its reply contains a chained struct that has + compatible fields" + command_name: replyWithCompatibleChainedStruct + namespace: ignored + cpp_name: replyWithCompatibleChainedStruct + strict: true + api_version: "1" + reply_type: CompatibleChainedStructReply + + typeWithCompatibleChainedStruct: + description: "new command passes because its type contains a chained struct that has + compatible fields" + command_name: typeWithCompatibleChainedStruct + namespace: type + type: CompatibleChainedStructType + cpp_name: typeWithCompatibleChainedStruct + strict: true + api_version: "1" + reply_type: OkReply + + incompatibleChainedType: + description: "new command passes because its chained types are compatible" + command_name: incompatibleChainedType + namespace: ignored + cpp_name: incompatibleChainedType + strict: true + api_version: "1" + reply_type: OkReply + chained_types: + intToIntString: intToIntString diff --git a/buildscripts/idl/tests/compatibility_test_pass/old/compatibility_test_pass_old.idl b/buildscripts/idl/tests/compatibility_test_pass/old/compatibility_test_pass_old.idl index e866dbc24e0..3ba1907be5b 100644 --- a/buildscripts/idl/tests/compatibility_test_pass/old/compatibility_test_pass_old.idl +++ b/buildscripts/idl/tests/compatibility_test_pass/old/compatibility_test_pass_old.idl @@ -443,6 +443,16 @@ structs: missingUnstableFieldNewField: type: string + CompatibleChainedStructReply: + description: "This reply contains an compatible chained struct" + chained_structs: + StructFieldTypeRecursiveReplyTwo: StructFieldTypeRecursiveReplyTwo + + CompatibleChainedStructType: + description: "This struct type contains an compatible chained struct" + chained_structs: + StructCommandParameterTypeRecursive: StructCommandParameterTypeRecursive + commands: testCommand: description: "old passing test command" @@ -1495,3 +1505,47 @@ commands: missingUnstableFieldOldParameter: type: int reply_type: OkReply + + chainedStructCompatible: + description: "new command passes because it contains a chained struct that has compatible + fields" + command_name: chainedStructCompatible + namespace: ignored + cpp_name: chainedStructCompatible + strict: true + api_version: "1" + reply_type: OkReply + chained_structs: + VariantStructRecursiveStruct: VariantStructRecursiveStruct + + replyWithCompatibleChainedStruct: + description: "new command passes because its reply contains a chained struct that has + compatible fields" + command_name: replyWithCompatibleChainedStruct + namespace: ignored + cpp_name: replyWithCompatibleChainedStruct + strict: true + api_version: "1" + reply_type: CompatibleChainedStructReply + + typeWithCompatibleChainedStruct: + description: "new command passes because its type contains a chained struct that has + compatible fields" + command_name: typeWithCompatibleChainedStruct + namespace: type + type: CompatibleChainedStructType + cpp_name: typeWithCompatibleChainedStruct + strict: true + api_version: "1" + reply_type: OkReply + + incompatibleChainedType: + description: "new command passes because its chained types are compatible" + command_name: incompatibleChainedType + namespace: ignored + cpp_name: incompatibleChainedType + strict: true + api_version: "1" + reply_type: OkReply + chained_types: + intToIntString: intToIntString diff --git a/buildscripts/idl/tests/test_compatibility.py b/buildscripts/idl/tests/test_compatibility.py index b8265e75284..5567c871bdb 100644 --- a/buildscripts/idl/tests/test_compatibility.py +++ b/buildscripts/idl/tests/test_compatibility.py @@ -92,7 +92,7 @@ class TestIDLCompatibilityChecker(unittest.TestCase): path.join(dir_path, "compatibility_test_fail/new"), ["src"], ["src"]) self.assertTrue(error_collection.has_errors()) - self.assertEqual(error_collection.count(), 176) + self.assertEqual(error_collection.count(), 182) invalid_api_version_new_error = error_collection.get_error_by_command_name( "invalidAPIVersionNew") @@ -1294,6 +1294,47 @@ class TestIDLCompatibilityChecker(unittest.TestCase): str(added_new_parameter_missing_unstable_field_error), "addedNewParameterMissingUnstableField") + chained_struct_incompatible_error = error_collection.get_error_by_command_name( + "chainedStructIncompatible") + self.assertTrue(chained_struct_incompatible_error.error_id == + idl_compatibility_errors.ERROR_ID_COMMAND_PARAMETER_TYPE_NOT_SUPERSET) + self.assertRegex(str(chained_struct_incompatible_error), "chainedStructIncompatible") + + reply_with_incompatible_chained_struct_error = error_collection.get_error_by_command_name( + "replyWithIncompatibleChainedStruct") + self.assertTrue(reply_with_incompatible_chained_struct_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_REPLY_FIELD_VARIANT_TYPE_NOT_SUBSET) + self.assertRegex( + str(reply_with_incompatible_chained_struct_error), "replyWithIncompatibleChainedStruct") + + type_with_incompatible_chained_struct_error = error_collection.get_error_by_command_name( + "typeWithIncompatibleChainedStruct") + self.assertTrue( + type_with_incompatible_chained_struct_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_BSON_SERIALIZATION_TYPE_ANY) + self.assertRegex( + str(type_with_incompatible_chained_struct_error), "typeWithIncompatibleChainedStruct") + + incompatible_chained_type_error = error_collection.get_error_by_command_name( + "incompatibleChainedType") + self.assertTrue(incompatible_chained_type_error.error_id == + idl_compatibility_errors.ERROR_ID_COMMAND_TYPE_NOT_SUPERSET) + self.assertRegex(str(incompatible_chained_type_error), "incompatibleChainedType") + + new_parameter_removed_chained_type_error = error_collection.get_error_by_command_name( + "newParameterRemovedChainedType") + self.assertTrue( + new_parameter_removed_chained_type_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_PARAMETER_CHAINED_TYPE_NOT_SUPERSET) + self.assertRegex( + str(new_parameter_removed_chained_type_error), "newParameterRemovedChainedType") + + new_reply_added_chained_type_error = error_collection.get_error_by_command_name( + "newReplyAddedChainedType") + self.assertTrue(new_reply_added_chained_type_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_REPLY_CHAINED_TYPE_NOT_SUBSET) + self.assertRegex(str(new_reply_added_chained_type_error), "newReplyAddedChainedType") + def test_generic_argument_compatibility_pass(self): """Tests that compatible old and new generic_argument.idl files should pass.""" dir_path = path.dirname(path.realpath(__file__)) |