blob: 7a815bad2fa413e1fe98be697d1a743db42b0814 [file] [log] [blame]
Lorenz Brunc7b036b2023-06-01 12:23:57 +02001package kmod
2
3import (
4 "fmt"
5 "regexp"
6 "strings"
7 "testing"
8 "unicode"
9
10 "github.com/google/go-cmp/cmp"
11 "google.golang.org/protobuf/testing/protocmp"
12
13 kmodpb "source.monogon.dev/metropolis/pkg/kmod/spec"
14)
15
16func TestParsePattern(t *testing.T) {
17 cases := []struct {
18 name string
19 pattern string
20 expectedNodes []*kmodpb.RadixNode
21 }{
22 {"Empty", "", nil},
23 {"SingleLiteral", "asdf", []*kmodpb.RadixNode{{Type: kmodpb.RadixNode_LITERAL, Literal: "asdf"}}},
24 {"SingleWildcard", "as*df", []*kmodpb.RadixNode{
25 {Type: kmodpb.RadixNode_LITERAL, Literal: "as"},
26 {Type: kmodpb.RadixNode_WILDCARD},
27 {Type: kmodpb.RadixNode_LITERAL, Literal: "df"},
28 }},
29 {"EscapedWildcard", "a\\*", []*kmodpb.RadixNode{{Type: kmodpb.RadixNode_LITERAL, Literal: "a*"}}},
30 {"SingleRange", "[y-z]", []*kmodpb.RadixNode{{Type: kmodpb.RadixNode_BYTE_RANGE, StartByte: 121, EndByte: 122}}},
31 {"SingleWildcardChar", "a?c", []*kmodpb.RadixNode{
32 {Type: kmodpb.RadixNode_LITERAL, Literal: "a"},
33 {Type: kmodpb.RadixNode_SINGLE_WILDCARD},
34 {Type: kmodpb.RadixNode_LITERAL, Literal: "c"},
35 }},
36 }
37 for _, c := range cases {
38 t.Run(c.name, func(t *testing.T) {
39 out, err := parsePattern(c.pattern)
40 if err != nil {
41 t.Fatal(err)
42 }
43 diff := cmp.Diff(c.expectedNodes, out, protocmp.Transform())
44 if diff != "" {
45 t.Error(diff)
46 }
47 })
48 }
49}
50
51func TestLookupComplex(t *testing.T) {
52 root := &kmodpb.RadixNode{
53 Type: kmodpb.RadixNode_LITERAL,
54 }
55 if err := AddPattern(root, "usb:v0B95p1790d*dc*dsc*dp*icFFiscFFip00in*", 2); err != nil {
56 t.Error(err)
57 }
58 if err := AddPattern(root, "usb:v0B95p178Ad*dc*dsc*dp*icFFiscFFip00in*", 3); err != nil {
59 t.Error(err)
60 }
61 if err := AddPattern(root, "acpi*:PNP0C14:*", 10); err != nil {
62 t.Error(err)
63 }
64 matches := make(map[uint32]bool)
65 lookupModulesRec(root, "acpi:PNP0C14:asdf", matches)
66 if !matches[10] {
67 t.Error("value should match pattern 10")
68 }
69}
70
71func isASCII(s string) bool {
72 for i := 0; i < len(s); i++ {
73 if s[i] > unicode.MaxASCII {
74 return false
75 }
76 }
77 return true
78}
79
80func FuzzRadixImpl(f *testing.F) {
81 f.Add("acpi*:PNP0C14:*\x00usb:v0B95p1790d*dc*dsc*dp*icFFiscFFip00in*", "acpi:PNP0C14:asdf\x00usb:v0B95p1790d0dc0dsc0dp0icFFiscFFip00in")
82 f.Fuzz(func(t *testing.T, a string, b string) {
83 patternsRaw := strings.Split(a, "\x00")
84 values := strings.Split(b, "\x00")
85 var patternsRegexp []regexp.Regexp
86 root := &kmodpb.RadixNode{
87 Type: kmodpb.RadixNode_LITERAL,
88 }
89 for i, p := range patternsRaw {
90 if !isASCII(p) {
91 // Ignore non-ASCII patterns, there are tons of edge cases with them
92 return
93 }
94 pp, err := parsePattern(p)
95 if err != nil {
96 // Bad pattern
97 return
98 }
99 if err := AddPattern(root, p, uint32(i)); err != nil {
100 t.Fatal(err)
101 }
102 var regexb strings.Builder
103 regexb.WriteString("(?s)^")
104 for _, part := range pp {
105 switch part.Type {
106 case kmodpb.RadixNode_LITERAL:
107 regexb.WriteString(regexp.QuoteMeta(part.Literal))
108 case kmodpb.RadixNode_SINGLE_WILDCARD:
109 regexb.WriteString(".")
110 case kmodpb.RadixNode_WILDCARD:
111 regexb.WriteString(".*")
112 case kmodpb.RadixNode_BYTE_RANGE:
113 regexb.WriteString(fmt.Sprintf("[%s-%s]", regexp.QuoteMeta(string([]rune{rune(part.StartByte)})), regexp.QuoteMeta(string([]rune{rune(part.EndByte)}))))
114 default:
115 t.Errorf("Unknown node type %v", part.Type)
116 }
117 }
118 regexb.WriteString("$")
119 patternsRegexp = append(patternsRegexp, *regexp.MustCompile(regexb.String()))
120 }
121 for _, v := range values {
122 if !isASCII(v) {
123 // Ignore non-ASCII values
124 return
125 }
126 if len(v) > 64 {
127 // Ignore big values as they are not realistic and cause the
128 // wildcard matches to be very expensive.
129 return
130 }
131 radixMatchesSet := make(map[uint32]bool)
132 lookupModulesRec(root, v, radixMatchesSet)
133 for i, re := range patternsRegexp {
134 if re.MatchString(v) {
135 if !radixMatchesSet[uint32(i)] {
136 t.Errorf("Pattern %q is expected to match %q but didn't", patternsRaw[i], v)
137 }
138 } else {
139 if radixMatchesSet[uint32(i)] {
140 t.Errorf("Pattern %q is not expected to match %q but did", patternsRaw[i], v)
141 }
142 }
143 }
144 }
145 })
146}