diff options
author | Huayu Ouyang <huayu.ouyang@mongodb.com> | 2021-02-04 23:22:39 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-02-09 18:33:31 +0000 |
commit | 3a23cc592fcfb2fa50b277782fddb4ff2b94171f (patch) | |
tree | 9a6db17f9956f538083a9fd5b20f427358b1751a | |
parent | 4b8f3c1b9bb07dab930f56e37285416820a6e812 (diff) | |
download | mongo-3a23cc592fcfb2fa50b277782fddb4ff2b94171f.tar.gz |
SERVER-54109 Check for incompatible "namespace" and "type" field changes in IDL compatibility checker script
7 files changed, 908 insertions, 5 deletions
diff --git a/buildscripts/idl/idl_check_compatibility.py b/buildscripts/idl/idl_check_compatibility.py index 278eee572c9..9e6406bf1aa 100644 --- a/buildscripts/idl/idl_check_compatibility.py +++ b/buildscripts/idl/idl_check_compatibility.py @@ -42,7 +42,7 @@ import os import sys from typing import Dict, List, Optional, Tuple, Union -from idl import parser, syntax, errors +from idl import parser, syntax, errors, common from idl.compiler import CompilerImportResolver from idl_compatibility_errors import IDLCompatibilityContext, IDLCompatibilityErrorCollection @@ -91,7 +91,7 @@ def get_new_commands( return new_commands, new_command_file, new_command_file_path -def get_field_type(field: syntax.Field, idl_file: syntax.IDLParsedSpec, +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.""" parser_ctxt = errors.ParserContext(idl_file_path, errors.ParserErrorCollection()) @@ -111,6 +111,15 @@ def check_subset(ctxt: IDLCompatibilityContext, cmd_name: str, field_name: str, ctxt.add_command_not_subset_error(cmd_name, field_name, type_name, file_path) +def check_type_superset(ctxt: IDLCompatibilityContext, cmd_name: str, type_name: str, + sub_list: List[Union[str, syntax.EnumValue]], + super_list: List[Union[str, syntax.EnumValue]], file_path: str): + # pylint: disable=too-many-arguments + """Check if sub_list is a subset of the super_list and log an error if not.""" + if not set(sub_list).issubset(super_list): + ctxt.add_command_type_not_superset_error(cmd_name, type_name, file_path) + + def check_reply_field_type(ctxt: IDLCompatibilityContext, old_field_type: Optional[Union[syntax.Enum, syntax.Struct, syntax.Type]], new_field_type: Optional[Union[syntax.Enum, syntax.Struct, syntax.Type]], @@ -159,6 +168,96 @@ def check_reply_field_type(ctxt: IDLCompatibilityContext, cmd_name, field_name, new_field_type.name, old_field_type.name, new_idl_file_path) +def check_command_type(ctxt: IDLCompatibilityContext, + old_type: Optional[Union[syntax.Enum, syntax.Struct, syntax.Type]], + new_type: Optional[Union[syntax.Enum, syntax.Struct, syntax.Type]], + cmd_name: str, old_idl_file: syntax.IDLParsedSpec, + new_idl_file: syntax.IDLParsedSpec, old_idl_file_path: str, + new_idl_file_path: str): + """Check compatibility between old and new command type.""" + # pylint: disable=too-many-arguments,too-many-branches + if old_type is None: + ctxt.add_command_type_invalid_error(cmd_name, old_idl_file_path) + ctxt.errors.dump_errors() + sys.exit(1) + if new_type is None: + ctxt.add_command_type_invalid_error(cmd_name, new_idl_file_path) + ctxt.errors.dump_errors() + sys.exit(1) + + if isinstance(old_type, syntax.Type): + if isinstance(new_type, syntax.Type): + if "any" in old_type.bson_serialization_type: + ctxt.add_old_command_type_bson_any_error(cmd_name, old_type.name, old_idl_file_path) + elif "any" in new_type.bson_serialization_type: + ctxt.add_new_command_type_bson_any_error(cmd_name, new_type.name, new_idl_file_path) + else: + check_type_superset(ctxt, cmd_name, new_type.name, old_type.bson_serialization_type, + new_type.bson_serialization_type, new_idl_file_path) + else: + ctxt.add_new_command_type_enum_or_struct_error(cmd_name, new_type.name, old_type.name, + new_idl_file_path) + elif isinstance(old_type, syntax.Enum): + if isinstance(new_type, syntax.Enum): + check_type_superset(ctxt, cmd_name, new_type.name, old_type.values, new_type.values, + new_idl_file_path) + else: + ctxt.add_new_command_type_not_enum_error(cmd_name, new_type.name, old_type.name, + new_idl_file_path) + elif isinstance(old_type, syntax.Struct): + if isinstance(new_type, syntax.Struct): + check_command_type_struct_fields(ctxt, old_type, new_type, cmd_name, old_idl_file, + new_idl_file, old_idl_file_path, new_idl_file_path) + else: + ctxt.add_new_command_type_not_struct_error(cmd_name, new_type.name, old_type.name, + new_idl_file_path) + + +def check_command_type_struct_field( + ctxt: IDLCompatibilityContext, type_name: str, old_field: syntax.Field, + new_field: syntax.Field, cmd_name: str, old_idl_file: syntax.IDLParsedSpec, + new_idl_file: syntax.IDLParsedSpec, old_idl_file_path: str, new_idl_file_path: str): + """Check compatibility between old and new type struct field.""" + # pylint: disable=too-many-arguments + if new_field.unstable: + ctxt.add_new_command_type_field_unstable_error(cmd_name, type_name, new_field.name, + new_idl_file_path) + if old_field.optional and not new_field.optional: + ctxt.add_new_command_type_field_required_error(cmd_name, type_name, new_field.name, + new_idl_file_path) + + old_field_type = get_field_type(old_field, old_idl_file, old_idl_file_path) + new_field_type = get_field_type(new_field, new_idl_file, new_idl_file_path) + + check_command_type(ctxt, old_field_type, new_field_type, cmd_name, old_idl_file, new_idl_file, + old_idl_file_path, new_idl_file_path) + + +def check_command_type_struct_fields( + ctxt: IDLCompatibilityContext, old_type: syntax.Struct, new_type: 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): + """Check compatibility between old and new type fields.""" + # pylint: disable=too-many-arguments + for old_field in old_type.fields or []: + if old_field.unstable: + continue + + new_field_exists = False + for new_field in new_type.fields or []: + if new_field.name == old_field.name: + new_field_exists = True + check_command_type_struct_field(ctxt, old_type.name, old_field, new_field, cmd_name, + old_idl_file, new_idl_file, old_idl_file_path, + new_idl_file_path) + + break + + if not new_field_exists: + ctxt.add_new_command_type_field_missing_error(cmd_name, old_type.name, old_field.name, + old_idl_file_path) + + def check_reply_field(ctxt: IDLCompatibilityContext, old_field: syntax.Field, new_field: syntax.Field, cmd_name: str, old_idl_file: syntax.IDLParsedSpec, new_idl_file: syntax.IDLParsedSpec, old_idl_file_path: str, @@ -200,6 +299,46 @@ def check_reply_fields(ctxt: IDLCompatibilityContext, old_reply: syntax.Struct, ctxt.add_new_reply_field_missing_error(cmd_name, old_field.name, old_idl_file_path) +def check_namespace(ctxt: IDLCompatibilityContext, old_cmd: syntax.Command, new_cmd: syntax.Command, + old_idl_file: syntax.IDLParsedSpec, new_idl_file: syntax.IDLParsedSpec, + old_idl_file_path: str, new_idl_file_path: str): + """Check compatibility between old and new namespace.""" + # pylint: disable=too-many-arguments + old_namespace = old_cmd.namespace + new_namespace = new_cmd.namespace + + # IDL parser already checks that namespace must be one of these 4 types. + if old_namespace == common.COMMAND_NAMESPACE_IGNORED: + if new_namespace != common.COMMAND_NAMESPACE_IGNORED: + ctxt.add_new_namespace_incompatible_error(old_cmd.command_name, old_namespace, + new_namespace, new_idl_file_path) + elif old_namespace == common.COMMAND_NAMESPACE_CONCATENATE_WITH_DB_OR_UUID: + if new_namespace not in (common.COMMAND_NAMESPACE_IGNORED, + common.COMMAND_NAMESPACE_CONCATENATE_WITH_DB_OR_UUID): + ctxt.add_new_namespace_incompatible_error(old_cmd.command_name, old_namespace, + new_namespace, new_idl_file_path) + elif old_namespace == common.COMMAND_NAMESPACE_CONCATENATE_WITH_DB: + if new_namespace == common.COMMAND_NAMESPACE_TYPE: + ctxt.add_new_namespace_incompatible_error(old_cmd.command_name, old_namespace, + new_namespace, new_idl_file_path) + elif old_namespace == common.COMMAND_NAMESPACE_TYPE: + 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) + check_command_type(ctxt, old_type, new_type, old_cmd.command_name, old_idl_file, + new_idl_file, old_idl_file_path, new_idl_file_path) + + # If old type is "namespacestring", the new namespace can be changed to any + # of the other namespace types. + elif old_type.name != "namespacestring": + # Otherwise, the new namespace can only be changed to "ignored". + if new_namespace != common.COMMAND_NAMESPACE_IGNORED: + ctxt.add_new_namespace_incompatible_error(old_cmd.command_name, old_namespace, + new_namespace, new_idl_file_path) + else: + assert False, 'unrecognized namespace option' + + def check_compatibility(old_idl_dir: str, new_idl_dir: str, import_directories: List[str]) -> IDLCompatibilityErrorCollection: """Check IDL compatibility between old and new IDL commands.""" @@ -253,6 +392,9 @@ def check_compatibility(old_idl_dir: str, new_idl_dir: str, new_idl_file = new_command_file[old_cmd.command_name] new_idl_file_path = new_command_file_path[old_cmd.command_name] + check_namespace(ctxt, old_cmd, new_cmd, old_idl_file, new_idl_file, + old_idl_file_path, new_idl_file_path) + old_reply = old_idl_file.spec.symbols.get_struct(old_cmd.reply_type) new_reply = new_idl_file.spec.symbols.get_struct(new_cmd.reply_type) check_reply_fields(ctxt, old_reply, new_reply, old_cmd.command_name, diff --git a/buildscripts/idl/idl_compatibility_errors.py b/buildscripts/idl/idl_compatibility_errors.py index d248278ab3f..353f774eace 100644 --- a/buildscripts/idl/idl_compatibility_errors.py +++ b/buildscripts/idl/idl_compatibility_errors.py @@ -54,6 +54,17 @@ ERROR_ID_NEW_REPLY_FIELD_BSON_SERIALIZATION_TYPE_ANY = "ID0010" ERROR_ID_NEW_REPLY_FIELD_TYPE_ENUM_OR_STRUCT = "ID0011" ERROR_ID_REPLY_FIELD_TYPE_INVALID = "ID0012" ERROR_ID_COMMAND_NOT_SUBSET = "ID0013" +ERROR_ID_NEW_NAMESPACE_INCOMPATIBLE = "ID0014" +ERROR_ID_COMMAND_TYPE_NOT_SUPERSET = "ID0015" +ERROR_ID_COMMAND_TYPE_INVALID = "ID0016" +ERROR_ID_OLD_COMMAND_TYPE_BSON_SERIALIZATION_TYPE_ANY = "ID0017" +ERROR_ID_NEW_COMMAND_TYPE_BSON_SERIALIZATION_TYPE_ANY = "ID0018" +ERROR_ID_NEW_COMMAND_TYPE_FIELD_MISSING = "ID0019" +ERROR_ID_NEW_COMMAND_TYPE_FIELD_REQUIRED = "ID0020" +ERROR_ID_NEW_COMMAND_TYPE_FIELD_UNSTABLE = "ID0021" +ERROR_ID_NEW_COMMAND_TYPE_NOT_STRUCT = "ID0022" +ERROR_ID_NEW_COMMAND_TYPE_NOT_ENUM = "ID0023" +ERROR_ID_NEW_COMMAND_TYPE_ENUM_OR_STRUCT = "ID0024" class IDLCompatibilityCheckerError(Exception): @@ -165,6 +176,8 @@ class IDLCompatibilityContext(object): - single class responsible for producing actual error messages. """ + # pylint:disable=too-many-public-methods + def __init__(self, old_idl_dir: str, new_idl_dir: str, errors: IDLCompatibilityErrorCollection) -> None: """Construct a new IDLCompatibilityContext.""" @@ -200,6 +213,85 @@ class IDLCompatibilityContext(object): "'%s' has field '%s' with type '%s' that is not a subset of the other version of this command." % (command_name, field_name, type_name), file) + def add_command_type_invalid_error(self, command_name: str, file: str) -> None: + """Add an error about the command type being invalid.""" + self._add_error(ERROR_ID_COMMAND_TYPE_INVALID, command_name, + ("'%s' has an invalid type") % (command_name), file) + + def add_command_type_not_superset_error(self, command_name: str, type_name: str, + file: str) -> None: + """Add an error about the command type not being a subset.""" + self._add_error( + ERROR_ID_COMMAND_TYPE_NOT_SUPERSET, command_name, + "'%s' has type '%s' that is not a subset of the other version of this command." % + (command_name, type_name), file) + + def add_new_command_type_bson_any_error(self, command_name: str, new_type: str, + file: str) -> None: + """Add an error about the new command type's bson serialization type being of type "any".""" + self._add_error(ERROR_ID_NEW_COMMAND_TYPE_BSON_SERIALIZATION_TYPE_ANY, command_name, + ("'%s' has type '%s' that has a bson serialization type 'any'") % + (command_name, new_type), file) + + def add_new_command_type_enum_or_struct_error(self, command_name: str, new_type: str, + old_type: str, file: str) -> None: + # pylint: disable=too-many-arguments + """Add an error when the new command type is an enum or struct and the old one is a non-enum or struct type.""" + self._add_error(ERROR_ID_NEW_COMMAND_TYPE_ENUM_OR_STRUCT, command_name, + ("'%s' has type '%s' that is an enum or struct while the corresponding " + "old type was a non-enum or struct of type '%s'.") % + (command_name, new_type, old_type), file) + + def add_new_command_type_field_missing_error(self, command_name: str, type_name: str, + field_name: str, file: str) -> None: + """Add an error about the new command type missing a field that exists in the old command.""" + self._add_error( + ERROR_ID_NEW_COMMAND_TYPE_FIELD_MISSING, command_name, + "'%s' has type '%s' that is missing a field '%s' that exists in the old command." % + (command_name, type_name, field_name), file) + + def add_new_command_type_field_required_error(self, command_name: str, type_name: str, + field_name: str, file: str) -> None: + """Add an error about the new command type field being required when the old type field is not.""" + self._add_error( + ERROR_ID_NEW_COMMAND_TYPE_FIELD_REQUIRED, command_name, + "'%s' has type '%s' with a required type field '%s' that was optional in the old command." + % (command_name, type_name, field_name), file) + + def add_new_command_type_field_unstable_error(self, command_name: str, type_name, + field_name: str, file: str) -> None: + """Add an error about the new command type field being unstable when the old one is stable.""" + self._add_error( + ERROR_ID_NEW_COMMAND_TYPE_FIELD_UNSTABLE, command_name, + "'%s' has type '%s' with an unstable field '%s' that was stable in the old command." % + (command_name, type_name, field_name), file) + + def add_new_command_type_not_enum_error(self, command_name: str, new_type: str, old_type: str, + file: str) -> None: + # pylint: disable=too-many-arguments + """Add an error about the new command type not being an enum when the old one is.""" + self._add_error(ERROR_ID_NEW_COMMAND_TYPE_NOT_ENUM, command_name, + ("'%s' has type '%s' that is not an enum while the corresponding " + "old type was an enum of type '%s'.") % (command_name, new_type, old_type), + file) + + def add_new_command_type_not_struct_error(self, command_name: str, new_type: str, old_type: str, + file: str) -> None: + # pylint: disable=too-many-arguments + """Add an error about the new command type not being a struct when the old one is.""" + self._add_error( + ERROR_ID_NEW_COMMAND_TYPE_NOT_STRUCT, command_name, + ("'%s' has type '%s' that is not a struct while the corresponding " + "old type was a struct of type '%s'.") % (command_name, new_type, old_type), 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.""" + self._add_error( + ERROR_ID_NEW_NAMESPACE_INCOMPATIBLE, command_name, + "'%s' has namespace '%s' that is incompatible with the old namespace '%s'." % + (command_name, new_namespace, old_namespace), file) + def add_new_reply_field_missing_error(self, command_name: str, field_name: str, file: str) -> None: """Add an error about the new command missing a reply field that exists in the old command.""" @@ -263,6 +355,13 @@ class IDLCompatibilityContext(object): "'%s' has an unstable reply field '%s' that was stable in the old command." % (command_name, field_name), file) + def add_old_command_type_bson_any_error(self, command_name: str, old_type: str, + file: str) -> None: + """Add an error about the old command type's bson serialization type being of type "any".""" + self._add_error(ERROR_ID_OLD_COMMAND_TYPE_BSON_SERIALIZATION_TYPE_ANY, command_name, + ("'%s' has type '%s' that has a bson serialization type 'any'") % + (command_name, old_type), file) + def add_old_reply_field_bson_any_error(self, command_name: str, field_name: str, old_field_type: str, file: str) -> None: """Add an error about the old reply field type's bson serialization type being of type "any".""" 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 c7f836b2a27..aaf7cd22272 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 @@ -65,6 +65,15 @@ types: description: "The new bson_serialization_type contains 'any'" cpp_type: "std::int32_t" + intStringBoolToIntString: + bson_serialization_type: + - int + - string + description: "The bson_serialization_type changes from [int, string, bool] in the old + command's reply field type to [int, string] in the new command's reply + field type" + cpp_type: "std::int32_t" + enums: NewReplyFieldEnumNotSubset: description: "The new reply type is an enum that is not a subset of the old reply type's @@ -75,6 +84,13 @@ enums: valueTwo: "two" valueThree: "three" + EnumNotSuperset: + description: "The new enum is not a superset of the old enum values" + type: string + values: + valueOne: "one" + valueTwo: "two" + structs: UnstableNewFieldReply: description: "This reply contains a field that is stable in the old command but is @@ -95,6 +111,13 @@ structs: MissingNewFieldReply: description: "This reply contains a field that exists in the old command but is missing in the new command." + + RequiredNewField: + description: "This struct contains a field that is optional in the old command but is + required in the new command." + fields: + requiredNewField: + type: string EnumNotSubsetReply: description: "This reply contains an enum field where the new enum values is not a subset @@ -353,3 +376,163 @@ commands: strict: true api_version: "1" reply_type: StructFieldTypeRecursiveReplyTwo + + newNamespaceNotIgnored: + description: "new command fails because its namespace is not ignored" + command_name: newNamespaceNotIgnored + namespace: concatenate_with_db_or_uuid + cpp_name: newNamespaceNotIgnored + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceNotConcatenateWithDbOrUuid: + description: "new command fails because its namespace is not concatenate_with_db_or_uuid + or ignored" + command_name: newNamespaceNotConcatenateWithDbOrUuid + namespace: concatenate_with_db + cpp_name: newNamespaceNotConcatenateWithDbOrUuid + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceNotConcatenateWithDb: + description: "new command fails because its namespace changes to type" + command_name: newNamespaceNotConcatenateWithDb + namespace: type + type: string + cpp_name: newNamespaceNotConcatenateWithDb + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceNotType: + description: "new command fails because its namespace changes from type in + an incompatible way" + command_name: newNamespaceNotType + namespace: concatenate_with_db + cpp_name: newNamespaceNotType + strict: true + api_version: "1" + reply_type: OkReply + + oldTypeBsonAny: + description: "old command fails because its type has a bson_serialization_type + that contains 'any'" + command_name: oldTypeBsonAny + namespace: type + type: oldBsonSerializationTypeAny + cpp_name: oldTypeBsonAny + strict: true + api_version: "1" + reply_type: OkReply + + newTypeBsonAny: + description: "new command fails because its type has a bson_serialization_type + that contains 'any'" + command_name: newTypeBsonAny + namespace: type + type: newBsonSerializationTypeAny + cpp_name: newTypeBsonAny + strict: true + api_version: "1" + reply_type: OkReply + + newTypeNotSuperset: + description: "new command fails because its type is not a superset + of the old type" + command_name: newTypeNotSuperset + namespace: type + type: intStringBoolToIntString + cpp_name: newTypeNotSuperset + strict: true + api_version: "1" + reply_type: OkReply + + newTypeEnumNotSuperset: + description: "new command fails because its type is an enum that is not + a superset of the corresponding old type's enum values" + command_name: newTypeEnumNotSuperset + namespace: type + type: EnumNotSuperset + cpp_name: newTypeEnumNotSuperset + strict: true + api_version: "1" + reply_type: OkReply + + newTypeNotEnum: + description: "new command fails because its type is not an enum when the + old type is an enum" + command_name: newTypeNotEnum + namespace: type + type: StructType + cpp_name: newTypeNotEnum + strict: true + api_version: "1" + reply_type: OkReply + + newTypeNotStruct: + description: "new command fails because its type is not a struct when the + old type is a struct" + command_name: newTypeNotStruct + namespace: type + type: EnumNotSuperset + cpp_name: newTypeNotStruct + strict: true + api_version: "1" + reply_type: OkReply + + newTypeEnumOrStruct: + description: "new command fails because the type is a non-enum or struct + type in the old command, and an enum or struct in the new command" + command_name: newTypeEnumOrStruct + namespace: type + type: StructType + cpp_name: newTypeEnumOrStruct + strict: true + api_version: "1" + reply_type: OkReply + + newTypeStructRecursive: + description: "new command fails because its type is a struct that is + incompatible with the old type struct" + command_name: newTypeStructRecursive + namespace: type + type: StructFieldTypeRecursiveReplyOne + cpp_name: newTypeStructRecursive + strict: true + api_version: "1" + reply_type: OkReply + + newTypeFieldUnstable: + description: "new command fails because it contains an unstable type field that is stable + in the corresponding old command" + command_name: newTypeFieldUnstable + namespace: type + type: UnstableNewFieldReply + cpp_name: newTypeFieldUnstable + strict: true + api_version: "1" + reply_type: OkReply + + newTypeFieldRequired: + description: "new command fails because it contains a required reply field that is + optional in the corresponding old command" + command_name: newTypeFieldRequired + namespace: type + type: RequiredNewField + cpp_name: newTypeFieldRequired + strict: true + api_version: "1" + reply_type: OkReply + + newTypeFieldMissing: + description: "new command fails because it is missing a type field that exists in + the corresponding old command" + command_name: newTypeFieldMissing + namespace: type + type: MissingNewFieldReply + cpp_name: newTypeFieldMissing + strict: true + api_version: "1" + reply_type: OkReply 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 ca2c9c5915e..6a0fb8ff267 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 @@ -61,6 +61,16 @@ types: - int description: "The new bson_serialization_type contains 'any'" cpp_type: "std::int32_t" + + intStringBoolToIntString: + bson_serialization_type: + - int + - string + - bool + description: "The bson_serialization_type changes from [int, string, bool] in the old + command's reply field type to [int, string] in the new command's reply + field type" + cpp_type: "std::int32_t" enums: NewReplyFieldEnumNotSubset: @@ -70,6 +80,14 @@ enums: values: valueOne: "one" valueTwo: "two" + + EnumNotSuperset: + description: "The new enum is not a superset of the old enum values" + type: string + values: + valueOne: "one" + valueTwo: "two" + valueThree: "three" structs: UnstableNewFieldReply: @@ -86,6 +104,14 @@ structs: optionalNewField: type: string + RequiredNewField: + description: "This struct contains a field that is optional in the old command but is + required in the new command." + fields: + requiredNewField: + type: string + optional: true + MissingNewFieldReply: description: "This reply contains a field that exists in the old command but is missing in the new command." @@ -343,3 +369,163 @@ commands: strict: true api_version: "1" reply_type: StructFieldTypeRecursiveReplyTwo + + newNamespaceNotIgnored: + description: "new command fails because its namespace is not ignored" + command_name: newNamespaceNotIgnored + namespace: ignored + cpp_name: newNamespaceNotIgnored + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceNotConcatenateWithDbOrUuid: + description: "new command fails because its namespace is not concatenate_with_db_or_uuid + or ignored" + command_name: newNamespaceNotConcatenateWithDbOrUuid + namespace: concatenate_with_db_or_uuid + cpp_name: newNamespaceNotConcatenateWithDbOrUuid + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceNotConcatenateWithDb: + description: "new command fails because its namespace changes to type" + command_name: newNamespaceNotConcatenateWithDb + namespace: concatenate_with_db + cpp_name: newNamespaceNotConcatenateWithDb + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceNotType: + description: "new command fails because its namespace changes from type in + an incompatible way" + command_name: newNamespaceNotType + namespace: type + type: string + cpp_name: newNamespaceNotType + strict: true + api_version: "1" + reply_type: OkReply + + oldTypeBsonAny: + description: "old command fails because its type has a bson_serialization_type + that contains 'any'" + command_name: oldTypeBsonAny + namespace: type + type: oldBsonSerializationTypeAny + cpp_name: oldTypeBsonAny + strict: true + api_version: "1" + reply_type: OkReply + + newTypeBsonAny: + description: "new command fails because its type has a bson_serialization_type + that contains 'any'" + command_name: newTypeBsonAny + namespace: type + type: newBsonSerializationTypeAny + cpp_name: newTypeBsonAny + strict: true + api_version: "1" + reply_type: OkReply + + newTypeNotSuperset: + description: "new command fails because its type is not a superset + of the old type" + command_name: newTypeNotSuperset + namespace: type + type: intStringBoolToIntString + cpp_name: newTypeNotSuperset + strict: true + api_version: "1" + reply_type: OkReply + + newTypeEnumNotSuperset: + description: "new command fails because its type is an enum that is not + a superset of the corresponding old type's enum values" + command_name: newTypeEnumNotSuperset + namespace: type + type: EnumNotSuperset + cpp_name: newTypeEnumNotSuperset + strict: true + api_version: "1" + reply_type: OkReply + + newTypeNotEnum: + description: "new command fails because its type is not an enum when the + old type is an enum" + command_name: newTypeNotEnum + namespace: type + type: EnumNotSuperset + cpp_name: newTypeNotEnum + strict: true + api_version: "1" + reply_type: OkReply + + newTypeNotStruct: + description: "new command fails because its type is not a struct when the + old type is a struct" + command_name: newTypeNotStruct + namespace: type + type: StructType + cpp_name: newTypeNotStruct + strict: true + api_version: "1" + reply_type: OkReply + + newTypeEnumOrStruct: + description: "new command fails because the type is a non-enum or struct + type in the old command, and an enum or struct in the new command" + command_name: newTypeEnumOrStruct + namespace: type + type: namespacestring + cpp_name: newTypeEnumOrStruct + strict: true + api_version: "1" + reply_type: OkReply + + newTypeStructRecursive: + description: "new command fails because its type is a struct that is + incompatible with the old type struct" + command_name: newTypeStructRecursive + namespace: type + type: StructFieldTypeRecursiveReplyOne + cpp_name: newTypeStructRecursive + strict: true + api_version: "1" + reply_type: OkReply + + newTypeFieldUnstable: + description: "new command fails because it contains an unstable type field that is stable + in the corresponding old command" + command_name: newTypeFieldUnstable + namespace: type + type: UnstableNewFieldReply + cpp_name: newTypeFieldUnstable + strict: true + api_version: "1" + reply_type: OkReply + + newTypeFieldRequired: + description: "new command fails because it contains a required reply field that is + optional in the corresponding old command" + command_name: newTypeFieldRequired + namespace: type + type: RequiredNewField + cpp_name: newTypeFieldRequired + strict: true + api_version: "1" + reply_type: OkReply + + newTypeFieldMissing: + description: "new command fails because it is missing a type field that exists in + the corresponding old command" + command_name: newTypeFieldMissing + namespace: type + type: MissingNewFieldReply + cpp_name: newTypeFieldMissing + strict: true + api_version: "1" + reply_type: OkReply 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 931f0d5ec53..5509de88591 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 @@ -48,6 +48,16 @@ types: command's reply field type to [int, string] in the new command's reply field type" cpp_type: "std::int32_t" + + intStringToIntStringBool: + bson_serialization_type: + - int + - string + - bool + description: "The bson_serialization_type changes from [int, string] in the old command's + reply field type to [int, string, bool] in the new command's reply field + type" + cpp_type: "std::int32_t" enums: NewReplyFieldEnumSubset: @@ -57,6 +67,14 @@ enums: values: valueOne: "one" valueTwo: "two" + + EnumSuperset: + description: "The new enum is not a superset of the old enum values" + type: string + values: + valueOne: "one" + valueTwo: "two" + valueThree: "three" structs: StableNewFieldReply: @@ -68,11 +86,19 @@ structs: RequiredNewFieldReply: description: "This reply contains a field that is optional in the old command but is - unstable in the new command." + required in the new command." fields: requiredNewField: type: string + OptionalNewField: + description: "This struct contains a field that is required in the old command but is + optional in the new command." + fields: + optionalNewField: + type: string + optional: true + AddedNewFieldReply: description: "This reply contains a field that is added in the new command." fields: @@ -248,3 +274,84 @@ commands: strict: true api_version: "1" reply_type: StructFieldTypeRecursiveReplyTwo + + newNamespaceIgnored: + description: "new command passes when its namespace is changed to ignored" + command_name: newNamespaceIgnored + namespace: ignored + cpp_name: newNamespaceIgnored + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceConcatenateWithDbOrUuid: + description: "new command passes when its namespace is changed to concatenate_with_db_or_uuid + from concatenate_with_db" + command_name: newNamespaceConcatenateWithDbOrUuid + namespace: concatenate_with_db_or_uuid + cpp_name: newNamespaceConcatenateWithDbOrUuid + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceTypeToIgnored: + description: "new command passes when its namespace is changed from type to ignored" + command_name: newNamespaceTypeToIgnored + namespace: ignored + cpp_name: newNamespaceTypeToIgnored + strict: true + api_version: "1" + reply_type: OkReply + + oldNamespaceTypeNamespaceString: + description: "If old command has namespace: type and type: namespacestring, the new namespace + can be changed to concatenate_with_db" + command_name: oldNamespaceTypeNamespaceString + namespace: concatenate_with_db + cpp_name: oldNamespaceTypeNamespaceString + strict: true + api_version: "1" + reply_type: OkReply + + oldNamespaceTypeNamespaceStringTwo: + description: "If old command has namespace: type and type: namespacestring, the new namespace + can be changed to concatenate_with_db_or_uuid" + command_name: oldNamespaceTypeNamespaceStringTwo + namespace: concatenate_with_db_or_uuid + cpp_name: oldNamespaceTypeNamespaceStringTwo + strict: true + api_version: "1" + reply_type: OkReply + + newTypeSuperset: + description: "new command passes because its type is a superset + of the old type" + command_name: newTypeSuperset + namespace: type + type: intStringToIntStringBool + cpp_name: newTypeSuperset + strict: true + api_version: "1" + reply_type: OkReply + + newTypeEnumSuperset: + description: "new command passes because its type is an enum that is + a superset of the corresponding old type's enum values" + command_name: newTypeEnumSuperset + namespace: type + type: EnumSuperset + cpp_name: newTypeEnumSuperset + strict: true + api_version: "1" + reply_type: OkReply + + newTypeFieldOptional: + description: "new command type contains an optional field that is required + in the corresponding old command and still passes" + command_name: newTypeFieldOptional + namespace: type + type: OptionalNewField + cpp_name: newTypeFieldOptional + strict: true + api_version: "1" + reply_type: OkReply 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 a70d8ef3747..99bc674cccd 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 @@ -51,6 +51,15 @@ types: command's reply field type to [int, string] in the new command's reply field type" cpp_type: "std::int32_t" + + intStringToIntStringBool: + bson_serialization_type: + - int + - string + description: "The bson_serialization_type changes from [int, string] in the old command's + reply field type to [int, string, bool] in the new command's reply field + type" + cpp_type: "std::int32_t" enums: NewReplyFieldEnumSubset: @@ -61,6 +70,13 @@ enums: valueOne: "one" valueTwo: "two" valueThree: "three" + + EnumSuperset: + description: "The new enum is not a superset of the old enum values" + type: string + values: + valueOne: "one" + valueTwo: "two" structs: StableNewFieldReply: @@ -73,12 +89,19 @@ structs: RequiredNewFieldReply: description: "This reply contains a field that is optional in the old command but is - unstable in the new command." + required in the new command." fields: requiredNewField: type: string optional: true + OptionalNewField: + description: "This struct contains a field that is required in the old command but is + optional in the new command." + fields: + optionalNewField: + type: string + AddedNewFieldReply: description: "This reply contains a field that is added in the new command." @@ -250,3 +273,87 @@ commands: strict: true api_version: "1" reply_type: StructFieldTypeRecursiveReplyTwo + + newNamespaceIgnored: + description: "new command passes when its namespace is changed to ignored" + command_name: newNamespaceIgnored + namespace: concatenate_with_db_or_uuid + cpp_name: newNamespaceIgnored + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceConcatenateWithDbOrUuid: + description: "new command passes when its namespace is changed to concatenate_with_db_or_uuid + from concatenate_with_db" + command_name: newNamespaceConcatenateWithDbOrUuid + namespace: concatenate_with_db + cpp_name: newNamespaceConcatenateWithDbOrUuid + strict: true + api_version: "1" + reply_type: OkReply + + newNamespaceTypeToIgnored: + description: "new command passes when its namespace is changed from type to ignored" + command_name: newNamespaceTypeToIgnored + namespace: type + type: string + cpp_name: newNamespaceTypeToIgnored + strict: true + api_version: "1" + reply_type: OkReply + + oldNamespaceTypeNamespaceString: + description: "If old command has namespace: type and type: namespacestring, the new namespace + can be changed to concatenate_with_db" + command_name: oldNamespaceTypeNamespaceString + namespace: type + type: namespacestring + cpp_name: oldNamespaceTypeNamespaceString + strict: true + api_version: "1" + reply_type: OkReply + + oldNamespaceTypeNamespaceStringTwo: + description: "If old command has namespace: type and type: namespacestring, the new namespace + can be changed to concatenate_with_db_or_uuid" + command_name: oldNamespaceTypeNamespaceStringTwo + namespace: type + type: namespacestring + cpp_name: oldNamespaceTypeNamespaceStringTwo + strict: true + api_version: "1" + reply_type: OkReply + + newTypeSuperset: + description: "new command passes because its type is a superset + of the old type" + command_name: newTypeSuperset + namespace: type + type: intStringToIntStringBool + cpp_name: newTypeSuperset + strict: true + api_version: "1" + reply_type: OkReply + + newTypeEnumSuperset: + description: "new command passes because its type is an enum that is + a superset of the corresponding old type's enum values" + command_name: newTypeEnumSuperset + namespace: type + type: EnumSuperset + cpp_name: newTypeEnumSuperset + strict: true + api_version: "1" + reply_type: OkReply + + newTypeFieldOptional: + description: "new command type contains an optional field that is required + in the corresponding old command and still passes" + command_name: newTypeFieldOptional + namespace: type + type: OptionalNewField + cpp_name: newTypeFieldOptional + strict: true + api_version: "1" + reply_type: OkReply diff --git a/buildscripts/idl/tests/test_compatibility.py b/buildscripts/idl/tests/test_compatibility.py index 44960facec4..4673d9124ad 100644 --- a/buildscripts/idl/tests/test_compatibility.py +++ b/buildscripts/idl/tests/test_compatibility.py @@ -69,7 +69,7 @@ class TestIDLCompatibilityChecker(unittest.TestCase): path.join(dir_path, "compatibility_test_fail/new"), ["src"]) self.assertTrue(error_collection.has_errors()) - self.assertTrue(error_collection.count() == 19) + self.assertTrue(error_collection.count() == 34) invalid_api_version_new_error = error_collection.get_error_by_command_name( "invalidAPIVersionNew") @@ -176,6 +176,85 @@ class TestIDLCompatibilityChecker(unittest.TestCase): self.assertRegex( str(new_reply_field_type_struct_two_error), "newReplyFieldTypeStructRecursiveTwo") + new_namespace_not_ignored_error = error_collection.get_error_by_command_name( + "newNamespaceNotIgnored") + self.assertTrue(new_namespace_not_ignored_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_NAMESPACE_INCOMPATIBLE) + self.assertRegex(str(new_namespace_not_ignored_error), "newNamespaceNotIgnored") + + new_namespace_not_concatenate_with_db_or_uuid_error = error_collection.get_error_by_command_name( + "newNamespaceNotConcatenateWithDbOrUuid") + self.assertTrue(new_namespace_not_concatenate_with_db_or_uuid_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_NAMESPACE_INCOMPATIBLE) + self.assertRegex( + str(new_namespace_not_concatenate_with_db_or_uuid_error), + "newNamespaceNotConcatenateWithDbOrUuid") + + new_namespace_not_concatenate_with_db_error = error_collection.get_error_by_command_name( + "newNamespaceNotConcatenateWithDb") + self.assertTrue(new_namespace_not_concatenate_with_db_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_NAMESPACE_INCOMPATIBLE) + self.assertRegex( + str(new_namespace_not_concatenate_with_db_error), "newNamespaceNotConcatenateWithDb") + + new_namespace_not_type_error = error_collection.get_error_by_command_name( + "newNamespaceNotType") + self.assertTrue(new_namespace_not_type_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_NAMESPACE_INCOMPATIBLE) + self.assertRegex(str(new_namespace_not_type_error), "newNamespaceNotType") + + old_type_bson_any_error = error_collection.get_error_by_error_id( + idl_compatibility_errors.ERROR_ID_OLD_COMMAND_TYPE_BSON_SERIALIZATION_TYPE_ANY) + self.assertRegex(str(old_type_bson_any_error), "oldTypeBsonAny") + + new_type_bson_any_error = error_collection.get_error_by_error_id( + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_BSON_SERIALIZATION_TYPE_ANY) + self.assertRegex(str(new_type_bson_any_error), "newTypeBsonAny") + + new_type_not_enum_error = error_collection.get_error_by_error_id( + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_NOT_ENUM) + self.assertRegex(str(new_type_not_enum_error), "newTypeNotEnum") + + new_type_not_struct_error = error_collection.get_error_by_error_id( + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_NOT_STRUCT) + self.assertRegex(str(new_type_not_struct_error), "newTypeNotStruct") + + new_type_enum_or_struct_error = error_collection.get_error_by_error_id( + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_ENUM_OR_STRUCT) + self.assertRegex(str(new_type_enum_or_struct_error), "newTypeEnumOrStruct") + + new_type_not_superset_error = error_collection.get_error_by_command_name( + "newTypeNotSuperset") + self.assertTrue(new_type_not_superset_error.error_id == + idl_compatibility_errors.ERROR_ID_COMMAND_TYPE_NOT_SUPERSET) + self.assertRegex(str(new_type_not_superset_error), "newTypeNotSuperset") + + new_type_enum_not_superset_error = error_collection.get_error_by_command_name( + "newTypeEnumNotSuperset") + self.assertTrue(new_type_enum_not_superset_error.error_id == + idl_compatibility_errors.ERROR_ID_COMMAND_TYPE_NOT_SUPERSET) + self.assertRegex(str(new_type_enum_not_superset_error), "newTypeEnumNotSuperset") + + new_type_struct_recursive_error = error_collection.get_error_by_command_name( + "newTypeStructRecursive") + self.assertTrue(new_type_struct_recursive_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_FIELD_UNSTABLE) + self.assertRegex(str(new_type_struct_recursive_error), "newTypeStructRecursive") + + new_type_field_unstable_error = error_collection.get_error_by_command_name( + "newTypeFieldUnstable") + self.assertTrue(new_type_field_unstable_error.error_id == + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_FIELD_UNSTABLE) + self.assertRegex(str(new_type_field_unstable_error), "newTypeFieldUnstable") + + new_type_field_required_error = error_collection.get_error_by_error_id( + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_FIELD_REQUIRED) + self.assertRegex(str(new_type_field_required_error), "newTypeFieldRequired") + + new_type_field_missing_error = error_collection.get_error_by_error_id( + idl_compatibility_errors.ERROR_ID_NEW_COMMAND_TYPE_FIELD_MISSING) + self.assertRegex(str(new_type_field_missing_error), "newTypeFieldMissing") + if __name__ == '__main__': unittest.main() |