mirror of
https://github.com/tailscale/tailscale.git
synced 2026-10-23 03:37:19 +09:00
fuzz: refactor fuzzing to new layout for oss-fuzz support (#21069)
Prior to this change there were two problems with our fuzzing for oss-fuzz: 1. There was an issue if the fuzzing spanned two files (mingled with the testing). 2. The fuzzing needs to be part of the implementation package (no _test packages). This change fixes that by moving all package fuzzing into a common `fuzz_test.go` within the package. Updates https://github.com/tailscale/corp/issues/46608 Change-Id: I0b95edcd0df946f723eea32f575c679214c0b202 Signed-off-by: Mike Jensen <mikej@tailscale.com>
This commit is contained in:
1 parent
316b22b775
commit
0188b38666
19 files changed
+546
-420
No files matched your search
@@ -339,64 +339,6 @@ func BenchmarkNameFromQuery(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
// Reproduces https://github.com/tailscale/tailscale/issues/2533
|
||||
// Fixed by https://github.com/tailscale/tailscale/commit/f414a9cc01f3264912513d07c0244ff4f3e4ba54
|
||||
//
|
||||
// NOTE: fuzz tests act like unit tests when run without `-fuzz`
|
||||
func FuzzClampEDNSSize(f *testing.F) {
|
||||
// Empty DNS packet
|
||||
f.Add([]byte{
|
||||
// query id
|
||||
0x12, 0x34,
|
||||
// flags: standard query, recurse
|
||||
0x01, 0x20,
|
||||
// num questions
|
||||
0x00, 0x00,
|
||||
// num answers
|
||||
0x00, 0x00,
|
||||
// num authority RRs
|
||||
0x00, 0x00,
|
||||
// num additional RRs
|
||||
0x00, 0x00,
|
||||
})
|
||||
|
||||
// Empty OPT
|
||||
f.Add([]byte{
|
||||
// header
|
||||
0xaf, 0x66, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
// query
|
||||
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f,
|
||||
0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||
// OPT
|
||||
0x00, // name: <root>
|
||||
0x00, 0x29, // type: OPT
|
||||
0x10, 0x00, // UDP payload size
|
||||
0x00, // higher bits in extended RCODE
|
||||
0x00, // EDNS0 version
|
||||
0x80, 0x00, // "Z" field
|
||||
0x00, 0x00, // data length
|
||||
})
|
||||
|
||||
// Query for "google.com"
|
||||
f.Add([]byte{
|
||||
// header
|
||||
0xaf, 0x66, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
// query
|
||||
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f,
|
||||
0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||
// OPT
|
||||
0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
|
||||
0x0c, 0x00, 0x0a, 0x00, 0x08, 0x62, 0x18, 0x1a, 0xcb, 0x19,
|
||||
0xd7, 0xee, 0x23,
|
||||
})
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
clampEDNSSize(data, maxResponseBytes)
|
||||
})
|
||||
}
|
||||
|
||||
type testDNSServerOptions struct {
|
||||
SkipUDP bool
|
||||
SkipTCP bool
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolver
|
||||
|
||||
import "testing"
|
||||
|
||||
func FuzzClampEDNSSize(f *testing.F) {
|
||||
// Empty DNS packet
|
||||
f.Add([]byte{
|
||||
// query id
|
||||
0x12, 0x34,
|
||||
// flags: standard query, recurse
|
||||
0x01, 0x20,
|
||||
// num questions
|
||||
0x00, 0x00,
|
||||
// num answers
|
||||
0x00, 0x00,
|
||||
// num authority RRs
|
||||
0x00, 0x00,
|
||||
// num additional RRs
|
||||
0x00, 0x00,
|
||||
})
|
||||
|
||||
// Empty OPT
|
||||
f.Add([]byte{
|
||||
// header
|
||||
0xaf, 0x66, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
// query
|
||||
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f,
|
||||
0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||
// OPT
|
||||
0x00, // name: <root>
|
||||
0x00, 0x29, // type: OPT
|
||||
0x10, 0x00, // UDP payload size
|
||||
0x00, // higher bits in extended RCODE
|
||||
0x00, // EDNS0 version
|
||||
0x80, 0x00, // "Z" field
|
||||
0x00, 0x00, // data length
|
||||
})
|
||||
|
||||
// Query for "google.com"
|
||||
f.Add([]byte{
|
||||
// header
|
||||
0xaf, 0x66, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
// query
|
||||
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f,
|
||||
0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||
// OPT
|
||||
0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
|
||||
0x0c, 0x00, 0x0a, 0x00, 0x08, 0x62, 0x18, 0x1a, 0xcb, 0x19,
|
||||
0xd7, 0xee, 0x23,
|
||||
})
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
clampEDNSSize(data, maxResponseBytes)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package traffic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func FuzzNodeHasherCompare(f *testing.F) {
|
||||
for _, seed := range [][]uint64{
|
||||
{0, 0, 0},
|
||||
{1, 1, 1},
|
||||
{1, 10, 11},
|
||||
{1, 11, 10},
|
||||
{2, 10, 11},
|
||||
} {
|
||||
selfID, aID, bID := seed[0], seed[1], seed[2]
|
||||
f.Add(selfID, aID, bID)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, selfID, aID, bID uint64) {
|
||||
h := MakeRendezvousHasher(tailcfg.NodeID(selfID))
|
||||
a, b := tailcfg.NodeID(aID), tailcfg.NodeID(bID)
|
||||
c := h.Compare(a, b)
|
||||
if c == 0 && a != b {
|
||||
t.Fatalf("got %d: expected different hashes because a ≠ b, ", c)
|
||||
}
|
||||
if cc := h.Compare(a, b); c != cc {
|
||||
t.Fatalf("c %d, cc %d: expected matching values", c, cc)
|
||||
}
|
||||
if d := h.Compare(b, a); c != -d {
|
||||
t.Fatalf("c %d, d %d: expected inverse values", c, d)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzSortNodes tests that nodes are sorted such that every node
|
||||
// with an equal score has an equal chance of being first.
|
||||
func FuzzSortNodes(f *testing.F) {
|
||||
for _, seed := range [][]uint64{
|
||||
{0, 0},
|
||||
{1, 2},
|
||||
{37, 42},
|
||||
{5376970906336561236, 2417489263451880030},
|
||||
} {
|
||||
f.Add(seed[0], seed[1])
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, seed1, seed2 uint64) {
|
||||
rnd := rand.New(rand.NewPCG(seed1, seed2))
|
||||
|
||||
wantScore := Score(rnd.IntN(2000))
|
||||
|
||||
// Create a reasonably large tailnet.
|
||||
// If the number of nodes is too small, the chances of one node
|
||||
// getting ranked best becomes non-trivial.
|
||||
clients := make([]tailcfg.NodeView, 10_000)
|
||||
for i := range len(clients) {
|
||||
n := tailcfg.Node{
|
||||
ID: tailcfg.NodeID(rnd.Int64()),
|
||||
}
|
||||
clients[i] = n.View()
|
||||
}
|
||||
|
||||
// Smaller number of candidates, crossing some power of
|
||||
// two boundaries to ensure most-significant-bits don't skew
|
||||
// the hash output distribution.
|
||||
candidates := make([]tailcfg.NodeView, rnd.IntN(16)+1)
|
||||
for i := range len(candidates) {
|
||||
n := tailcfg.Node{
|
||||
ID: tailcfg.NodeID(rnd.Int64()),
|
||||
Hostinfo: (&tailcfg.Hostinfo{
|
||||
Location: &tailcfg.Location{
|
||||
Priority: int(wantScore),
|
||||
},
|
||||
}).View(),
|
||||
}
|
||||
candidates[i] = n.View()
|
||||
}
|
||||
|
||||
// All scores should be the same.
|
||||
ss := ScoresFor(clients[0].ID(), candidates)
|
||||
for _, n := range candidates {
|
||||
s := ss.Score(n)
|
||||
if s != wantScore {
|
||||
t.Errorf("%s: score %d, want %d", n.ID(), s, wantScore)
|
||||
}
|
||||
}
|
||||
|
||||
// Map each candidate to the number of clients that it was the best candidate for
|
||||
best := make(map[tailcfg.NodeID]int, len(candidates))
|
||||
for i := range len(clients) {
|
||||
peers := slices.Clone(candidates)
|
||||
selfID := clients[i].ID()
|
||||
ss := ScoresFor(selfID, peers)
|
||||
ss.SortNodes(peers)
|
||||
bestID := peers[0].ID()
|
||||
best[bestID]++
|
||||
}
|
||||
|
||||
// 20% margin off perfect fairness
|
||||
fair := float64(len(clients)) / float64(len(candidates))
|
||||
lo, hi := math.Floor(fair*0.8), math.Ceil(fair*1.2)
|
||||
|
||||
for candidateID, count := range best {
|
||||
if float64(count) < lo || float64(count) > hi {
|
||||
t.Logf("total clients %d; total candidates %d; fair %f", len(clients), len(candidates), fair)
|
||||
t.Errorf("%s is best too frequently: %d", candidateID, count)
|
||||
t.Fatalf("best map: %v", best)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,9 +5,6 @@ package traffic_test
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
@@ -114,111 +111,6 @@ func TestScoreMany(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzNodeHasherCompare(f *testing.F) {
|
||||
for _, seed := range [][]uint64{
|
||||
{0, 0, 0},
|
||||
{1, 1, 1},
|
||||
{1, 10, 11},
|
||||
{1, 11, 10},
|
||||
{2, 10, 11},
|
||||
} {
|
||||
selfID, aID, bID := seed[0], seed[1], seed[2]
|
||||
f.Add(selfID, aID, bID)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, selfID, aID, bID uint64) {
|
||||
h := traffic.MakeRendezvousHasher(tailcfg.NodeID(selfID))
|
||||
a, b := tailcfg.NodeID(aID), tailcfg.NodeID(bID)
|
||||
c := h.Compare(a, b)
|
||||
if c == 0 && a != b {
|
||||
t.Fatalf("got %d: expected different hashes because a ≠ b, ", c)
|
||||
}
|
||||
if cc := h.Compare(a, b); c != cc {
|
||||
t.Fatalf("c %d, cc %d: expected matching values", c, cc)
|
||||
}
|
||||
if d := h.Compare(b, a); c != -d {
|
||||
t.Fatalf("c %d, d %d: expected inverse values", c, d)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzSortNodes tests that nodes are sorted such that every node
|
||||
// with an equal score has an equal chance of being first.
|
||||
func FuzzSortNodes(f *testing.F) {
|
||||
for _, seed := range [][]uint64{
|
||||
{0, 0},
|
||||
{1, 2},
|
||||
{37, 42},
|
||||
{5376970906336561236, 2417489263451880030},
|
||||
} {
|
||||
f.Add(seed[0], seed[1])
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, seed1, seed2 uint64) {
|
||||
rnd := rand.New(rand.NewPCG(seed1, seed2))
|
||||
|
||||
wantScore := traffic.Score(rnd.IntN(2000))
|
||||
|
||||
// Create a reasonably large tailnet.
|
||||
// If the number of nodes is too small, the chances of one node
|
||||
// getting ranked best becomes non-trivial.
|
||||
clients := make([]tailcfg.NodeView, 10_000)
|
||||
for i := range len(clients) {
|
||||
n := tailcfg.Node{
|
||||
ID: tailcfg.NodeID(rnd.Int64()),
|
||||
}
|
||||
clients[i] = n.View()
|
||||
}
|
||||
|
||||
// Smaller number of candidates, crossing some power of
|
||||
// two boundaries to ensure most-significant-bits don't skew
|
||||
// the hash output distribution.
|
||||
candidates := make([]tailcfg.NodeView, rnd.IntN(16)+1)
|
||||
for i := range len(candidates) {
|
||||
n := tailcfg.Node{
|
||||
ID: tailcfg.NodeID(rnd.Int64()),
|
||||
Hostinfo: (&tailcfg.Hostinfo{
|
||||
Location: &tailcfg.Location{
|
||||
Priority: int(wantScore),
|
||||
},
|
||||
}).View(),
|
||||
}
|
||||
candidates[i] = n.View()
|
||||
}
|
||||
|
||||
// All scores should be the same.
|
||||
ss := traffic.ScoresFor(clients[0].ID(), candidates)
|
||||
for _, n := range candidates {
|
||||
s := ss.Score(n)
|
||||
if s != wantScore {
|
||||
t.Errorf("%s: score %d, want %d", n.ID(), s, wantScore)
|
||||
}
|
||||
}
|
||||
|
||||
// Map each candidate to the number of clients that it was the best candidate for
|
||||
best := make(map[tailcfg.NodeID]int, len(candidates))
|
||||
for i := range len(clients) {
|
||||
peers := slices.Clone(candidates)
|
||||
selfID := clients[i].ID()
|
||||
ss := traffic.ScoresFor(selfID, peers)
|
||||
ss.SortNodes(peers)
|
||||
bestID := peers[0].ID()
|
||||
best[bestID]++
|
||||
}
|
||||
|
||||
// 20% margin off perfect fairness
|
||||
fair := float64(len(clients)) / float64(len(candidates))
|
||||
lo, hi := math.Floor(fair*0.8), math.Ceil(fair*1.2)
|
||||
|
||||
for candidateID, count := range best {
|
||||
if float64(count) < lo || float64(count) > hi {
|
||||
t.Logf("total clients %d; total candidates %d; fair %f", len(clients), len(candidates), fair)
|
||||
t.Errorf("%s is best too frequently: %d", candidateID, count)
|
||||
t.Fatalf("best map: %v", best)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMakeRendezvousHasher(t *testing.T) {
|
||||
t.Run("new-hasher", func(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package tailcfg
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzNodeIsRouter(f *testing.F) {
|
||||
encodePrefixes := func(f *testing.F, prefixes ...netip.Prefix) string {
|
||||
f.Helper()
|
||||
out := make([]string, len(prefixes))
|
||||
for i, p := range prefixes {
|
||||
out[i] = p.String()
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
decodePrefixes := func(t *testing.T, prefixes string) []netip.Prefix {
|
||||
t.Helper()
|
||||
var out []netip.Prefix
|
||||
for p := range strings.FieldsSeq(prefixes) {
|
||||
pfx, err := netip.ParsePrefix(p)
|
||||
if err != nil {
|
||||
t.Skipf("%q: %v", prefixes, err)
|
||||
}
|
||||
out = append(out, pfx)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
node Node
|
||||
wantRouter bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
node: Node{},
|
||||
},
|
||||
{
|
||||
name: "plain-ipv4",
|
||||
node: Node{
|
||||
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")},
|
||||
AllowedIPs: []netip.Prefix{netip.MustParsePrefix("100.70.2.3/24")},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exit-node-ipv4",
|
||||
node: Node{
|
||||
Addresses: []netip.Prefix{netip.MustParsePrefix("10.0.1.2/32")},
|
||||
AllowedIPs: []netip.Prefix{netip.MustParsePrefix("0.70.3.4/24"), netip.MustParsePrefix("0.71.5.6/16")},
|
||||
},
|
||||
wantRouter: true,
|
||||
},
|
||||
} {
|
||||
addresses := encodePrefixes(f, tc.node.Addresses...)
|
||||
allowedIPs := encodePrefixes(f, tc.node.AllowedIPs...)
|
||||
f.Logf("addresses=%q allowedIPs=%q", addresses, allowedIPs)
|
||||
f.Add(addresses, allowedIPs)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, addresses, allowedIPs string) {
|
||||
n := Node{
|
||||
Addresses: decodePrefixes(t, addresses),
|
||||
AllowedIPs: decodePrefixes(t, allowedIPs),
|
||||
}
|
||||
if gotN, gotV := n.IsRouter(), n.View().IsRouter(); gotN != gotV {
|
||||
t.Errorf("mismatched node %t, view %t; addresses=%q allowedIPs=%q",
|
||||
gotN, gotV, addresses, allowedIPs)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,13 +5,11 @@ package tailcfg_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/netip"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -797,57 +795,6 @@ func TestNodeIsRouter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzNodeIsRouter(f *testing.F) {
|
||||
encodePrefixes := func(f *testing.F, prefixes ...netip.Prefix) string {
|
||||
f.Helper()
|
||||
out := make([]string, len(prefixes))
|
||||
for i, p := range prefixes {
|
||||
out[i] = p.String()
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
decodePrefixes := func(t *testing.T, prefixes string) []netip.Prefix {
|
||||
t.Helper()
|
||||
var out []netip.Prefix
|
||||
for p := range strings.FieldsSeq(prefixes) {
|
||||
pfx, err := netip.ParsePrefix(p)
|
||||
if err != nil {
|
||||
log.Printf("skipping %q: %v", prefixes, err)
|
||||
t.Skipf("%q: %v", prefixes, err)
|
||||
}
|
||||
out = append(out, pfx)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
for _, tc := range nodeIsRouterCases {
|
||||
addresses := encodePrefixes(f, tc.node.Addresses...)
|
||||
allowedIPs := encodePrefixes(f, tc.node.AllowedIPs...)
|
||||
f.Logf("addresses=%q allowedIPs=%q", addresses, allowedIPs)
|
||||
f.Add(addresses, allowedIPs)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, addresses, allowedIPs string) {
|
||||
n := Node{
|
||||
Addresses: decodePrefixes(t, addresses),
|
||||
AllowedIPs: decodePrefixes(t, allowedIPs),
|
||||
}
|
||||
ps := peerStatusFromNode(n.View())
|
||||
t.Logf("%v %v", n.Addresses, n.AllowedIPs)
|
||||
|
||||
if len(n.Addresses) != len(ps.TailscaleIPs) ||
|
||||
len(n.AllowedIPs) != ps.AllowedIPs.Len() {
|
||||
t.Skip("n and ps are not equivalent")
|
||||
}
|
||||
|
||||
gotN := n.IsRouter()
|
||||
gotPS := ps.IsRouter()
|
||||
if gotN != gotPS {
|
||||
t.Errorf("mismatched node %t, peer status %t; addresses=%q allowedIPs=%q",
|
||||
gotN, gotPS, addresses, allowedIPs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func peerStatusFromNode(n NodeView) *ipnstate.PeerStatus {
|
||||
ps := &ipnstate.PeerStatus{
|
||||
ID: n.StableID(),
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package geo
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzPointSphericalAngleTo(f *testing.F) {
|
||||
for _, tt := range []struct{ xLat, xLng, yLat, yLng float64 }{
|
||||
{0.0, 0.0, 0.0, 0.0},
|
||||
{90.0, 0.0, 90.0, 90.0},
|
||||
{-90.0, 0.0, -90.0, -90.0},
|
||||
{90.0, 0.0, -90.0, -90.0},
|
||||
{43.6532, -79.3832, 45.5019, -73.5674},
|
||||
{-33.8727, 151.2057, 37.7749, -122.4194},
|
||||
{40.7128, -74.006, 48.8575, 2.3514},
|
||||
{47.6061, -122.3328, 35.6764, 139.65},
|
||||
{-6.0, 0.0, -6.0, 0.0},
|
||||
} {
|
||||
f.Add(tt.xLat, tt.xLng, tt.yLat, tt.yLng)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, xLat float64, xLng float64, yLat float64, yLng float64) {
|
||||
x := MakePoint(Degrees(xLat), Degrees(xLng))
|
||||
y := MakePoint(Degrees(yLat), Degrees(yLng))
|
||||
got, _ := x.SphericalAngleTo(y)
|
||||
if math.IsNaN(float64(got)) {
|
||||
t.Errorf("got NaN result with xLat=%f xLng=%f yLat=%f yLng=%f", xLat, xLng, yLat, yLng)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -213,62 +212,6 @@ func encodeNaive(src []byte) (dst []byte) {
|
||||
return dst
|
||||
}
|
||||
|
||||
func FuzzRoundtrip(f *testing.F) {
|
||||
for _, tt := range tests {
|
||||
f.Add(tt.decoded)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, wantDecoded []byte) {
|
||||
var seed [32]byte
|
||||
copy(seed[:], wantDecoded)
|
||||
rn := rand.New(rand.NewChaCha8(seed))
|
||||
|
||||
prefixLen := min(rn.IntN(len(wantDecoded)+1), 8)
|
||||
gotDecoded := slices.Grow(bytes.Clone(wantDecoded), rn.IntN(numOverhead(wantDecoded[prefixLen:])+1))
|
||||
wantEncoded := append(bytes.Clone(wantDecoded[:prefixLen]), encodeNaive(wantDecoded[prefixLen:])...)
|
||||
|
||||
gotEncodedForward := appendEncodeForward(slices.Clip(gotDecoded[:prefixLen]), gotDecoded[prefixLen:])
|
||||
if string(gotEncodedForward) != string(wantEncoded) {
|
||||
t.Errorf("EncodeForward(%d:%x) = %x, want %x", prefixLen, wantDecoded, gotEncodedForward, wantEncoded)
|
||||
}
|
||||
|
||||
gotEncodedReverse := appendEncodeReverse(gotDecoded[:prefixLen], gotDecoded[prefixLen:])
|
||||
if string(gotEncodedReverse) != string(wantEncoded) {
|
||||
t.Errorf("EncodeReverse(%d:%x) = %x, want %x", prefixLen, wantDecoded, gotEncodedReverse, wantEncoded)
|
||||
}
|
||||
|
||||
gotOverhead := numOverhead(wantDecoded[prefixLen:])
|
||||
wantOverhead := len(wantEncoded) - len(wantDecoded)
|
||||
if gotOverhead != wantOverhead {
|
||||
t.Errorf("numOverhead(%x) = %d, want %d", wantDecoded[prefixLen:], gotOverhead, wantOverhead)
|
||||
}
|
||||
|
||||
gotDecoded = must.Get(AppendDecode(wantEncoded[:prefixLen], wantEncoded[prefixLen:]))
|
||||
if string(gotDecoded) != string(wantDecoded) {
|
||||
t.Errorf("Decode(Encode(%d:%x)) != %x", prefixLen, wantDecoded, gotDecoded)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzMostlyBijective(f *testing.F) {
|
||||
for _, tt := range tests {
|
||||
f.Add(tt.encoded)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, wantEncoded []byte) {
|
||||
// There is mostly a bijective mapping for the COBS-encoding
|
||||
// such that there is exactly only one valid COBS-encoded blob
|
||||
// for every possible non-encoded blob.
|
||||
// The only exception is a trailing empty block following
|
||||
// a full block of non-zeros.
|
||||
decoded, err := AppendDecode(nil, wantEncoded) // must never panic
|
||||
if err == nil {
|
||||
gotEncoded := AppendEncode(nil, decoded)
|
||||
if string(gotEncoded) != string(wantEncoded) && string(gotEncoded)+"\x01" != string(wantEncoded) {
|
||||
t.Errorf("Encode(Decode(%x)) != %x", wantEncoded, gotEncoded)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark(b *testing.B) {
|
||||
const length = 1 << 20
|
||||
out := make([]byte, MaxEncodedLen(length))
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cobs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
func FuzzRoundtrip(f *testing.F) {
|
||||
for _, tt := range tests {
|
||||
f.Add(tt.decoded)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, wantDecoded []byte) {
|
||||
var seed [32]byte
|
||||
copy(seed[:], wantDecoded)
|
||||
rn := rand.New(rand.NewChaCha8(seed))
|
||||
|
||||
prefixLen := min(rn.IntN(len(wantDecoded)+1), 8)
|
||||
gotDecoded := slices.Grow(bytes.Clone(wantDecoded), rn.IntN(numOverhead(wantDecoded[prefixLen:])+1))
|
||||
wantEncoded := append(bytes.Clone(wantDecoded[:prefixLen]), encodeNaive(wantDecoded[prefixLen:])...)
|
||||
|
||||
gotEncodedForward := appendEncodeForward(slices.Clip(gotDecoded[:prefixLen]), gotDecoded[prefixLen:])
|
||||
if string(gotEncodedForward) != string(wantEncoded) {
|
||||
t.Errorf("EncodeForward(%d:%x) = %x, want %x", prefixLen, wantDecoded, gotEncodedForward, wantEncoded)
|
||||
}
|
||||
|
||||
gotEncodedReverse := appendEncodeReverse(gotDecoded[:prefixLen], gotDecoded[prefixLen:])
|
||||
if string(gotEncodedReverse) != string(wantEncoded) {
|
||||
t.Errorf("EncodeReverse(%d:%x) = %x, want %x", prefixLen, wantDecoded, gotEncodedReverse, wantEncoded)
|
||||
}
|
||||
|
||||
gotOverhead := numOverhead(wantDecoded[prefixLen:])
|
||||
wantOverhead := len(wantEncoded) - len(wantDecoded)
|
||||
if gotOverhead != wantOverhead {
|
||||
t.Errorf("numOverhead(%x) = %d, want %d", wantDecoded[prefixLen:], gotOverhead, wantOverhead)
|
||||
}
|
||||
|
||||
gotDecoded = must.Get(AppendDecode(wantEncoded[:prefixLen], wantEncoded[prefixLen:]))
|
||||
if string(gotDecoded) != string(wantDecoded) {
|
||||
t.Errorf("Decode(Encode(%d:%x)) != %x", prefixLen, wantDecoded, gotDecoded)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzMostlyBijective(f *testing.F) {
|
||||
for _, tt := range tests {
|
||||
f.Add(tt.encoded)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, wantEncoded []byte) {
|
||||
// There is mostly a bijective mapping for the COBS-encoding
|
||||
// such that there is exactly only one valid COBS-encoded blob
|
||||
// for every possible non-encoded blob.
|
||||
// The only exception is a trailing empty block following
|
||||
// a full block of non-zeros.
|
||||
decoded, err := AppendDecode(nil, wantEncoded) // must never panic
|
||||
if err == nil {
|
||||
gotEncoded := AppendEncode(nil, decoded)
|
||||
if string(gotEncoded) != string(wantEncoded) && string(gotEncoded)+"\x01" != string(wantEncoded) {
|
||||
t.Errorf("Encode(Decode(%x)) != %x", wantEncoded, gotEncoded)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,7 +5,6 @@ package def_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -95,54 +94,3 @@ func TestDuration(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzBool(f *testing.F) {
|
||||
for _, tc := range []struct {
|
||||
in string
|
||||
def bool
|
||||
}{
|
||||
{in: "", def: true},
|
||||
{in: "", def: false},
|
||||
{in: "true", def: false},
|
||||
{in: "false", def: true},
|
||||
{in: "sure", def: true},
|
||||
{in: "sure", def: false},
|
||||
} {
|
||||
f.Add(tc.in, tc.def)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, in string, fallback bool) {
|
||||
got := def.Bool(in, fallback)
|
||||
want, err := strconv.ParseBool(in)
|
||||
if in == "" || err != nil {
|
||||
want = fallback
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("Bool(%q, %v) = %v; want %v", in, fallback, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDuration(f *testing.F) {
|
||||
for _, tc := range []struct {
|
||||
in string
|
||||
def time.Duration
|
||||
}{
|
||||
{in: "", def: time.Second},
|
||||
{in: "", def: 0},
|
||||
{in: "2m30s", def: time.Second},
|
||||
{in: "soon", def: time.Second},
|
||||
} {
|
||||
f.Add(tc.in, int64(tc.def))
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, in string, fallbackN int64) {
|
||||
fallback := time.Duration(fallbackN)
|
||||
got := def.Duration(in, fallback)
|
||||
want, err := time.ParseDuration(in)
|
||||
if in == "" || err != nil {
|
||||
want = fallback
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("Duration(%q, %v) = %v; want %v", in, fallback, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package def
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func FuzzBool(f *testing.F) {
|
||||
for _, tc := range []struct {
|
||||
in string
|
||||
def bool
|
||||
}{
|
||||
{in: "", def: true},
|
||||
{in: "", def: false},
|
||||
{in: "true", def: false},
|
||||
{in: "false", def: true},
|
||||
{in: "sure", def: true},
|
||||
{in: "sure", def: false},
|
||||
} {
|
||||
f.Add(tc.in, tc.def)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, in string, fallback bool) {
|
||||
got := Bool(in, fallback)
|
||||
want, err := strconv.ParseBool(in)
|
||||
if in == "" || err != nil {
|
||||
want = fallback
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("Bool(%q, %v) = %v; want %v", in, fallback, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDuration(f *testing.F) {
|
||||
for _, tc := range []struct {
|
||||
in string
|
||||
def time.Duration
|
||||
}{
|
||||
{in: "", def: time.Second},
|
||||
{in: "", def: 0},
|
||||
{in: "2m30s", def: time.Second},
|
||||
{in: "soon", def: time.Second},
|
||||
} {
|
||||
f.Add(tc.in, int64(tc.def))
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, in string, fallbackN int64) {
|
||||
fallback := time.Duration(fallbackN)
|
||||
got := Duration(in, fallback)
|
||||
want, err := time.ParseDuration(in)
|
||||
if in == "" || err != nil {
|
||||
want = fallback
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("Duration(%q, %v) = %v; want %v", in, fallback, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -128,59 +127,6 @@ func TestAllocations(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzBlock512(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64) {
|
||||
c := qt.New(t)
|
||||
|
||||
execute := func(h hasher, r *rand.Rand) {
|
||||
for range r.Intn(256) {
|
||||
switch r.Intn(5) {
|
||||
case 0:
|
||||
n := uint8(r.Uint64())
|
||||
h.HashUint8(n)
|
||||
case 1:
|
||||
n := uint16(r.Uint64())
|
||||
h.HashUint16(n)
|
||||
case 2:
|
||||
n := uint32(r.Uint64())
|
||||
h.HashUint32(n)
|
||||
case 3:
|
||||
n := uint64(r.Uint64())
|
||||
h.HashUint64(n)
|
||||
case 4:
|
||||
b := make([]byte, r.Intn(256))
|
||||
r.Read(b)
|
||||
h.HashBytes(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r1 := rand.New(rand.NewSource(seed))
|
||||
r2 := rand.New(rand.NewSource(seed))
|
||||
|
||||
h1 := must.Get(New512(sha256.New()))
|
||||
h2 := newNaive()
|
||||
|
||||
execute(h1, r1)
|
||||
execute(h2, r2)
|
||||
|
||||
c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
|
||||
|
||||
execute(h1, r1)
|
||||
execute(h2, r2)
|
||||
|
||||
c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
|
||||
|
||||
h1.Reset()
|
||||
h2.Reset()
|
||||
|
||||
execute(h1, r1)
|
||||
execute(h2, r2)
|
||||
|
||||
c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark(b *testing.B) {
|
||||
var sum [sha256.Size]byte
|
||||
b.Run("Hash", func(b *testing.B) {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package hashx
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
func FuzzBlock512(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed int64) {
|
||||
c := qt.New(t)
|
||||
|
||||
execute := func(h hasher, r *rand.Rand) {
|
||||
for range r.Intn(256) {
|
||||
switch r.Intn(5) {
|
||||
case 0:
|
||||
n := uint8(r.Uint64())
|
||||
h.HashUint8(n)
|
||||
case 1:
|
||||
n := uint16(r.Uint64())
|
||||
h.HashUint16(n)
|
||||
case 2:
|
||||
n := uint32(r.Uint64())
|
||||
h.HashUint32(n)
|
||||
case 3:
|
||||
n := uint64(r.Uint64())
|
||||
h.HashUint64(n)
|
||||
case 4:
|
||||
b := make([]byte, r.Intn(256))
|
||||
r.Read(b)
|
||||
h.HashBytes(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r1 := rand.New(rand.NewSource(seed))
|
||||
r2 := rand.New(rand.NewSource(seed))
|
||||
|
||||
h1 := must.Get(New512(sha256.New()))
|
||||
h2 := newNaive()
|
||||
|
||||
execute(h1, r1)
|
||||
execute(h2, r2)
|
||||
|
||||
c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
|
||||
|
||||
execute(h1, r1)
|
||||
execute(h2, r2)
|
||||
|
||||
c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
|
||||
|
||||
h1.Reset()
|
||||
h2.Reset()
|
||||
|
||||
execute(h1, r1)
|
||||
execute(h2, r2)
|
||||
|
||||
c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package nocasemaps
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzAppendToLower(f *testing.F) {
|
||||
for _, tt := range lowerTests {
|
||||
f.Add(tt.in)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, in string) {
|
||||
got := string(appendToLower(nil, in))
|
||||
want := strings.ToLower(in)
|
||||
if got != want {
|
||||
t.Errorf("appendToLower(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -85,19 +85,6 @@ func TestAppendToLower(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzAppendToLower(f *testing.F) {
|
||||
for _, tt := range lowerTests {
|
||||
f.Add(tt.in)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, in string) {
|
||||
got := string(appendToLower(nil, in))
|
||||
want := strings.ToLower(in)
|
||||
if got != want {
|
||||
t.Errorf("appendToLower(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
testLower = "production-server"
|
||||
testUpper = "PRODUCTION-SERVER"
|
||||
|
||||
@@ -178,19 +178,3 @@ func TestLines(t *testing.T) {
|
||||
t.Errorf("Lines: output unexpectedly truncated")
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzDiff(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, x, y string, maxSize int) {
|
||||
const maxInput = 1e3
|
||||
if len(x) > maxInput {
|
||||
x = x[:maxInput]
|
||||
}
|
||||
if len(y) > maxInput {
|
||||
y = y[:maxInput]
|
||||
}
|
||||
diff, _ := Lines(x, y, maxSize) // make sure this does not panic
|
||||
if strings.Count(diff, "\n") > 1 && maxSize >= 0 && len(diff) > maxSize {
|
||||
t.Fatal("maxSize exceeded")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package safediff
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzDiff(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, x, y string, maxSize int) {
|
||||
const maxInput = 1e3
|
||||
if len(x) > maxInput {
|
||||
x = x[:maxInput]
|
||||
}
|
||||
if len(y) > maxInput {
|
||||
y = y[:maxInput]
|
||||
}
|
||||
diff, _ := Lines(x, y, maxSize) // make sure this does not panic
|
||||
if strings.Count(diff, "\n") > 1 && maxSize >= 0 && len(diff) > maxSize {
|
||||
t.Fatal("maxSize exceeded")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netlog
|
||||
|
||||
import "testing"
|
||||
|
||||
func FuzzQuotedLen(f *testing.F) {
|
||||
for _, s := range quotedLenTestdata {
|
||||
f.Add(s)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
testQuotedLen(t, s)
|
||||
})
|
||||
}
|
||||
@@ -218,15 +218,6 @@ func TestToNode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzQuotedLen(f *testing.F) {
|
||||
for _, s := range quotedLenTestdata {
|
||||
f.Add(s)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
testQuotedLen(t, s)
|
||||
})
|
||||
}
|
||||
|
||||
func TestQuotedLen(t *testing.T) {
|
||||
for _, s := range quotedLenTestdata {
|
||||
testQuotedLen(t, s)
|
||||
|
||||
Reference in new issue
Block a user