summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHuayu Ouyang <huayu.ouyang@mongodb.com>2021-02-04 23:22:39 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-02-09 18:33:31 +0000
commit3a23cc592fcfb2fa50b277782fddb4ff2b94171f (patch)
tree9a6db17f9956f538083a9fd5b20f427358b1751a
parent4b8f3c1b9bb07dab930f56e37285416820a6e812 (diff)
downloadmongo-3a23cc592fcfb2fa50b277782fddb4ff2b94171f.tar.gz
SERVER-54109 Check for incompatible "namespace" and "type" field changes in IDL compatibility checker script
-rw-r--r--buildscripts/idl/idl_check_compatibility.py146
-rw-r--r--buildscripts/idl/idl_compatibility_errors.py99
-rw-r--r--buildscripts/idl/tests/compatibility_test_fail/new/compatibility_test_fail_new.idl183
-rw-r--r--buildscripts/idl/tests/compatibility_test_fail/old/compatibility_test_fail_old.idl186
-rw-r--r--buildscripts/idl/tests/compatibility_test_pass/new/compatibility_test_pass_new.idl109
-rw-r--r--buildscripts/idl/tests/compatibility_test_pass/old/compatibility_test_pass_old.idl109
-rw-r--r--buildscripts/idl/tests/test_compatibility.py81
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()