Skip to content

Commit 8bfc2cb

Browse files
committed
Add test for handling registration errors
Ensure that graph errors are handled as expected. Signed-off-by: Derek McGowan <derek@mcg.dev>
1 parent 4f0df13 commit 8bfc2cb

2 files changed

Lines changed: 154 additions & 1 deletion

File tree

plugin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ var (
3939
ErrPluginNotFound = errors.New("plugin: not found")
4040
// ErrPluginMultipleInstances is used when a plugin is expected a single instance but has multiple
4141
ErrPluginMultipleInstances = errors.New("plugin: multiple instances")
42+
// ErrPluginCircularDependency is used when the graph detect a circular plugin dependency
43+
ErrPluginCircularDependency = errors.New("plugin: dependency loop detected")
4244

4345
// ErrInvalidRequires will be thrown if the requirements for a plugin are
4446
// defined in an invalid manner.

plugin_test.go

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const (
4444
)
4545

4646
func testRegistry() Registry {
47-
4847
var register Registry
4948
return register.Register(&Registration{
5049
Type: TaskMonitorPlugin,
@@ -517,6 +516,158 @@ func testPlugin(t Type, id string, i interface{}, err error) *Plugin {
517516
}
518517
}
519518

519+
func TestRequiresAll(t *testing.T) {
520+
var register Registry
521+
register = register.Register(&Registration{
522+
Type: InternalPlugin,
523+
ID: "system",
524+
}).Register(&Registration{
525+
Type: ServicePlugin,
526+
ID: "introspection",
527+
Requires: []Type{
528+
"*",
529+
},
530+
}).Register(&Registration{
531+
Type: ServicePlugin,
532+
ID: "task",
533+
Requires: []Type{
534+
InternalPlugin,
535+
},
536+
}).Register(&Registration{
537+
Type: ServicePlugin,
538+
ID: "version",
539+
})
540+
ordered := register.Graph(mockPluginFilter)
541+
expectedURI := []string{
542+
"io.containerd.internal.v1.system",
543+
"io.containerd.service.v1.task",
544+
"io.containerd.service.v1.version",
545+
"io.containerd.service.v1.introspection",
546+
}
547+
cmpOrdered(t, ordered, expectedURI)
548+
}
549+
550+
func TestRegisterErrors(t *testing.T) {
551+
552+
for _, tc := range []struct {
553+
name string
554+
expected error
555+
register func(Registry) Registry
556+
}{
557+
{
558+
name: "duplicate",
559+
expected: ErrIDRegistered,
560+
register: func(r Registry) Registry {
561+
return r.Register(&Registration{
562+
Type: TaskMonitorPlugin,
563+
ID: "cgroups",
564+
}).Register(&Registration{
565+
Type: TaskMonitorPlugin,
566+
ID: "cgroups",
567+
})
568+
},
569+
},
570+
{
571+
name: "circular",
572+
expected: ErrPluginCircularDependency,
573+
register: func(r Registry) Registry {
574+
// Circular dependencies should not loop but order will be based on registration order
575+
return r.Register(&Registration{
576+
Type: InternalPlugin,
577+
ID: "p1",
578+
Requires: []Type{
579+
RuntimePlugin,
580+
},
581+
}).Register(&Registration{
582+
Type: RuntimePlugin,
583+
ID: "p2",
584+
Requires: []Type{
585+
InternalPlugin,
586+
},
587+
}).Register(&Registration{
588+
Type: InternalPlugin,
589+
ID: "p3",
590+
})
591+
},
592+
},
593+
{
594+
name: "self",
595+
expected: ErrInvalidRequires,
596+
register: func(r Registry) Registry {
597+
// Circular dependencies should not loop but order will be based on registration order
598+
return r.Register(&Registration{
599+
Type: InternalPlugin,
600+
ID: "p1",
601+
Requires: []Type{
602+
InternalPlugin,
603+
},
604+
})
605+
},
606+
},
607+
{
608+
name: "no-type",
609+
expected: ErrNoType,
610+
register: func(r Registry) Registry {
611+
// Circular dependencies should not loop but order will be based on registration order
612+
return r.Register(&Registration{
613+
Type: "",
614+
ID: "p1",
615+
})
616+
},
617+
},
618+
{
619+
name: "no-ID",
620+
expected: ErrNoPluginID,
621+
register: func(r Registry) Registry {
622+
// Circular dependencies should not loop but order will be based on registration order
623+
return r.Register(&Registration{
624+
Type: InternalPlugin,
625+
ID: "",
626+
})
627+
},
628+
},
629+
{
630+
name: "bad-requires-all",
631+
expected: ErrInvalidRequires,
632+
register: func(r Registry) Registry {
633+
// Circular dependencies should not loop but order will be based on registration order
634+
return r.Register(&Registration{
635+
Type: InternalPlugin,
636+
ID: "p1",
637+
Requires: []Type{
638+
"*",
639+
InternalPlugin,
640+
},
641+
})
642+
},
643+
},
644+
} {
645+
t.Run(tc.name, func(t *testing.T) {
646+
var (
647+
r Registry
648+
panicAny any
649+
)
650+
func() {
651+
defer func() {
652+
panicAny = recover()
653+
}()
654+
655+
tc.register(r).Graph(mockPluginFilter)
656+
}()
657+
if panicAny == nil {
658+
t.Fatalf("expected panic with error %v", tc.expected)
659+
}
660+
err, ok := panicAny.(error)
661+
if !ok {
662+
t.Fatalf("expected panic: %v, expected error %v", panicAny, tc.expected)
663+
}
664+
if !errors.Is(err, tc.expected) {
665+
t.Fatalf("unexpected error type: %v, expected %v", panicAny, tc.expected)
666+
}
667+
})
668+
}
669+
}
670+
520671
func BenchmarkGraph(b *testing.B) {
521672
register := testRegistry()
522673
b.ResetTimer()

0 commit comments

Comments
 (0)