summaryrefslogtreecommitdiff
path: root/buildscripts/idl
diff options
context:
space:
mode:
authorHuayu Ouyang <huayu.ouyang@mongodb.com>2021-12-09 15:57:04 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-12-09 17:42:47 +0000
commit498f8a7cffd652321d235b4684ffad6897cdd792 (patch)
tree2c06419c112a035ec88db54de929d0365a5959ec /buildscripts/idl
parent61652acf89b2d9e5e3f22cfe74c1de0979d40cd1 (diff)
downloadmongo-498f8a7cffd652321d235b4684ffad6897cdd792.tar.gz
SERVER-61551 IDL compatibility checker should check chained structs
Diffstat (limited to 'buildscripts/idl')
-rw-r--r--buildscripts/idl/idl_check_compatibility.py174
-rw-r--r--buildscripts/idl/idl_compatibility_errors.py44
-rw-r--r--buildscripts/idl/tests/compatibility_test_fail/new/compatibility_test_fail_new.idl84
-rw-r--r--buildscripts/idl/tests/compatibility_test_fail/old/compatibility_test_fail_old.idl84
-rw-r--r--buildscripts/idl/tests/compatibility_test_pass/new/compatibility_test_pass_new.idl54
-rw-r--r--buildscripts/idl/tests/compatibility_test_pass/old/compatibility_test_pass_old.idl54
-rw-r--r--buildscripts/idl/tests/test_compatibility.py43
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__))