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:
Mike Jensen authored and GitHub committed 2026-09-01 13:15:45 -06:00
1 parent 316b22b775
commit 0188b38666
19 files changed
+546 -420

No files matched your search

-58
View File
@@ -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
+60
View File
@@ -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)
})
}
+119
View File
@@ -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)
}
}
})
}
-108
View File
@@ -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) {
+75
View File
@@ -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)
}
})
}
-53
View File
@@ -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(),
+34
View File
@@ -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)
}
})
}
-57
View File
@@ -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))
+69
View File
@@ -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)
}
}
})
}
-52
View File
@@ -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)
}
})
}
+61
View File
@@ -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)
}
})
}
-54
View File
@@ -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) {
+66
View File
@@ -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))
})
}
+22
View File
@@ -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)
}
})
}
-13
View File
@@ -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"
-16
View File
@@ -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")
}
})
}
+25
View File
@@ -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")
}
})
}
+15
View File
@@ -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)
})
}
-9
View File
@@ -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)