My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add DAG tests: dedup, different universes, universe correctness

Tests verify:
- Same package with same deps across solutions is deduplicated
- Same package with different deps gets separate nodes
- Universe is computed from transitive deps

Also fix: remove broken trans_cache that keyed by min_binding
(different solutions with same min package shared wrong transitive
deps).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+84 -12
+1 -12
day11/build/dag.ml
··· 2 2 3 3 let build_dag cache ~base_hash solutions = 4 4 let memo : (string * string, build) Hashtbl.t = Hashtbl.create 256 in 5 - let trans_cache : (OpamPackage.t, Day11_graph.Graph.solution) Hashtbl.t = 6 - Hashtbl.create 16 in 7 - let get_trans solution = 8 - let target = OpamPackage.Map.min_binding solution |> fst in 9 - match Hashtbl.find_opt trans_cache target with 10 - | Some t -> t 11 - | None -> 12 - let t = Day11_graph.Graph.transitive_deps solution in 13 - Hashtbl.replace trans_cache target t; 14 - t 15 - in 16 5 let rec get_node solution trans pkg = 17 6 let pkg_deps = 18 7 match OpamPackage.Map.find_opt pkg trans with ··· 44 33 node 45 34 in 46 35 List.iter (fun (_target, solution) -> 47 - let trans = get_trans solution in 36 + let trans = Day11_graph.Graph.transitive_deps solution in 48 37 OpamPackage.Map.iter (fun pkg _deps -> 49 38 ignore (get_node solution trans pkg) 50 39 ) solution
+83
day11/build/test/test_build.ml
··· 107 107 OpamPackage.to_string n.pkg) nodes in 108 108 Alcotest.(check (list string)) "topo order" [ "c.1"; "b.1" ] names 109 109 110 + let test_dag_dedup_across_solutions () = 111 + (* c.1 appears in both solutions with the same deps — should be 112 + deduplicated to one node *) 113 + let find_opam p = 114 + make_find_opam {|opam-version: "2.0"|} p in 115 + let sol1 = 116 + OpamPackage.Map.empty 117 + |> OpamPackage.Map.add (pkg "c.1") OpamPackage.Set.empty 118 + |> OpamPackage.Map.add (pkg "a.1") 119 + (OpamPackage.Set.singleton (pkg "c.1")) 120 + in 121 + let sol2 = 122 + OpamPackage.Map.empty 123 + |> OpamPackage.Map.add (pkg "c.1") OpamPackage.Set.empty 124 + |> OpamPackage.Map.add (pkg "b.1") 125 + (OpamPackage.Set.singleton (pkg "c.1")) 126 + in 127 + let cache = Hash_cache.create ~find_opam () in 128 + let nodes = Dag.build_dag cache ~base_hash:"base" 129 + [ (pkg "a.1", sol1); (pkg "b.1", sol2) ] in 130 + (* c.1, a.1, b.1 — c.1 appears once despite being in 2 solutions *) 131 + Alcotest.(check int) "3 nodes (c deduplicated)" 3 (List.length nodes); 132 + let c_nodes = List.filter (fun (n : Day11_layer.Layer_type.build) -> 133 + OpamPackage.to_string n.pkg = "c.1") nodes in 134 + Alcotest.(check int) "1 c node" 1 (List.length c_nodes) 135 + 136 + let test_dag_different_universes () = 137 + (* c.1 in two solutions with different deps — NOT deduplicated *) 138 + let find_opam p = 139 + make_find_opam {|opam-version: "2.0"|} p in 140 + let sol1 = 141 + OpamPackage.Map.empty 142 + |> OpamPackage.Map.add (pkg "d.1") OpamPackage.Set.empty 143 + |> OpamPackage.Map.add (pkg "c.1") 144 + (OpamPackage.Set.singleton (pkg "d.1")) 145 + in 146 + let sol2 = 147 + OpamPackage.Map.empty 148 + |> OpamPackage.Map.add (pkg "e.1") OpamPackage.Set.empty 149 + |> OpamPackage.Map.add (pkg "c.1") 150 + (OpamPackage.Set.singleton (pkg "e.1")) 151 + in 152 + let cache = Hash_cache.create ~find_opam () in 153 + let nodes = Dag.build_dag cache ~base_hash:"base" 154 + [ (pkg "c.1", sol1); (pkg "c.1", sol2) ] in 155 + let c_nodes = List.filter (fun (n : Day11_layer.Layer_type.build) -> 156 + OpamPackage.to_string n.pkg = "c.1") nodes in 157 + (* c.1 with dep d.1 vs c.1 with dep e.1 — different universes *) 158 + Alcotest.(check int) "2 c nodes (different universes)" 2 (List.length c_nodes); 159 + (* Universes should differ *) 160 + let u1 = (List.nth c_nodes 0).universe in 161 + let u2 = (List.nth c_nodes 1).universe in 162 + Alcotest.(check bool) "different universes" 163 + false (Day11_graph.Universe.equal u1 u2) 164 + 165 + let test_dag_universe_set () = 166 + (* Check universe is computed from transitive deps *) 167 + let find_opam p = 168 + make_find_opam {|opam-version: "2.0"|} p in 169 + let solution = 170 + OpamPackage.Map.empty 171 + |> OpamPackage.Map.add (pkg "d.1") OpamPackage.Set.empty 172 + |> OpamPackage.Map.add (pkg "c.1") 173 + (OpamPackage.Set.singleton (pkg "d.1")) 174 + |> OpamPackage.Map.add (pkg "b.1") 175 + (OpamPackage.Set.singleton (pkg "c.1")) 176 + in 177 + let cache = Hash_cache.create ~find_opam () in 178 + let nodes = Dag.build_dag cache ~base_hash:"base" 179 + [ (pkg "b.1", solution) ] in 180 + let b_node = List.find (fun (n : Day11_layer.Layer_type.build) -> 181 + OpamPackage.to_string n.pkg = "b.1") nodes in 182 + (* b.1's universe should include c.1 and d.1 (transitive deps) *) 183 + let expected = Day11_graph.Universe.of_deps 184 + (OpamPackage.Set.of_list [ pkg "c.1"; pkg "d.1" ]) in 185 + Alcotest.(check bool) "universe includes transitive deps" 186 + true (Day11_graph.Universe.equal b_node.universe expected) 187 + 110 188 (* ── Test registration ───────────────────────────────────────────── *) 111 189 112 190 let () = ··· 147 225 [ 148 226 Alcotest.test_case "empty" `Quick test_dag_empty; 149 227 Alcotest.test_case "single solution" `Quick test_dag_single_solution; 228 + Alcotest.test_case "dedup across solutions" `Quick 229 + test_dag_dedup_across_solutions; 230 + Alcotest.test_case "different universes" `Quick 231 + test_dag_different_universes; 232 + Alcotest.test_case "universe set" `Quick test_dag_universe_set; 150 233 ] ); 151 234 ]