diff --git a/module_build_service/utils/reuse.py b/module_build_service/utils/reuse.py index b51cf266..7f37da52 100644 --- a/module_build_service/utils/reuse.py +++ b/module_build_service/utils/reuse.py @@ -94,6 +94,10 @@ def get_reusable_module(db_session, module): previous_module_build = None base_mmds = get_base_module_mmds(db_session, mmd)["ready"] + # Sort the base_mmds based on the stream version, higher version first. + base_mmds.sort( + key=lambda mmd: models.ModuleBuild.get_stream_version(mmd.get_stream_name(), False), + reverse=True) for base_mmd in base_mmds: mbs_xmd = mmd.get_xmd()["mbs"] if base_mmd.get_module_name() not in mbs_xmd["buildrequires"]: diff --git a/tests/test_utils/test_utils.py b/tests/test_utils/test_utils.py index 63cd02f2..2f5166bc 100644 --- a/tests/test_utils/test_utils.py +++ b/tests/test_utils/test_utils.py @@ -26,6 +26,7 @@ from shutil import copyfile, rmtree from datetime import datetime from werkzeug.datastructures import FileStorage from mock import patch +from sqlalchemy.orm.session import make_transient from module_build_service.utils.general import load_mmd_file, mmd_to_str import module_build_service.utils import module_build_service.scm @@ -1614,3 +1615,66 @@ class TestUtilsModuleReuse: assert build_module.reused_module assert reusable_module.id == build_module.reused_module_id assert reusable_module.id == reused_module.id + + @patch( + "module_build_service.config.Config.allow_only_compatible_base_modules", + new_callable=mock.PropertyMock, return_value=False + ) + def test_get_reusable_module_use_latest_build(self, cfg, db_session): + """ + Test that the `get_reusable_module` tries to reuse the latest module in case when + multiple modules can be reused. + """ + # Set "fedora" virtual stream to platform:f28. + platform_f28 = db_session.query(models.ModuleBuild).filter_by(name="platform").one() + mmd = platform_f28.mmd() + xmd = mmd.get_xmd() + xmd["mbs"]["virtual_streams"] = ["fedora"] + mmd.set_xmd(xmd) + platform_f28.modulemd = mmd_to_str(mmd) + platform_f28.update_virtual_streams(db_session, ["fedora"]) + + # Create platform:f29 with "fedora" virtual stream. + mmd = load_mmd(read_staged_data("platform")) + mmd = mmd.copy("platform", "f29") + xmd = mmd.get_xmd() + xmd["mbs"]["virtual_streams"] = ["fedora"] + mmd.set_xmd(xmd) + platform_f29 = module_build_service.utils.import_mmd(db_session, mmd)[0] + + # Create another copy of `testmodule:master` which should be reused, because its + # stream version will be higher than the previous one. Also set its buildrequires + # to platform:f29. + latest_module = db_session.query(models.ModuleBuild).filter_by( + name="testmodule").filter_by(state=models.BUILD_STATES["ready"]).one() + # This is used to clone the ModuleBuild SQLAlchemy object without recreating it from + # scratch. + db_session.expunge(latest_module) + make_transient(latest_module) + + # Change the platform:f28 buildrequirement to platform:f29 and recompute the build_context. + mmd = latest_module.mmd() + xmd = mmd.get_xmd() + xmd["mbs"]["buildrequires"]["platform"]["stream"] = "f29" + mmd.set_xmd(xmd) + latest_module.modulemd = mmd_to_str(mmd) + latest_module.build_context = module_build_service.models.ModuleBuild.contexts_from_mmd( + latest_module.modulemd + ).build_context + latest_module.buildrequires = [platform_f29] + + # Set the `id` to None, so new one is generated by SQLAlchemy. + latest_module.id = None + db_session.add(latest_module) + db_session.commit() + + module = db_session.query(models.ModuleBuild)\ + .filter_by(name="testmodule")\ + .filter_by(state=models.BUILD_STATES["build"])\ + .one() + db_session.commit() + + reusable_module = module_build_service.utils.get_reusable_module( + db_session, module) + + assert reusable_module.id == latest_module.id