coder/cryptorand/strings_test.go

211 lines
4.3 KiB
Go

package cryptorand_test
import (
"crypto/rand"
"encoding/binary"
"math/big"
"strings"
"testing"
"unicode/utf8"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cryptorand"
)
func TestString(t *testing.T) {
t.Parallel()
for i := 0; i < 20; i++ {
rs, err := cryptorand.String(10)
require.NoError(t, err, "unexpected error from String")
t.Logf("value: %v <- random?", rs)
}
}
func TestStringCharset(t *testing.T) {
t.Parallel()
tests := []struct {
Name string
Charset string
HelperFunc func(int) (string, error)
Length int
}{
{
Name: "MultiByte-20",
Charset: "💓😘💓🌷",
Length: 20,
},
{
Name: "MultiByte-7",
Charset: "😇🥰😍🤩😘😗☺️😚😙🥲😋😛😜🤪😝🤑",
Length: 7,
},
{
Name: "MixedBytes",
Charset: "🍋🍌🍍🥭🍎🍏🍐🍑🍒🍓🫐🥝🍅🫒🥥🥑🍆🥔abcdefg1234",
Length: 10,
},
{
Name: "Empty",
Charset: cryptorand.Default,
Length: 0,
HelperFunc: cryptorand.String,
},
{
Name: "Numeric",
Charset: cryptorand.Numeric,
Length: 1,
},
{
Name: "Upper",
Charset: cryptorand.Upper,
Length: 3,
},
{
Name: "Lower",
Charset: cryptorand.Lower,
Length: 10,
},
{
Name: "Alpha",
Charset: cryptorand.Alpha,
Length: 20,
},
{
Name: "Default",
Charset: cryptorand.Default,
Length: 10,
},
{
Name: "Hex",
Charset: cryptorand.Hex,
Length: 15,
HelperFunc: cryptorand.HexString,
},
{
Name: "Human",
Charset: cryptorand.Human,
Length: 20,
},
}
for _, test := range tests {
test := test
t.Run(test.Name, func(t *testing.T) {
t.Parallel()
for i := 0; i < 5; i++ {
rs, err := cryptorand.StringCharset(test.Charset, test.Length)
require.NoError(t, err, "unexpected error from StringCharset")
require.Equal(t, test.Length, utf8.RuneCountInString(rs), "expected RuneCountInString to match requested")
if i == 0 {
t.Logf("value: %v <- random?", rs)
}
}
})
if test.HelperFunc != nil {
t.Run(test.Name+"HelperFunc", func(t *testing.T) {
t.Parallel()
for i := 0; i < 5; i++ {
rs, err := test.HelperFunc(test.Length)
require.NoError(t, err, "unexpected error from HelperFunc")
require.Equal(t, test.Length, utf8.RuneCountInString(rs), "expected RuneCountInString to match requested")
if i == 0 {
t.Logf("value: %v <- random?", rs)
}
}
})
}
}
}
func TestSha1String(t *testing.T) {
t.Parallel()
for i := 0; i < 20; i++ {
rs, err := cryptorand.Sha1String()
require.NoError(t, err, "unexpected error from String")
require.Equal(t, 40, utf8.RuneCountInString(rs), "expected RuneCountInString to match requested")
t.Logf("value: %v <- random?", rs)
}
}
func BenchmarkString20(b *testing.B) {
b.SetBytes(20)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _ = cryptorand.String(20)
}
}
func BenchmarkStringUnsafe20(b *testing.B) {
mkstring := func(charSetStr string, size int) (string, error) {
charSet := []rune(charSetStr)
// This buffer facilitates pre-emptively creation of random uint32s
// to reduce syscall overhead.
ibuf := make([]byte, 4*size)
_, err := rand.Read(ibuf)
if err != nil {
return "", err
}
var buf strings.Builder
buf.Grow(size)
for i := 0; i < size; i++ {
n := binary.BigEndian.Uint32(ibuf[i*4 : (i+1)*4])
_, _ = buf.WriteRune(charSet[n%uint32(len(charSet))])
}
return buf.String(), nil
}
b.SetBytes(20)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _ = mkstring(cryptorand.Default, 20)
}
}
func BenchmarkStringBigint20(b *testing.B) {
mkstring := func(charSetStr string, size int) (string, error) {
charSet := []rune(charSetStr)
var buf strings.Builder
buf.Grow(size)
bi := big.NewInt(int64(size))
for i := 0; i < size; i++ {
num, err := rand.Int(rand.Reader, bi)
if err != nil {
return "", err
}
_, _ = buf.WriteRune(charSet[num.Uint64()%uint64(len(charSet))])
}
return buf.String(), nil
}
b.SetBytes(20)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _ = mkstring(cryptorand.Default, 20)
}
}
func BenchmarkStringRuneCast(b *testing.B) {
s := strings.Repeat("0", 20)
b.SetBytes(int64(len(s)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = []rune(s)
}
}