Skip to content

Commit

Permalink
🦤 domainset: clean up and add builder tests
Browse files Browse the repository at this point in the history
  • Loading branch information
database64128 committed Aug 23, 2024
1 parent 3567c62 commit 635191a
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 43 deletions.
7 changes: 4 additions & 3 deletions domainset/domainset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package domainset

import (
"bytes"
"strings"
"testing"
)

Expand Down Expand Up @@ -96,11 +97,11 @@ func TestDomainSetFromGob(t *testing.T) {
}

func TestBuilderWriteText(t *testing.T) {
var buf bytes.Buffer
if err := testDomainSetBuilder.WriteText(&buf); err != nil {
var sb strings.Builder
if err := testDomainSetBuilder.WriteText(&sb); err != nil {
t.Fatal(err)
}
dsb, err := BuilderFromText(buf.String())
dsb, err := BuilderFromText(sb.String())
if err != nil {
t.Fatal(err)
}
Expand Down
85 changes: 53 additions & 32 deletions domainset/matcher_domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,36 @@ func NewDomainLinearMatcher(capacity int) MatcherBuilder {
return &dlm
}

// Match implements the Matcher Match method.
// DomainLinearMatcherFromSeq creates a [DomainLinearMatcher] from a sequence of domain rules.
func DomainLinearMatcherFromSeq(domainCount int, domainSeq iter.Seq[string]) DomainLinearMatcher {
dlm := make(DomainLinearMatcher, 0, domainCount)
return slices.AppendSeq(dlm, domainSeq)
}

// Match implements [Matcher.Match].
func (dlm DomainLinearMatcher) Match(domain string) bool {
return slices.Contains(dlm, domain)
}

// Insert implements the MatcherBuilder Insert method.
// Insert implements [MatcherBuilder.Insert].
func (dlmp *DomainLinearMatcher) Insert(rule string) {
*dlmp = append(*dlmp, rule)
}

// Rules implements the MatcherBuilder Rules method.
// Rules implements [MatcherBuilder.Rules].
func (dlm DomainLinearMatcher) Rules() (int, iter.Seq[string]) {
return len(dlm), slices.Values(dlm)
}

// MatcherCount implements the MatcherBuilder MatcherCount method.
// MatcherCount implements [MatcherBuilder.MatcherCount].
func (dlm DomainLinearMatcher) MatcherCount() int {
if len(dlm) == 0 {
return 0
}
return 1
}

// AppendTo implements the MatcherBuilder AppendTo method.
// AppendTo implements [MatcherBuilder.AppendTo].
func (dlmp *DomainLinearMatcher) AppendTo(matchers []Matcher) ([]Matcher, error) {
dlm := *dlmp

Expand All @@ -62,56 +68,71 @@ func (dlmp *DomainLinearMatcher) AppendTo(matchers []Matcher) ([]Matcher, error)
}

// DomainBinarySearchMatcher matches domain rules using binary search.
type DomainBinarySearchMatcher []string
type DomainBinarySearchMatcher struct {
domains []string
}

// NewDomainBinarySearchMatcher creates a [DomainBinarySearchMatcher] with the specified initial capacity.
func NewDomainBinarySearchMatcher(capacity int) MatcherBuilder {
dbsm := make(DomainBinarySearchMatcher, 0, capacity)
return &dbsm
domains := make([]string, 0, capacity)
return &DomainBinarySearchMatcher{domains}
}

// DomainBinarySearchMatcherFromSlice creates a [DomainBinarySearchMatcher] from a slice of domain rules.
func DomainBinarySearchMatcherFromSlice(domains []string) DomainBinarySearchMatcher {
slices.Sort(domains)
return domains
dbsm := DomainBinarySearchMatcher{
domains: make([]string, 0, len(domains)),
}
for _, domain := range domains {
dbsm.Insert(domain)
}
return dbsm
}

// Match implements the Matcher Match method.
// DomainBinarySearchMatcherFromSeq creates a [DomainBinarySearchMatcher] from a sequence of domain rules.
func DomainBinarySearchMatcherFromSeq(domainCount int, domainSeq iter.Seq[string]) DomainBinarySearchMatcher {
dbsm := DomainBinarySearchMatcher{
domains: make([]string, 0, domainCount),
}
for domain := range domainSeq {
dbsm.Insert(domain)
}
return dbsm
}

// Match implements [Matcher.Match].
func (dbsm DomainBinarySearchMatcher) Match(domain string) bool {
_, found := slices.BinarySearch(dbsm, domain)
_, found := slices.BinarySearch(dbsm.domains, domain)
return found
}

// Insert implements the MatcherBuilder Insert method.
func (dbsmp *DomainBinarySearchMatcher) Insert(rule string) {
index, found := slices.BinarySearch(*dbsmp, rule)
// Insert implements [MatcherBuilder.Insert].
func (dbsm *DomainBinarySearchMatcher) Insert(rule string) {
index, found := slices.BinarySearch(dbsm.domains, rule)
if !found {
*dbsmp = slices.Insert(*dbsmp, index, rule)
dbsm.domains = slices.Insert(dbsm.domains, index, rule)
}
}

// Rules implements the MatcherBuilder Rules method.
// Rules implements [MatcherBuilder.Rules].
func (dbsm DomainBinarySearchMatcher) Rules() (int, iter.Seq[string]) {
return len(dbsm), slices.Values(dbsm)
return len(dbsm.domains), slices.Values(dbsm.domains)
}

// MatcherCount implements the MatcherBuilder MatcherCount method.
// MatcherCount implements [MatcherBuilder.MatcherCount].
func (dbsm DomainBinarySearchMatcher) MatcherCount() int {
if len(dbsm) == 0 {
if len(dbsm.domains) == 0 {
return 0
}
return 1
}

// AppendTo implements the MatcherBuilder AppendTo method.
func (dbsmp *DomainBinarySearchMatcher) AppendTo(matchers []Matcher) ([]Matcher, error) {
dbsm := *dbsmp

if len(dbsm) == 0 {
// AppendTo implements [MatcherBuilder.AppendTo].
func (dbsm *DomainBinarySearchMatcher) AppendTo(matchers []Matcher) ([]Matcher, error) {
if len(dbsm.domains) == 0 {
return matchers, nil
}

return append(matchers, dbsmp), nil
return append(matchers, dbsm), nil
}

// DomainMapMatcher matches domain rules using a map.
Expand Down Expand Up @@ -143,31 +164,31 @@ func DomainMapMatcherFromSeq(domainCount int, domainSeq iter.Seq[string]) Domain
return dmm
}

// Match implements the Matcher Match method.
// Match implements [Matcher.Match].
func (dmm DomainMapMatcher) Match(domain string) bool {
_, ok := dmm[domain]
return ok
}

// Insert implements the MatcherBuilder Insert method.
// Insert implements [MatcherBuilder.Insert].
func (dmm DomainMapMatcher) Insert(rule string) {
dmm[rule] = struct{}{}
}

// Rules implements the MatcherBuilder Rules method.
// Rules implements [MatcherBuilder.Rules].
func (dmm DomainMapMatcher) Rules() (int, iter.Seq[string]) {
return len(dmm), maps.Keys(dmm)
}

// MatcherCount implements the MatcherBuilder MatcherCount method.
// MatcherCount implements [MatcherBuilder.MatcherCount].
func (dmm DomainMapMatcher) MatcherCount() int {
if len(dmm) == 0 {
return 0
}
return 1
}

// AppendTo implements the MatcherBuilder AppendTo method.
// AppendTo implements [MatcherBuilder.AppendTo].
func (dmmp *DomainMapMatcher) AppendTo(matchers []Matcher) ([]Matcher, error) {
dmm := *dmmp

Expand Down
27 changes: 24 additions & 3 deletions domainset/matcher_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,38 @@ func testDomainMatcher(t *testing.T, m Matcher) {

func TestDomainLinearMatcher(t *testing.T) {
dlm := DomainLinearMatcher(testDomains[:])
testDomainMatcher(t, &dlm)

t.Run("Match", func(t *testing.T) {
testDomainMatcher(t, &dlm)
})

t.Run("Rules", func(t *testing.T) {
testMatcherBuilderRules(t, &dlm, testDomains[:])
})
}

func TestDomainBinarySearchMatcher(t *testing.T) {
dbsm := DomainBinarySearchMatcherFromSlice(testDomains[:])
testDomainMatcher(t, &dbsm)

t.Run("Match", func(t *testing.T) {
testDomainMatcher(t, &dbsm)
})

t.Run("Rules", func(t *testing.T) {
testMatcherBuilderRules(t, &dbsm, testDomains[:])
})
}

func TestDomainMapMatcher(t *testing.T) {
dmm := DomainMapMatcherFromSlice(testDomains[:])
testDomainMatcher(t, &dmm)

t.Run("Match", func(t *testing.T) {
testDomainMatcher(t, &dmm)
})

t.Run("Rules", func(t *testing.T) {
testMatcherBuilderRules(t, &dmm, testDomains[:])
})
}

func benchmarkDomainMatcher(b *testing.B, count int, name string, m Matcher) {
Expand Down
6 changes: 5 additions & 1 deletion domainset/matcher_suffix.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ func (slmp *SuffixLinearMatcher) AppendTo(matchers []Matcher) ([]Matcher, error)
}

if len(slm) > MaxLinearSuffixes {
return append(matchers, DomainSuffixTrieFromSlice(slm)), nil
dst := DomainSuffixTrieFromSlice(slm)
return dst.AppendTo(matchers)
}

return append(matchers, slmp), nil
Expand Down Expand Up @@ -136,6 +137,9 @@ func (smmp *SuffixMapMatcher) AppendTo(matchers []Matcher) ([]Matcher, error) {
return matchers, nil
}

// With 16 suffix rules, a linear matcher is still mostly faster than a map matcher.
// But a linear matcher will migrate to a trie matcher when the number of rules exceeds 4.
// So we only migrate to a linear matcher when the number of rules does not exceed 4.
if len(smm) <= MaxLinearSuffixes {
slm := SuffixLinearMatcher(maphelper.Keys(smm))
return slm.AppendTo(matchers)
Expand Down
27 changes: 24 additions & 3 deletions domainset/matcher_suffix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,38 @@ func testSuffixMatcher(t *testing.T, m Matcher) {

func TestSuffixLinearMatcher(t *testing.T) {
slm := SuffixLinearMatcher(testSuffixes[:])
testSuffixMatcher(t, &slm)

t.Run("Match", func(t *testing.T) {
testSuffixMatcher(t, &slm)
})

t.Run("Rules", func(t *testing.T) {
testMatcherBuilderRules(t, &slm, testSuffixes[:])
})
}

func TestSuffixMapMatcher(t *testing.T) {
smm := SuffixMapMatcherFromSlice(testSuffixes[:])
testSuffixMatcher(t, &smm)

t.Run("Match", func(t *testing.T) {
testSuffixMatcher(t, &smm)
})

t.Run("Rules", func(t *testing.T) {
testMatcherBuilderRules(t, &smm, testSuffixes[:])
})
}

func TestSuffixTrieMatcher(t *testing.T) {
stm := DomainSuffixTrieFromSlice(testSuffixes[:])
testSuffixMatcher(t, stm)

t.Run("Match", func(t *testing.T) {
testSuffixMatcher(t, &stm)
})

t.Run("Rules", func(t *testing.T) {
testMatcherBuilderRules(t, &stm, testSuffixes[:])
})
}

func benchmarkSuffixMatcher(b *testing.B, count int, name string, m Matcher) {
Expand Down
20 changes: 19 additions & 1 deletion domainset/matcher_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
package domainset

import "testing"
import (
"slices"
"testing"
)

func testMatcher(t *testing.T, m Matcher, domain string, expectedResult bool) {
t.Helper()
if m.Match(domain) != expectedResult {
t.Errorf("%s should return %v", domain, expectedResult)
}
}

func testMatcherBuilderRules(t *testing.T, mb MatcherBuilder, expectedRules []string) {
t.Helper()

ruleCount, ruleSeq := mb.Rules()
rules := slices.AppendSeq(make([]string, 0, ruleCount), ruleSeq)
slices.Sort(rules)

sortedExpectedRules := slices.Clone(expectedRules)
slices.Sort(sortedExpectedRules)

if !slices.Equal(rules, sortedExpectedRules) {
t.Errorf("Expected rules %v, got %v", sortedExpectedRules, rules)
}
}

0 comments on commit 635191a

Please sign in to comment.