diff --git a/upgrade_analysis/compare.py b/upgrade_analysis/compare.py index 044e4a959ec..8760cc09834 100644 --- a/upgrade_analysis/compare.py +++ b/upgrade_analysis/compare.py @@ -540,7 +540,7 @@ def match(match_fields, match_type="direct"): if entry["noupdate_switched"]: content += " (noupdate switched)" reprs[module_map(entry["module"])].append(content) - return reprs + return reprs, moved_records, renamed_records, modified_records def compare_model_sets(old_records, new_records): diff --git a/upgrade_analysis/models/upgrade_analysis.py b/upgrade_analysis/models/upgrade_analysis.py index 8eefad21ba7..f60ca97afe6 100644 --- a/upgrade_analysis/models/upgrade_analysis.py +++ b/upgrade_analysis/models/upgrade_analysis.py @@ -164,7 +164,9 @@ def analyze(self): {field: record[field] for field in flds} for record in RemoteRecord.read(remote_xml_record_ids, flds) ] - res_xml = compare.compare_xml_sets(remote_xml_records, local_xml_records) + res_xml, moved_xml_records, renamed_xml_records, modified_xml_records = ( + compare.compare_xml_sets(remote_xml_records, local_xml_records) + ) # Retrieve model representations and compare flds = [ @@ -270,7 +272,9 @@ def analyze(self): ) noupdate_modules = [] try: - noupdate_modules = self.generate_noupdate_changes() + noupdate_modules = self.generate_noupdate_changes( + moved_xml_records, renamed_xml_records, modified_xml_records + ) except Exception as e: _logger.exception(f"Error generating noupdate changes: {e}") general_log += "ERROR: error when generating noupdate changes: {e}\n" @@ -486,7 +490,9 @@ def _parse_files(self, xml_files, module_name): return records_update, records_noupdate - def generate_noupdate_changes(self): + def generate_noupdate_changes( + self, moved_xml_records, renamed_xml_records, modified_xml_records + ): """Communicate with the remote server to fetch all xml data records per module, and generate a diff in XML format that can be imported from the module's migration script using openupgrade.load_data() @@ -498,6 +504,14 @@ def generate_noupdate_changes(self): local_modules = local_record_obj.list_modules() all_remote_modules = remote_record_obj.list_modules() changed_modules = [] + # {new_module: {name: previous_module}} + renamed_xmlids = {} + for renamed_xml_record in renamed_xml_records: + if renamed_xml_record.get("old") or not renamed_xml_record.get("new"): + continue + renamed_xmlids.setdefault(renamed_xml_record["module"], {}).update( + {renamed_xml_record["suffix"]: renamed_xml_record["renamed"]} + ) for local_module in local_modules: remote_files = [] remote_modules = [] @@ -515,6 +529,35 @@ def generate_noupdate_changes(self): ) remote_update.update(add_remote_update) remote_noupdate.update(add_remote_noupdate) + if any( + renamed_from_module == remote_module + for renamed_from_module in renamed_xmlids.get( + local_module, {} + ).values() + ): + # if xmlids have been renamed (moved) to the current module, query + # their definition from the module that contained it previously + remote_modules.append(remote_module) + renamed_from_module_files = remote_record_obj.get_xml_records( + remote_module + ) + renamed_from_module_update, renamed_from_module_noupdate = ( + self._parse_files(renamed_from_module_files, remote_module) + ) + remote_update.update( + { + name: xml + for name, xml in renamed_from_module_update.items() + if renamed_xmlids[local_module].get(name) == remote_module + } + ) + remote_noupdate.update( + { + name: xml + for name, xml in renamed_from_module_noupdate.items() + if renamed_xmlids[local_module].get(name) == remote_module + } + ) if not remote_modules: continue local_files = local_record_obj.get_xml_records(local_module) diff --git a/upgrade_analysis/tests/test_module.py b/upgrade_analysis/tests/test_module.py index 65d93b8d924..f7cfd34cea3 100644 --- a/upgrade_analysis/tests/test_module.py +++ b/upgrade_analysis/tests/test_module.py @@ -1,4 +1,5 @@ from copy import deepcopy +from unittest.mock import patch from lxml import etree @@ -152,3 +153,142 @@ def test_xml_comparison(self): ) self.assertIn('', diff) self.assertIn('', diff) + + def test_analyze(self): + """ + Test a full analysis run. + For the time being, only xmlid related functionality is tested + """ + analysis = self.env["upgrade.analysis"].create( + { + "config_id": self.env["upgrade.comparison.config"] + .create( + { + "database": self.env.cr.dbname, + } + ) + .id, + } + ) + upgrade_analysis_version = ( + self.env["ir.module.module"] + .search([("name", "=", "upgrade_analysis")]) + .latest_version + ) + + self.env["upgrade.record"].create( + { + "name": "upgrade_analysis.test_noupdate_xmlid", + "mode": "create", + "type": "xmlid", + "module": "upgrade_analysis", + "model": "upgrade.comparison.config", + "noupdate": True, + } + ) + + class RemoteUpgradeRecord: + _records = { + 1: { + "name": "other_module.test_noupdate_xmlid", + "mode": "create", + "type": "xmlid", + "module": "other_module", + "prefix": "other_module", + "model": "upgrade.comparison.config", + "noupdate": True, + "suffix": "test_noupdate_xmlid", + }, + } + + def search(self, domain): + if domain == [("type", "=", "xmlid")]: + return [ + _id + for _id, vals in self._records.items() + if vals["type"] == "xmlid" + ] + return [] + + def read(self, ids, fields): # pylint: disable=method-required-super + return [ + {field: record.get(field) for field in fields} + for _id, record in self._records.items() + if _id in ids + ] + + def field_dump(self): + return [] + + def list_modules(self): + return set(record["module"] for record in self._records.values()) + + def get_xml_records(self, module): + if module == "other_module": + return [ + """ + + + Noupdate xmlid from other_module + some_server + + + """ + ] + + def local_get_xml_records(module): + if module == "upgrade_analysis": + return [ + """ + + + Noupdate xmlid from upgrade_analysis + some_server + + + """ + ] + + written_files = {} + + def write_file(module_name, version, content, filename="upgrade_analysis.txt"): + written_files[f"{module_name}-{version}-{filename}"] = content + + with ( + patch.object(analysis.config_id.__class__, "get_connection"), + patch.object( + analysis.__class__, "_get_remote_model" + ) as patched_get_remote_model, + patch.object(analysis.__class__, "_write_file") as patched_write_file, + patch.object( + self.env["upgrade.record"].__class__, "get_xml_records" + ) as patched_get_xml_records, + ): + patched_get_remote_model.side_effect = lambda *args: RemoteUpgradeRecord() + patched_get_xml_records.side_effect = local_get_xml_records + patched_write_file.side_effect = write_file + analysis.analyze() + + expected_noupdate_content = """ + + + Noupdate xmlid from upgrade_analysis + + +""" + self.assertEqual( + written_files[ + f"upgrade_analysis-{upgrade_analysis_version}-noupdate_changes.xml" + ], + expected_noupdate_content, + )