Skip to content

Commit

Permalink
Generate code using Go generics (#20)
Browse files Browse the repository at this point in the history
* Generate code using Go generics

The code mutation is a bit less pretty but it still works well. Tests
are kept in this case as they are easy to mutate to check that
everything is working.

However, this requires bumping the minimal Go version to 1.18. For
clarity, generated code will be in a separate commit.

* Add generated code with generics

* Bump golangci/golangci-lint-action version for Go 1.18 compatibility
  • Loading branch information
vincentbernat committed Aug 10, 2022
1 parent f3af281 commit ae6f737
Show file tree
Hide file tree
Showing 21 changed files with 3,867 additions and 16 deletions.
11 changes: 7 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
go: [ '1.16', '1.17', '1.18' ]
go: [ '1.18' ]

steps:
- uses: actions/checkout@v2
Expand All @@ -33,10 +33,13 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Run linters
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v3
with:
version: v1.45.0
only-new-issues: true

coverage:
Expand All @@ -47,7 +50,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18
- name: Calc coverage
run: go test -covermode=count -coverprofile=coverage.out
- name: Convert coverage.out to coverage.lcov
Expand Down
24 changes: 22 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ endif
.PHONY: all
all: codegen code

.PHONY: ipv6code generics codegen codegen-%
codegen: ipv6code generics $(addprefix codegen-,$(GENERATED_TYPES))

ipv6code:
cp template/tree_v4.go template/tree_v6_generated.go
$(SED) -i -e 's/Template file./Code generated by automation. DO NOT EDIT/' template/tree_v6_generated.go
Expand All @@ -18,13 +21,30 @@ ipv6code:
$(SED) -i -e 's/treeNodeV4/treeNodeV6/g' template/tree_v6_generated.go
$(SED) -i -e 's/IPv4Address/IPv6Address/g' template/tree_v6_generated.go

codegen: ipv6code $(addprefix codegen-,$(GENERATED_TYPES))
generics: codegen-generics
# generics -> T, except in tests
( cd generics_tree && $(SED) -i -E -e 's/\bgenerics\b/string/g' *_test.go)
( cd generics_tree && $(SED) -i -E -e 's/\bgenerics\b/T/g' *.go)
# Each defined type should be parametrized with T
( cd generics_tree && $(SED) -nE 's/^type (\w+).*/\1/p' *.go \
| grep -vFx treeIteratorNext \
| while read T; do \
$(SED) -i -E -e 's/\b'$$T'\b/\0[T]/g' *.go ; \
$(SED) -i -E -e 's/\b('$$T')\[T\]/\1[string]/g' *_test.go ; \
done )
# Fix type definition
( cd generics_tree && $(SED) -i -E -e 's/^(type \w+)\[T\]/\1[T any]/' *.go)
# NewTreeVX function should be parametrized
( cd generics_tree && $(SED) -i -E -e 's/^(func NewTreeV.)/\1[T any]/' *.go)
( cd generics_tree && $(SED) -i -E -e 's/(NewTreeV.)\(/\1[string](/g' *_test.go)
# No need to cast interfaces
( cd generics_tree && $(SED) -i -E -e 's/\.\(string\)//g' *_test.go)

codegen-%:
@echo "** generating $* tree"
mkdir -p "./${*}_tree"
cp -pa template/*.go "./${*}_tree"
rm -f ./${*}_tree/*_test.go
test "${*}" = generics || rm -f ./${*}_tree/*_test.go
rm -f ./${*}_tree/types.go
( cd "${*}_tree" && $(SED) -i "s/Template file./Code generated by automation. DO NOT EDIT/g" *.go )
( cd "${*}_tree" && $(SED) -i "s/GeneratedType/${*}/g" *.go )
Expand Down
17 changes: 17 additions & 0 deletions generics_tree/tree_node_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package generics_tree

// Clears the bit at pos in n.
func clearBit32(n uint32, pos uint32) uint32 {
pos = 32 - pos
mask := uint32(^(1 << pos))
n &= mask
return n
}

// Clears the bit at pos in n.
func clearBit64(n uint64, pos uint64) uint64 {
pos = 64 - pos
mask := uint64(^(1 << pos))
n &= mask
return n
}
51 changes: 51 additions & 0 deletions generics_tree/tree_node_v4.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions generics_tree/tree_node_v4_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package generics_tree

import (
"testing"

"github.com/kentik/patricia"
"github.com/stretchr/testify/assert"
)

func TestV4MatchCount(t *testing.T) {
node := &treeNodeV4[string]{
prefix: uint32(0xFFFFFFFF),
prefixLength: 32,
}

// test moving the non-matching bit forward, making sure we never return more than the length of the prefix
addr := uint32(0xFFFFFFFF)
for i := 1; i < 32; i++ {
address := patricia.IPv4Address{
Address: clearBit32(addr, uint32(i)),
Length: 32,
}
assert.Equal(t, uint(i-1), node.MatchCount(address))
}

// test moving the non-matching bit forward, with max of node address's 16 length
node.prefixLength = 16
for i := 1; i < 32; i++ {
expected := uint(i - 1)
if expected > 16 {
expected = 16
}

address := patricia.IPv4Address{
Address: clearBit32(addr, uint32(i)),
Length: 32,
}
assert.Equal(t, expected, node.MatchCount(address))
}

// test moving the non-matching bit forward, with max of comparison address's 16 length
node.prefixLength = 32
for i := 1; i < 32; i++ {
expected := uint(i - 1)
if expected > 16 {
expected = 16
}

address := patricia.IPv4Address{
Address: clearBit32(addr, uint32(i)),
Length: 16,
}
assert.Equal(t, expected, node.MatchCount(address))
}
}
51 changes: 51 additions & 0 deletions generics_tree/tree_node_v6.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions generics_tree/tree_node_v6_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package generics_tree

import (
"testing"

"github.com/kentik/patricia"
"github.com/stretchr/testify/assert"
)

func TestV6MatchCount(t *testing.T) {
node := &treeNodeV6[string]{
prefixLeft: uint64(0xFFFFFFFFFFFFFFFF),
prefixRight: uint64(0xFFFFFFFFFFFFFFFF),
prefixLength: 84,
}

// test moving the non-matching bit forward, making sure we never return more than the length of the prefix
left := uint64(0xFFFFFFFFFFFFFFFF)
for i := 1; i < 128; i++ {
addressLeft := left
if i <= 64 {
addressLeft = clearBit64(addressLeft, uint64(i))
}

expected := i - 1
if i > 64 {
expected = 84
}

address := patricia.IPv6Address{
Left: addressLeft,
Right: uint64(0xFFFFFFFFFFFFFFFF),
Length: 128,
}
assert.Equal(t, uint(expected), node.MatchCount(address))
}

// test moving the non-matching bit forward, making sure we never return more than the length of the address
node.prefixLength = 128
left = uint64(0xFFFFFFFFFFFFFFFF)
for i := 1; i < 128; i++ {
addressLeft := left
if i <= 64 {
addressLeft = clearBit64(addressLeft, uint64(i))
}

expected := i - 1
if i > 64 {
expected = 84
}
address := patricia.IPv6Address{
Left: addressLeft,
Right: uint64(0xFFFFFFFFFFFFFFFF),
Length: 84,
}
assert.Equal(t, uint(expected), node.MatchCount(address))
}
}

0 comments on commit ae6f737

Please sign in to comment.