From cc2e1b80d40a1badb18866478a8a5227b8295ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E9=82=A3=E5=87=A0?= <2078634206@qq.com> Date: Wed, 30 Sep 2026 13:40:01 +0800 Subject: [PATCH] fix(cli): make search result ordering a strict weak ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The duplicated sort comparators in CLIPrinter::printSearchResult and JSONPrinter::printSearchResult returned false whenever either version failed to parse, making record equivalence non-transitive when mixed with parsable versions and violating std::sort's precondition (undefined behavior). Order parsable versions first descending as before and unparsable ones last by raw string, in both printers; add a unit test covering mixed parsable/unparsable versions through both printers. Signed-off-by: 陈那几 <2078634206@qq.com> --- .../linglong/src/linglong/cli/cli_printer.cpp | 7 ++- .../src/linglong/cli/json_printer.cpp | 7 ++- .../src/linglong/cli/cli_printer_test.cpp | 45 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/libs/linglong/src/linglong/cli/cli_printer.cpp b/libs/linglong/src/linglong/cli/cli_printer.cpp index c4d209879..14d5f8a1c 100644 --- a/libs/linglong/src/linglong/cli/cli_printer.cpp +++ b/libs/linglong/src/linglong/cli/cli_printer.cpp @@ -121,12 +121,15 @@ void CLIPrinter::printSearchResult( return lhs.packageInfoV2Module < rhs.packageInfoV2Module; auto lhsVer = package::Version::parse(lhs.version.c_str()); + auto rhsVer = package::Version::parse(rhs.version.c_str()); + if (!lhsVer && !rhsVer) { + return lhs.version > rhs.version; + } if (!lhsVer) { return false; } - auto rhsVer = package::Version::parse(rhs.version.c_str()); if (!rhsVer) { - return false; + return true; } return *lhsVer > *rhsVer; diff --git a/libs/linglong/src/linglong/cli/json_printer.cpp b/libs/linglong/src/linglong/cli/json_printer.cpp index dd19ee7e0..ce9d54908 100644 --- a/libs/linglong/src/linglong/cli/json_printer.cpp +++ b/libs/linglong/src/linglong/cli/json_printer.cpp @@ -51,12 +51,15 @@ void JSONPrinter::printSearchResult( return lhs.packageInfoV2Module < rhs.packageInfoV2Module; auto lhsVer = package::Version::parse(lhs.version.c_str()); + auto rhsVer = package::Version::parse(rhs.version.c_str()); + if (!lhsVer && !rhsVer) { + return lhs.version > rhs.version; + } if (!lhsVer) { return false; } - auto rhsVer = package::Version::parse(rhs.version.c_str()); if (!rhsVer) { - return false; + return true; } return *lhsVer > *rhsVer; diff --git a/libs/linglong/tests/ll-tests/src/linglong/cli/cli_printer_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/cli/cli_printer_test.cpp index 2e01174f2..e17b36038 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/cli/cli_printer_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/cli/cli_printer_test.cpp @@ -128,6 +128,51 @@ TEST(CLIPrinter, PrintSearchResultSorts) EXPECT_LT(out.find("2.0.0"), out.find("1.0.0")); } +TEST(CLIPrinter, PrintSearchResultSortsInvalidVersionsLast) +{ + // Only versions which are empty or consist of dots alone fail + // package::Version::parse; any other string is accepted by the fallback + // parser, so use "" and "..." to exercise the unparsable path. + const std::map> searchResult{ + { "repo1", + { makePackage("org.deepin.sort", "ver1", "1.0.0", "stable", "binary", "sorted"), + makePackage("org.deepin.sort", "bad1", "", "stable", "binary", "sorted"), + makePackage("org.deepin.sort", "ver3", "3.0.0", "stable", "binary", "sorted"), + makePackage("org.deepin.sort", "ver2", "2.0.0", "stable", "binary", "sorted"), + makePackage("org.deepin.sort", "bad2", "...", "stable", "binary", "sorted") } } + }; + + std::string cliOut; + { + CaptureStdout capture; + linglong::cli::CLIPrinter printer; + printer.printSearchResult(searchResult); + cliOut = capture.str(); + } + + std::string jsonOut; + { + CaptureStdout capture; + linglong::cli::JSONPrinter printer; + printer.printSearchResult(searchResult); + jsonOut = capture.str(); + } + + // records with a parsable version come first, highest to lowest; the + // unparsable ones come last, ordered by raw version string ("..." > "") + auto assertSearchOrder = [](const std::string &out) { + const std::vector expectedOrder{ "ver3", "ver2", "ver1", "bad2", "bad1" }; + for (const auto &marker : expectedOrder) { + EXPECT_THAT(out, ::testing::HasSubstr(marker)); + } + for (std::size_t i = 1; i < expectedOrder.size(); ++i) { + EXPECT_LT(out.find(expectedOrder[i - 1]), out.find(expectedOrder[i])); + } + }; + assertSearchOrder(cliOut); + assertSearchOrder(jsonOut); +} + TEST(CLIPrinter, PrintPruneResultEmpty) { CaptureStdout capture;