caddyfile: fix importGraph self-loop and stale-edge bugs (#7971)

- willCycle now reports a cycle when from == to, so addEdge rejects
  self-loops (a self-importing file was previously accepted).
- removeNode now drops the removed node's outgoing edges and any
  incoming edges pointing at it, keeping the adjacency map consistent.

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
This commit is contained in:
Mohammed Al Sahaf authored and GitHub committed 2026-09-03 07:48:22 +10:00
1 parent af29f9ea91
commit 2cb7ebca45
2 files changed
+52

No files matched your search

+7
View File
@@ -44,6 +44,10 @@ func (i *importGraph) addNodes(names []string) {
func (i *importGraph) removeNode(name string) {
delete(i.nodes, name)
delete(i.edges, name)
for k, targets := range i.edges {
i.edges[k] = slices.DeleteFunc(targets, func(t string) bool { return t == name })
}
}
func (i *importGraph) removeNodes(names []string) {
@@ -96,6 +100,9 @@ func (i *importGraph) areConnected(from, to string) bool {
}
func (i *importGraph) willCycle(from, to string) bool {
if from == to {
return true
}
collector := make(map[string]bool)
var visit func(string)
+45
View File
@@ -0,0 +1,45 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import "testing"
func TestImportGraphSelfLoop(t *testing.T) {
g := &importGraph{}
g.addNode("a")
if err := g.addEdge("a", "a"); err == nil {
t.Error("expected error for self-loop cycle a -> a")
}
}
func TestImportGraphRemoveNodeCleansEdges(t *testing.T) {
g := &importGraph{}
g.addNodes([]string{"a", "b", "c"})
_ = g.addEdge("a", "b")
_ = g.addEdge("b", "c")
g.removeNode("b")
if g.exists("b") {
t.Error("node 'b' should not exist after removeNode")
}
if targets, ok := g.edges["b"]; ok && len(targets) > 0 {
t.Errorf("outgoing edges from removed node 'b' should be cleared, got %v", targets)
}
if g.areConnected("a", "b") {
t.Error("incoming edge 'a' -> 'b' should be removed when 'b' is removed")
}
}