@@ -224,39 +224,93 @@ func TestExplain_SelfImportDoesNotAddDepth(t *testing.T) {
224224 }
225225}
226226
227- // TestExplain_OversizedClusterNotDeep: a large autoload cluster (a ring of
228- // OversizedClusterModules+3 modules) must count as ONE logical layer, not its full
229- // size, so it does not masquerade as a deep dependency chain. With a short tail
230- // below it the whole graph's real layering stays under minDepth and nothing is
231- // reported — matching the cycles explainer already covering the cluster.
232- func TestExplain_OversizedClusterNotDeep (t * testing.T ) {
227+ // oversizedRing builds one big SCC of OversizedClusterModules+3 modules named
228+ // prefix+NN, returning the module list, the dep map, and the ring members.
229+ func oversizedRing (prefix string ) ([]string , map [string ][]string , []string ) {
233230 n := common .OversizedClusterModules + 3
234- mods := make ([]string , 0 , n + 1 )
235- deps := map [string ][]string {}
236231 ring := make ([]string , n )
232+ deps := map [string ][]string {}
237233 for i := 0 ; i < n ; i ++ {
238- ring [i ] = fmt .Sprintf ("c/m%02d" , i )
239- mods = append (mods , ring [i ])
234+ ring [i ] = fmt .Sprintf ("%s%02d" , prefix , i )
240235 }
241236 for i := 0 ; i < n ; i ++ {
242- deps [ring [i ]] = []string {ring [(i + 1 )% n ]} // one big SCC
237+ deps [ring [i ]] = []string {ring [(i + 1 )% n ]}
243238 }
239+ return append ([]string (nil ), ring ... ), deps , ring
240+ }
241+
242+ // TestExplain_OversizedClusterNotDeep: a large autoload cluster with a short tail
243+ // hanging off it produces no deep-chain finding — the cluster is a weight-0 sink, so
244+ // it neither counts toward depth nor lets a chain thread through it.
245+ func TestExplain_OversizedClusterNotDeep (t * testing.T ) {
246+ mods , deps , ring := oversizedRing ("c/m" )
244247 // A short 2-module tail hanging off the cluster: c/m00 -> t/t0 -> t/t1.
245248 mods = append (mods , "t/t0" , "t/t1" )
246- deps ["c/m00" ] = append (deps ["c/m00" ], "t/t0" )
249+ deps [ring [ 0 ]] = append (deps [ring [ 0 ] ], "t/t0" )
247250 deps ["t/t0" ] = []string {"t/t1" }
248251
249252 insights , err := New ().Explain (context .Background (), makeStore (mods , deps ))
250253 if err != nil {
251254 t .Fatalf ("Explain: %v" , err )
252255 }
253- // Cluster weighted as 1 + tail(2) = depth 3 < minDepth(5) -> no findings, and
254- // crucially not a "depth ~N" report of the whole cluster.
255256 if len (insights ) != 0 {
256257 t .Fatalf ("oversized cluster should not produce a deep-chain finding, got %d: %v" , len (insights ), titles (insights ))
257258 }
258259}
259260
261+ // TestExplain_ClusterIsDepthSink: genuine layering ABOVE a cluster still fires (the
262+ // cluster contributes nothing but the real layers count), while shallow layering
263+ // into a cluster does not — proving depth is earned only outside the cluster.
264+ func TestExplain_ClusterIsDepthSink (t * testing.T ) {
265+ mods , deps , ring := oversizedRing ("k/m" )
266+ // 5 genuine layers a0->a1->a2->a3->a4, then a4 imports into the cluster.
267+ for i := 0 ; i < 5 ; i ++ {
268+ mods = append (mods , fmt .Sprintf ("a/l%d" , i ))
269+ }
270+ for i := 0 ; i < 4 ; i ++ {
271+ deps [fmt .Sprintf ("a/l%d" , i )] = []string {fmt .Sprintf ("a/l%d" , i + 1 )}
272+ }
273+ deps ["a/l4" ] = []string {ring [0 ]}
274+ // 3 shallow layers b0->b1->b2, then b2 imports into the cluster.
275+ for i := 0 ; i < 3 ; i ++ {
276+ mods = append (mods , fmt .Sprintf ("b/l%d" , i ))
277+ }
278+ deps ["b/l0" ] = []string {"b/l1" }
279+ deps ["b/l1" ] = []string {"b/l2" }
280+ deps ["b/l2" ] = []string {ring [0 ]}
281+
282+ insights , err := New ().Explain (context .Background (), makeStore (mods , deps ))
283+ if err != nil {
284+ t .Fatalf ("Explain: %v" , err )
285+ }
286+ byMod := map [string ]facts.Insight {}
287+ for _ , in := range insights {
288+ for _ , ev := range in .Evidence {
289+ // first evidence fact is the chain head
290+ byMod [ev .Fact ] = in
291+ break
292+ }
293+ }
294+ // a/l0 has 5 real layers into the cluster -> reported at depth 5, and the chain
295+ // must NOT include any cluster member.
296+ aIn , ok := byMod ["a/l0" ]
297+ if ! ok {
298+ t .Fatalf ("5-layer chain above the cluster should be reported; got %v" , titles (insights ))
299+ }
300+ if ! strings .Contains (aIn .Title , "depth 5" ) {
301+ t .Errorf ("a/l0 should be depth 5 (cluster contributes 0), got %q" , aIn .Title )
302+ }
303+ for _ , ev := range aIn .Evidence {
304+ if strings .HasPrefix (ev .Fact , "k/m" ) {
305+ t .Errorf ("cluster member %q must not appear in the chain" , ev .Fact )
306+ }
307+ }
308+ // b/l0 has only 3 layers into the cluster -> below minDepth -> not reported.
309+ if _ , ok := byMod ["b/l0" ]; ok {
310+ t .Errorf ("3-layer chain into the cluster should be suppressed; got %v" , titles (insights ))
311+ }
312+ }
313+
260314func TestExplain_EmptyGraph (t * testing.T ) {
261315 insights , err := New ().Explain (context .Background (), facts .NewStore ())
262316 if err != nil {
0 commit comments