def check_error_reply(old_basic_types_path: str, new_basic_types_path: str, import_directories: List[str]) -> IDLCompatibilityErrorCollection: """Check IDL compatibility between old and new ErrorReply.""" old_idl_dir = os.path.dirname(old_basic_types_path) new_idl_dir = os.path.dirname(new_basic_types_path) ctxt = IDLCompatibilityContext(old_idl_dir, new_idl_dir, IDLCompatibilityErrorCollection()) with open(old_basic_types_path) as old_file: old_idl_file = parser.parse(old_file, old_basic_types_path, CompilerImportResolver(import_directories)) if old_idl_file.errors: old_idl_file.errors.dump_errors() raise ValueError(f"Cannot parse {old_basic_types_path}") old_error_reply_struct = old_idl_file.spec.symbols.get_struct("ErrorReply") if old_error_reply_struct is None: ctxt.add_missing_error_reply_error(old_basic_types_path) else: with open(new_basic_types_path) as new_file: new_idl_file = parser.parse(new_file, new_basic_types_path, CompilerImportResolver(import_directories)) if new_idl_file.errors: new_idl_file.errors.dump_errors() raise ValueError(f"Cannot parse {new_basic_types_path}") new_error_reply_struct = new_idl_file.spec.symbols.get_struct("ErrorReply") if new_error_reply_struct is None: ctxt.add_missing_error_reply_error(new_basic_types_path) else: check_reply_fields(ctxt, old_error_reply_struct, new_error_reply_struct, "n/a", old_idl_file, new_idl_file, old_basic_types_path, new_basic_types_path) ctxt.errors.dump_errors() return ctxt.errors
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.""" # pylint: disable=too-many-locals ctxt = IDLCompatibilityContext(old_idl_dir, new_idl_dir, IDLCompatibilityErrorCollection()) new_commands, new_command_file, new_command_file_path = get_new_commands( ctxt, new_idl_dir, import_directories) # Check new commands' compatibility with old ones. # Note, a command can be added to V1 at any time, it's ok if a # new command has no corresponding old command. old_commands: Dict[str, syntax.Command] = dict() for dirpath, _, filenames in os.walk(old_idl_dir): for old_filename in filenames: if not old_filename.endswith('.idl'): continue old_idl_file_path = os.path.join(dirpath, old_filename) with open(old_idl_file_path) as old_file: old_idl_file = parser.parse( old_file, old_idl_file_path, CompilerImportResolver(import_directories + [old_idl_dir])) if old_idl_file.errors: old_idl_file.errors.dump_errors() raise ValueError(f"Cannot parse {old_idl_file_path}") for old_cmd in old_idl_file.spec.symbols.commands: if old_cmd.api_version == "": continue if old_cmd.api_version != "1": # We're not ready to handle future API versions yet. ctxt.add_command_invalid_api_version_error( old_cmd.command_name, old_cmd.api_version, old_idl_file_path) continue if old_cmd.command_name in old_commands: ctxt.add_duplicate_command_name_error(old_cmd.command_name, old_idl_dir, old_idl_file_path) continue old_commands[old_cmd.command_name] = old_cmd if old_cmd.command_name not in new_commands: # Can't remove a command from V1 ctxt.add_command_removed_error(old_cmd.command_name, old_idl_file_path) continue new_cmd = new_commands[old_cmd.command_name] new_idl_file = new_command_file[old_cmd.command_name] new_idl_file_path = new_command_file_path[old_cmd.command_name] check_reply_fields(ctxt, old_cmd, new_cmd, old_idl_file, new_idl_file, old_idl_file_path, new_idl_file_path) ctxt.errors.dump_errors() return ctxt.errors
def parse_idl(idl_path: str, import_directories: List[str]) -> syntax.IDLParsedSpec: """Parse an IDL file or throw an error.""" parsed_doc = parser.parse(open(idl_path), idl_path, CompilerImportResolver(import_directories)) if parsed_doc.errors: parsed_doc.errors.dump_errors() raise ValueError(f"Cannot parse {idl_path}") return parsed_doc
def get_new_commands( ctxt: IDLCompatibilityContext, new_idl_dir: str, import_directories: List[str] ) -> Tuple[Dict[str, syntax.Command], Dict[str, syntax.IDLParsedSpec], Dict[ str, str]]: """Get new IDL commands and check validity.""" new_commands: Dict[str, syntax.Command] = dict() new_command_file: Dict[str, syntax.IDLParsedSpec] = dict() new_command_file_path: Dict[str, str] = dict() for dirpath, _, filenames in os.walk(new_idl_dir): for new_filename in filenames: if not new_filename.endswith( '.idl') or new_filename in SKIPPED_FILES: continue new_idl_file_path = os.path.join(dirpath, new_filename) with open(new_idl_file_path) as new_file: new_idl_file = parser.parse( new_file, new_idl_file_path, CompilerImportResolver(import_directories + [new_idl_dir])) if new_idl_file.errors: new_idl_file.errors.dump_errors() raise ValueError(f"Cannot parse {new_idl_file_path}") for new_cmd in new_idl_file.spec.symbols.commands: # Ignore imported commands as they will be processed in their own file. if new_cmd.api_version == "" or new_cmd.imported: continue if new_cmd.api_version != "1": # We're not ready to handle future API versions yet. ctxt.add_command_invalid_api_version_error( new_cmd.command_name, new_cmd.api_version, new_idl_file_path) continue if new_cmd.command_name in new_commands: ctxt.add_duplicate_command_name_error( new_cmd.command_name, new_idl_dir, new_idl_file_path) continue new_commands[new_cmd.command_name] = new_cmd new_command_file[new_cmd.command_name] = new_idl_file new_command_file_path[ new_cmd.command_name] = new_idl_file_path return new_commands, new_command_file, new_command_file_path
def check_compatibility( old_idl_dir: str, new_idl_dir: str, import_directories: List[str] ) -> idl_compatibility_errors.IDLCompatibilityErrorCollection: """Check IDL compatibility between old and new IDL commands.""" ctxt = idl_compatibility_errors.IDLCompatibilityContext( old_idl_dir, new_idl_dir, idl_compatibility_errors.IDLCompatibilityErrorCollection()) new_commands: Dict[str, syntax.Command] = dict() for dirpath, _, filenames in os.walk(new_idl_dir): for new_filename in filenames: new_idl_file_path = os.path.join(dirpath, new_filename) with open(new_idl_file_path) as new_file: new_idl_file = parser.parse( new_file, new_idl_file_path, CompilerImportResolver(import_directories + [new_idl_dir])) if new_idl_file.errors: new_idl_file.errors.dump_errors() raise ValueError(f"Cannot parse {new_idl_file_path}") for new_cmd in new_idl_file.spec.symbols.commands: if new_cmd.api_version == "": continue if new_cmd.api_version != "1": # We're not ready to handle future API versions yet. ctxt.add_command_invalid_api_version_error( new_cmd.command_name, new_cmd.api_version, new_idl_file_path) continue if new_cmd.command_name in new_commands: ctxt.add_duplicate_command_name_error( new_cmd.command_name, new_idl_dir, new_idl_file_path) continue new_commands[new_cmd.command_name] = new_cmd ctxt.errors.dump_errors() return ctxt.errors
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.""" # pylint: disable=too-many-locals ctxt = IDLCompatibilityContext(old_idl_dir, new_idl_dir, IDLCompatibilityErrorCollection()) new_commands, new_command_file, new_command_file_path = get_new_commands( ctxt, new_idl_dir, import_directories) # Check new commands' compatibility with old ones. # Note, a command can be added to V1 at any time, it's ok if a # new command has no corresponding old command. old_commands: Dict[str, syntax.Command] = dict() for dirpath, _, filenames in os.walk(old_idl_dir): for old_filename in filenames: if not old_filename.endswith('.idl') or old_filename in SKIPPED_FILES: continue old_idl_file_path = os.path.join(dirpath, old_filename) with open(old_idl_file_path) as old_file: old_idl_file = parser.parse( old_file, old_idl_file_path, CompilerImportResolver(import_directories + [old_idl_dir])) if old_idl_file.errors: old_idl_file.errors.dump_errors() raise ValueError(f"Cannot parse {old_idl_file_path}") for old_cmd in old_idl_file.spec.symbols.commands: # Ignore imported commands as they will be processed in their own file. if old_cmd.api_version == "" or old_cmd.imported: continue if old_cmd.api_version != "1": # We're not ready to handle future API versions yet. ctxt.add_command_invalid_api_version_error( old_cmd.command_name, old_cmd.api_version, old_idl_file_path) continue if old_cmd.command_name in old_commands: ctxt.add_duplicate_command_name_error(old_cmd.command_name, old_idl_dir, old_idl_file_path) continue old_commands[old_cmd.command_name] = old_cmd if old_cmd.command_name not in new_commands: # Can't remove a command from V1 ctxt.add_command_removed_error(old_cmd.command_name, old_idl_file_path) continue new_cmd = new_commands[old_cmd.command_name] new_idl_file = new_command_file[old_cmd.command_name] new_idl_file_path = new_command_file_path[old_cmd.command_name] if not old_cmd.strict and new_cmd.strict: ctxt.add_command_strict_true_error(new_cmd.command_name, new_idl_file_path) # Check compatibility of command's parameters. check_command_params_or_type_struct_fields( ctxt, old_cmd, new_cmd, old_cmd.command_name, old_idl_file, new_idl_file, old_idl_file_path, new_idl_file_path, is_command_parameter=True) 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, old_idl_file, new_idl_file, old_idl_file_path, new_idl_file_path) check_security_access_checks(ctxt, old_cmd.access_check, new_cmd.access_check, old_cmd, new_idl_file_path) # TODO (SERVER-55203): Remove error_skipped logic. ctxt.errors.remove_skipped_errors_and_dump_all_errors("Commands", old_idl_dir, new_idl_dir) return ctxt.errors
def check_compatibility( old_idl_dir: str, new_idl_dir: str, import_directories: List[str] ) -> idl_compatibility_errors.IDLCompatibilityErrorCollection: # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Check IDL compatibility between old and new IDL commands.""" ctxt = idl_compatibility_errors.IDLCompatibilityContext( old_idl_dir, new_idl_dir, idl_compatibility_errors.IDLCompatibilityErrorCollection()) new_commands: Dict[str, syntax.Command] = dict() new_command_replies: Dict[str, syntax.Struct] = dict() # command_name -> reply for dirpath, _, filenames in os.walk(new_idl_dir): for new_filename in filenames: if not new_filename.endswith('.idl'): continue new_idl_file_path = os.path.join(dirpath, new_filename) with open(new_idl_file_path) as new_file: new_idl_file = parser.parse( new_file, new_idl_file_path, CompilerImportResolver(import_directories + [new_idl_dir])) if new_idl_file.errors: new_idl_file.errors.dump_errors() raise ValueError(f"Cannot parse {new_idl_file_path}") for new_cmd in new_idl_file.spec.symbols.commands: if new_cmd.api_version == "": continue if new_cmd.api_version != "1": # We're not ready to handle future API versions yet. ctxt.add_command_invalid_api_version_error( new_cmd.command_name, new_cmd.api_version, new_idl_file_path) continue if new_cmd.command_name in new_commands: ctxt.add_duplicate_command_name_error( new_cmd.command_name, new_idl_dir, new_idl_file_path) continue new_commands[new_cmd.command_name] = new_cmd new_reply = new_idl_file.spec.symbols.get_struct( new_cmd.reply_type) new_command_replies[new_cmd.command_name] = new_reply # Check new commands' compatibility with old ones. # Note, a command can be added to V1 at any time, it's ok if a # new command has no corresponding old command. old_commands: Dict[str, syntax.Command] = dict() for dirpath, _, filenames in os.walk(old_idl_dir): for old_filename in filenames: if not old_filename.endswith('.idl'): continue old_idl_file_path = os.path.join(dirpath, old_filename) with open(old_idl_file_path) as old_file: old_idl_file = parser.parse( old_file, old_idl_file_path, CompilerImportResolver(import_directories + [old_idl_dir])) if old_idl_file.errors: old_idl_file.errors.dump_errors() raise ValueError(f"Cannot parse {old_idl_file_path}") for old_cmd in old_idl_file.spec.symbols.commands: if old_cmd.api_version == "": continue if old_cmd.api_version != "1": # We're not ready to handle future API versions yet. ctxt.add_command_invalid_api_version_error( old_cmd.command_name, old_cmd.api_version, old_idl_file_path) continue if old_cmd.command_name in old_commands: ctxt.add_duplicate_command_name_error( old_cmd.command_name, old_idl_dir, old_idl_file_path) continue old_commands[old_cmd.command_name] = old_cmd if old_cmd.command_name not in new_commands: # Can't remove a command from V1 ctxt.add_command_removed_error(old_cmd.command_name, old_idl_file_path) continue new_cmd = new_commands[old_cmd.command_name] old_reply = old_idl_file.spec.symbols.get_struct( old_cmd.reply_type) new_reply = new_command_replies[new_cmd.command_name] for old_field in old_reply.fields or []: if old_field.unstable: continue new_field_exists = False for new_field in new_reply.fields or []: if new_field.name == old_field.name: if new_field.unstable: ctxt.add_new_reply_field_unstable_error( new_cmd.command_name, new_field.name, old_idl_file_path) if new_field.optional and not old_field.optional: ctxt.add_new_reply_field_optional_error( new_cmd.command_name, new_field.name, old_idl_file_path) new_field_exists = True break if not new_field_exists: ctxt.add_new_reply_field_missing_error( new_cmd.command_name, old_field.name, old_idl_file_path) ctxt.errors.dump_errors() return ctxt.errors