diff --git a/client/goal.c b/client/goal.c index 87199b6c..a61acf4f 100644 --- a/client/goal.c +++ b/client/goal.c @@ -391,6 +391,7 @@ TDNFSolv( BAIL_ON_TDNF_ERROR(dwError); } + transaction_order(pTrans, 0); if (pTdnf->pConf->ppszProtectedPkgs) { /* catch protected obsoleted packages, and double check for removals */ dwError = TDNFSolvCheckProtectPkgsInTrans(pTdnf, pTrans, pTdnf->pSack->pPool); diff --git a/pytests/repo/tdnf-test-prein-base.spec b/pytests/repo/tdnf-test-prein-base.spec new file mode 100644 index 00000000..173464c2 --- /dev/null +++ b/pytests/repo/tdnf-test-prein-base.spec @@ -0,0 +1,21 @@ +Summary: Test package - prein ordering base dependency +Name: tdnf-test-prein-base +Version: 1.0 +Release: 1 +Vendor: VMware, Inc. +Distribution: Photon +License: VMware +Url: http://www.vmware.com +Group: Applications/tdnftest + +%description +Base package required by tdnf-test-prein-dep-a to give it more graph +weight than tdnf-test-prein-dep-b for transaction ordering tests. + +%prep +%build +%install +%files +%changelog +* Fri Aug 21 2026 tdnf team 1.0-1 +- Initial package diff --git a/pytests/repo/tdnf-test-prein-consumer.spec b/pytests/repo/tdnf-test-prein-consumer.spec new file mode 100644 index 00000000..e21448d6 --- /dev/null +++ b/pytests/repo/tdnf-test-prein-consumer.spec @@ -0,0 +1,27 @@ +Summary: Test package - prein ordering regression, consumer +Name: tdnf-test-prein-consumer +Version: 1.0 +Release: 1 +Vendor: VMware, Inc. +Distribution: Photon +License: VMware +Url: http://www.vmware.com +Group: Applications/tdnftest +Requires(pre): tdnf-test-prein-cap + +%description +Consumer of tdnf-test-prein-cap via Requires(pre). The %pre scriptlet +ends with a never-taken if branch to verify POSIX sh exit-code semantics. + +%pre +if [ "%{name}" = "not-this-name" ]; then + echo "unreachable" +fi + +%prep +%build +%install +%files +%changelog +* Fri Aug 21 2026 tdnf team 1.0-1 +- Initial package diff --git a/pytests/repo/tdnf-test-prein-dep-a.spec b/pytests/repo/tdnf-test-prein-dep-a.spec new file mode 100644 index 00000000..b9315634 --- /dev/null +++ b/pytests/repo/tdnf-test-prein-dep-a.spec @@ -0,0 +1,24 @@ +Summary: Test package - prein ordering regression, provider A +Name: tdnf-test-prein-dep-a +Version: 1.0 +Release: 1 +Vendor: VMware, Inc. +Distribution: Photon +License: VMware +Url: http://www.vmware.com +Group: Applications/tdnftest +Provides: tdnf-test-prein-cap +Requires: tdnf-test-prein-base + +%description +Provider A of tdnf-test-prein-cap. Requires tdnf-test-prein-base to gain +enough graph weight that transaction_order() consistently places it right +before the consumer (and after dep-b) in the install sequence. + +%prep +%build +%install +%files +%changelog +* Fri Aug 21 2026 tdnf team 1.0-1 +- Initial package diff --git a/pytests/repo/tdnf-test-prein-dep-b.spec b/pytests/repo/tdnf-test-prein-dep-b.spec new file mode 100644 index 00000000..96dbf148 --- /dev/null +++ b/pytests/repo/tdnf-test-prein-dep-b.spec @@ -0,0 +1,22 @@ +Summary: Test package - prein ordering regression, provider B +Name: tdnf-test-prein-dep-b +Version: 1.0 +Release: 1 +Vendor: VMware, Inc. +Distribution: Photon +License: VMware +Url: http://www.vmware.com +Group: Applications/tdnftest +Provides: tdnf-test-prein-cap + +%description +Provider B of tdnf-test-prein-cap. No dependencies, giving it less graph +weight than dep-a so transaction_order() consistently places it earlier. + +%prep +%build +%install +%files +%changelog +* Fri Aug 21 2026 tdnf team 1.0-1 +- Initial package diff --git a/pytests/tests/test_transaction_order.py b/pytests/tests/test_transaction_order.py new file mode 100644 index 00000000..2d12d50a --- /dev/null +++ b/pytests/tests/test_transaction_order.py @@ -0,0 +1,60 @@ +# +# Copyright (C) 2026 VMware, Inc. All Rights Reserved. +# +# Licensed under the GNU General Public License v2 (the "License"); +# you may not use this file except in compliance with the License. The terms +# of the License are located in the COPYING file of this distribution. +# + +import pytest + +PREIN_BASE = "tdnf-test-prein-base" +PREIN_DEP_A = "tdnf-test-prein-dep-a" +PREIN_DEP_B = "tdnf-test-prein-dep-b" +PREIN_CONSUMER = "tdnf-test-prein-consumer" +PREIN_PKGS = [PREIN_BASE, PREIN_DEP_A, PREIN_DEP_B, PREIN_CONSUMER] + + +@pytest.fixture(scope="function", autouse=True) +def cleanup_prein_packages(utils): + utils.run(["tdnf", "remove", "-y"] + PREIN_PKGS) + yield + utils.run(["tdnf", "remove", "-y"] + PREIN_PKGS) + + +def _install_and_get_order(utils, pkgs): + ret = utils.run(["tdnf", "install", "-y", "--nogpgcheck"] + pkgs) + assert ret["retval"] == 0, "tdnf install failed (retval={}):\n{}".format( + ret["retval"], "\n".join(ret["stdout"] + ret["stderr"]) + ) + return [line for line in ret["stdout"] if line.startswith("Installing/Updating:")] + + +def _remove_prein_packages(utils): + ret = utils.run(["tdnf", "remove", "-y"] + PREIN_PKGS) + assert ret["retval"] == 0, "tdnf remove failed (retval={}):\n{}".format( + ret["retval"], "\n".join(ret["stdout"] + ret["stderr"]) + ) + + +# Regression test for transaction_order() fix in client/goal.c: +# without the fix, solver_create_transaction() leaves pTrans->steps in +# CLI-argument order; rpmtsOrder() then picks whichever Requires(pre) +# provider appears closest to the consumer as the ordering anchor, making +# the install sequence depend on CLI argument order. +def test_prein_install_order_is_deterministic(utils): + order_a_first = _install_and_get_order( + utils, [PREIN_DEP_A, PREIN_DEP_B, PREIN_CONSUMER] + ) + + _remove_prein_packages(utils) + + order_b_first = _install_and_get_order( + utils, [PREIN_DEP_B, PREIN_DEP_A, PREIN_CONSUMER] + ) + + assert order_a_first == order_b_first, ( + "Install order differed with different CLI argument order -- " + "transaction_order() may not be called after solver_create_transaction().\n" + "dep-a first: {}\ndep-b first: {}".format(order_a_first, order_b_first) + )