mirror of
https://github.com/logos-co/logos-package-manager.git
synced 2026-08-27 18:21:08 +00:00
test(deps): pin what a diamond does to the flat dependency list
flatten() is the only projection resolveFlatDependencies -- and therefore
basecamp's load gate -- ever reads. It dedupes by name with first-wins, and
BFS reaches the depth-1 edge first, so when a package is named BOTH directly
(bare, as every manifest in the fleet does today) and by a dependency that
constrains it, the unconstrained edge wins and the mismatch is dropped.
Red on b7e2280, measured on the real module over real IPC first:
resolveFlatDependencies appA true -> lib "installed" (masked)
resolveDependencies appA true -> lib "version_mismatch" (tree is right)
Three cases: the diamond, the same graph with the root's entries swapped
(declaration order must not decide it), and a satisfied diamond as the
control that a duplicate is not by itself a mismatch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b7e2280738
commit
991d092d9d
@@ -815,6 +815,96 @@ TEST_F(DependencyResolutionTest, VersionMismatchSurvivesFlatten) {
|
||||
EXPECT_EQ(*it->requiredVersion, ">=3.0.0");
|
||||
}
|
||||
|
||||
TEST_F(DependencyResolutionTest, DeeperMismatchIsNotMaskedByAShallowerBareEdge) {
|
||||
// The DIAMOND, which the chain tests above cannot reach: "lib" is named
|
||||
// twice — once by the root with no range, once by "helper" with one it
|
||||
// does not satisfy. Both facts are true; only one of them survives the
|
||||
// flat list, and flatten() dedupes by name with FIRST-WINS, while BFS
|
||||
// guarantees the depth-1 edge is always reached first. So the
|
||||
// unconstrained edge wins and the mismatch never reaches the flat list —
|
||||
// which is the ONLY projection resolveFlatDependencies and basecamp's
|
||||
// load gate ever read. The tree keeps it; the wire does not.
|
||||
//
|
||||
// Every package in the fleet declares bare names today, so "the root also
|
||||
// depends on it directly, without a range" is the ordinary shape, not an
|
||||
// exotic one.
|
||||
writeManifest(modulesDir, "app", "core", {"lib", "helper"});
|
||||
writeManifestRawDeps(modulesDir, "helper",
|
||||
json::array({ json{{"name", "lib"}, {"version", "^2.0.0"}} }));
|
||||
writeManifest(modulesDir, "lib", "core", {}, "1.0.0");
|
||||
|
||||
PackageManagerLib pm;
|
||||
pm.setEmbeddedModulesDirectory(modulesDir.string());
|
||||
|
||||
auto tree = pm.resolveDependencies("app");
|
||||
ASSERT_TRUE(tree);
|
||||
|
||||
// The resolver itself is right — the mismatch IS on the tree.
|
||||
ASSERT_EQ(tree->children.size(), 2u);
|
||||
const auto& helper = tree->children[1];
|
||||
ASSERT_EQ(helper.name, "helper");
|
||||
ASSERT_EQ(helper.children.size(), 1u);
|
||||
ASSERT_EQ(helper.children[0].status, DependencyStatus::VersionMismatch);
|
||||
|
||||
// ...and the flat projection must not lose it. One row per package still,
|
||||
// but the row has to report the constraint that is NOT satisfied: a
|
||||
// package satisfies its dependants only if it satisfies ALL of them.
|
||||
auto flat = tree->flatten();
|
||||
EXPECT_EQ(flat.size(), 2u);
|
||||
auto it = std::find_if(flat.begin(), flat.end(),
|
||||
[](const DependencyTreeNode& n) { return n.name == "lib"; });
|
||||
ASSERT_NE(it, flat.end());
|
||||
EXPECT_EQ(it->status, DependencyStatus::VersionMismatch);
|
||||
ASSERT_TRUE(it->requiredVersion.has_value());
|
||||
EXPECT_EQ(*it->requiredVersion, "^2.0.0");
|
||||
// The installed version stays on the row: "requires ^2.0.0, found 1.0.0".
|
||||
EXPECT_EQ(it->version, "1.0.0");
|
||||
}
|
||||
|
||||
TEST_F(DependencyResolutionTest, DeeperMismatchSurvivesRegardlessOfDeclarationOrder) {
|
||||
// Same graph, the root's two entries swapped. BFS visits by DEPTH, so the
|
||||
// depth-1 "lib" is reached first either way — declaration order must not
|
||||
// decide whether a user is told about a broken dependency.
|
||||
writeManifest(modulesDir, "app", "core", {"helper", "lib"});
|
||||
writeManifestRawDeps(modulesDir, "helper",
|
||||
json::array({ json{{"name", "lib"}, {"version", "^2.0.0"}} }));
|
||||
writeManifest(modulesDir, "lib", "core", {}, "1.0.0");
|
||||
|
||||
PackageManagerLib pm;
|
||||
pm.setEmbeddedModulesDirectory(modulesDir.string());
|
||||
|
||||
auto tree = pm.resolveDependencies("app");
|
||||
ASSERT_TRUE(tree);
|
||||
auto flat = tree->flatten();
|
||||
auto it = std::find_if(flat.begin(), flat.end(),
|
||||
[](const DependencyTreeNode& n) { return n.name == "lib"; });
|
||||
ASSERT_NE(it, flat.end());
|
||||
EXPECT_EQ(it->status, DependencyStatus::VersionMismatch);
|
||||
}
|
||||
|
||||
TEST_F(DependencyResolutionTest, ASatisfiedDiamondStaysInstalledAndDeduped) {
|
||||
// The control that keeps the fix from degenerating into "any duplicate is
|
||||
// a mismatch". Two edges, both satisfied: one row, still installed, and
|
||||
// the first edge's constraint is the one reported.
|
||||
writeManifest(modulesDir, "app", "core", {"lib", "helper"});
|
||||
writeManifestRawDeps(modulesDir, "helper",
|
||||
json::array({ json{{"name", "lib"}, {"version", "^1.0.0"}} }));
|
||||
writeManifest(modulesDir, "lib", "core", {}, "1.0.0");
|
||||
|
||||
PackageManagerLib pm;
|
||||
pm.setEmbeddedModulesDirectory(modulesDir.string());
|
||||
|
||||
auto tree = pm.resolveDependencies("app");
|
||||
ASSERT_TRUE(tree);
|
||||
auto flat = tree->flatten();
|
||||
EXPECT_EQ(flat.size(), 2u);
|
||||
auto it = std::find_if(flat.begin(), flat.end(),
|
||||
[](const DependencyTreeNode& n) { return n.name == "lib"; });
|
||||
ASSERT_NE(it, flat.end());
|
||||
EXPECT_EQ(it->status, DependencyStatus::Installed);
|
||||
EXPECT_FALSE(it->requiredVersion.has_value()); // the depth-1 bare edge
|
||||
}
|
||||
|
||||
TEST_F(DependencyResolutionTest, RootCarriesNoConstraint) {
|
||||
// Nothing points AT the root, so it has no edge and no range — and it must
|
||||
// never be judged against one.
|
||||
|
||||
Reference in New Issue
Block a user