mirror of
https://github.com/jimeh/rands.git
synced 2026-02-19 03:16:39 +00:00
feat(randsmust): add randsmust package
randsmust is specifically intended as an alternative to rands for use in tests. All functions return a single value, and panic in the event of an error. This makes them easy to use when building structs in test cases that need random data. Internally the package simply calls the equivalent function from the rands package, and panics if a error is returned.
This commit is contained in:
65
README.md
65
README.md
@@ -36,6 +36,22 @@ alt="GitHub issues">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## [`rands`](https://pkg.go.dev/github.com/jimeh/rands) package
|
||||
|
||||
`rands` is intended for use in production code where random data generation is
|
||||
required. All functions have a error return value which should be checked.
|
||||
|
||||
For tests there is the `randsmust` package, which has all the same functions but
|
||||
with single return values, and they panic in the event of an error.
|
||||
|
||||
### Import
|
||||
|
||||
```
|
||||
import "github.com/jimeh/rands"
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```go
|
||||
s, err := rands.Base64(16) // => CYxqEdUB1Rzno3SyZu2g/g==
|
||||
s, err := rands.Base64URL(16) // => zlqw9aFqcFggbk2asn3_aQ
|
||||
@@ -60,17 +76,54 @@ n, err := rands.Int64(int64(9223372036854775807)) // => 8256935979116161233
|
||||
b, err := rands.Bytes(8) // => [0 220 137 243 135 204 34 63]
|
||||
```
|
||||
|
||||
## Import
|
||||
## [`randsmust`](https://pkg.go.dev/github.com/jimeh/rands/must) package
|
||||
|
||||
`randsmust` is specifically intended as an alternative to `rands` for use in
|
||||
tests. All functions return a single value, and panic in the event of an error.
|
||||
This makes them easy to use when building structs in test cases that need random
|
||||
data.
|
||||
|
||||
For production code, make sure to use the `rands` package and check returned
|
||||
errors.
|
||||
|
||||
### Import
|
||||
|
||||
```
|
||||
import "github.com/jimeh/rands"
|
||||
import "github.com/jimeh/rands/randsmust"
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```go
|
||||
s := randsmust.Base64(16) // => d1wm/wS6AQGduO3uaey1Cg==
|
||||
s := randsmust.Base64URL(16) // => 4pHWVcddXsL_45vhOfCdng
|
||||
s := randsmust.Hex(16) // => b5552558bc009264d129c422a666fe56
|
||||
s := randsmust.Alphanumeric(16) // => j5WkpNKmW8K701XF
|
||||
s := randsmust.Alphabetic(16) // => OXxsqfFjNLvmZqDb
|
||||
s := randsmust.Upper(16) // => AOTLYQRCVNMEPRCX
|
||||
s := randsmust.UpperNumeric(16) // => 1NTY6KATDVAXBTY2
|
||||
s := randsmust.Lower(16) // => xmftrwvurrritqfu
|
||||
s := randsmust.LowerNumeric(16) // => yszg56fzeql7pjpl
|
||||
s := randsmust.Numeric(16) // => 0761782105447226
|
||||
|
||||
s := randsmust.String(16, "abcdefABCDEF") // => dfAbBfaDDdDFDaEa
|
||||
s := randsmust.UnicodeString(16, []rune("九七二人入八力十下三千上口土夕大")) // => 十十千口三十十下九上千口七夕土口
|
||||
|
||||
s := randsmust.DNSLabel(16) // => pu31o0gqyk76x35f
|
||||
s := randsmust.UUID() // => d616c873-f3dd-4690-bcd6-ed307eec1105
|
||||
|
||||
n := randsmust.Int(2147483647) // => 1293388115
|
||||
n := randsmust.Int64(int64(9223372036854775807)) // => 6168113630900161239
|
||||
|
||||
b := randsmust.Bytes(8) // => [205 128 54 95 0 95 53 51]
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Please see the
|
||||
[Go Reference](https://pkg.go.dev/github.com/jimeh/rands#section-documentation)
|
||||
for documentation and examples.
|
||||
Please see the Go Reference for documentation and examples:
|
||||
|
||||
- [`rands`](https://pkg.go.dev/github.com/jimeh/rands)
|
||||
- [`randsmust`](https://pkg.go.dev/github.com/jimeh/rands/must)
|
||||
|
||||
## Benchmarks
|
||||
|
||||
@@ -79,4 +132,4 @@ https://jimeh.me/rands/dev/bench/
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/jimeh/rands/blob/master/LICENSE)
|
||||
[MIT](https://github.com/jimeh/rands/blob/main/LICENSE)
|
||||
|
||||
3
rands.go
3
rands.go
@@ -8,6 +8,9 @@
|
||||
// rands is intended for use in production code where random data generation is
|
||||
// required. All functions have a error return value, which should be
|
||||
// checked.
|
||||
//
|
||||
// For tests there is the randsmust package, which has all the same functions
|
||||
// but with single return values, and they panic in the event of an error.
|
||||
package rands
|
||||
|
||||
import "errors"
|
||||
|
||||
13
randsmust/bytes.go
Normal file
13
randsmust/bytes.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package randsmust
|
||||
|
||||
import "github.com/jimeh/rands"
|
||||
|
||||
// Bytes generates a byte slice of n number of random bytes.
|
||||
func Bytes(n int) []byte {
|
||||
r, err := rands.Bytes(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
12
randsmust/bytes_example_test.go
Normal file
12
randsmust/bytes_example_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package randsmust_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jimeh/rands/randsmust"
|
||||
)
|
||||
|
||||
func ExampleBytes() {
|
||||
b := randsmust.Bytes(8)
|
||||
fmt.Printf("%+v\n", b) // => [6 99 106 54 163 188 28 152]
|
||||
}
|
||||
19
randsmust/bytes_test.go
Normal file
19
randsmust/bytes_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package randsmust
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Bytes(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
})
|
||||
}
|
||||
}
|
||||
23
randsmust/ints.go
Normal file
23
randsmust/ints.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package randsmust
|
||||
|
||||
import "github.com/jimeh/rands"
|
||||
|
||||
// Int generates a random int ranging between 0 and max.
|
||||
func Int(max int) int {
|
||||
r, err := rands.Int(max)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Int64 generates a random int64 ranging between 0 and max.
|
||||
func Int64(max int64) int64 {
|
||||
r, err := rands.Int64(max)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
17
randsmust/ints_example_test.go
Normal file
17
randsmust/ints_example_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package randsmust_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jimeh/rands/randsmust"
|
||||
)
|
||||
|
||||
func ExampleInt() {
|
||||
n := randsmust.Int(2147483647)
|
||||
fmt.Printf("%d\n", n) // => 1616989970
|
||||
}
|
||||
|
||||
func ExampleInt64() {
|
||||
n := randsmust.Int64(int64(9223372036854775807))
|
||||
fmt.Printf("%d\n", n) // => 1599573251306894157
|
||||
}
|
||||
144
randsmust/ints_test.go
Normal file
144
randsmust/ints_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package randsmust
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jimeh/rands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testIntCases = []struct {
|
||||
name string
|
||||
max int
|
||||
panicErrIs error
|
||||
panicStr string
|
||||
}{
|
||||
{
|
||||
name: "n=-2394345",
|
||||
max: -2394345,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-409600",
|
||||
max: -409600,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-1024",
|
||||
max: -1024,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-128",
|
||||
max: -128,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-32",
|
||||
max: -32,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-16",
|
||||
max: -16,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-8",
|
||||
max: -8,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-7",
|
||||
max: -7,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-2",
|
||||
max: -2,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-1",
|
||||
max: -1,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=0",
|
||||
max: 0,
|
||||
panicErrIs: rands.ErrInvalidMaxInt,
|
||||
panicStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{name: "n=1", max: 1},
|
||||
{name: "n=2", max: 2},
|
||||
{name: "n=7", max: 7},
|
||||
{name: "n=8", max: 8},
|
||||
{name: "n=16", max: 16},
|
||||
{name: "n=32", max: 32},
|
||||
{name: "n=128", max: 128},
|
||||
{name: "n=1024", max: 1024},
|
||||
{name: "n=409600", max: 409600},
|
||||
{name: "n=2394345", max: 2394345},
|
||||
}
|
||||
|
||||
func TestInt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range testIntCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got int
|
||||
p := recoverPanic(func() {
|
||||
got = Int(tt.max)
|
||||
})
|
||||
|
||||
if tt.panicErrIs == nil || tt.panicStr == "" {
|
||||
assert.GreaterOrEqual(t, got, 0)
|
||||
assert.LessOrEqual(t, got, tt.max)
|
||||
}
|
||||
|
||||
if tt.panicErrIs != nil {
|
||||
assert.ErrorIs(t, p.(error), tt.panicErrIs)
|
||||
}
|
||||
|
||||
if tt.panicStr != "" {
|
||||
assert.EqualError(t, p.(error), tt.panicStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range testIntCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got int64
|
||||
p := recoverPanic(func() {
|
||||
got = Int64(int64(tt.max))
|
||||
})
|
||||
|
||||
if tt.panicErrIs == nil || tt.panicStr == "" {
|
||||
assert.GreaterOrEqual(t, got, int64(0))
|
||||
assert.LessOrEqual(t, got, int64(tt.max))
|
||||
}
|
||||
|
||||
if tt.panicErrIs != nil {
|
||||
assert.ErrorIs(t, p.(error), tt.panicErrIs)
|
||||
}
|
||||
|
||||
if tt.panicStr != "" {
|
||||
assert.EqualError(t, p.(error), tt.panicStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
15
randsmust/randsmust.go
Normal file
15
randsmust/randsmust.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Package randsmust provides a suite of functions that use crypto/rand to
|
||||
// generate cryptographically secure random strings in various formats, as well
|
||||
// as ints and bytes.
|
||||
//
|
||||
// All functions which produce strings from a alphabet of characters uses
|
||||
// rand.Int() to ensure a uniform distribution of all possible values.
|
||||
//
|
||||
// randsmust is specifically intended as an alternative to rands for use in
|
||||
// tests. All functions return a single value, and panic in the event of an
|
||||
// error. This makes them easy to use when building structs in test cases that
|
||||
// need random data.
|
||||
//
|
||||
// For production code, make sure to use the rands package and check returned
|
||||
// errors.
|
||||
package randsmust
|
||||
30
randsmust/randsmust_test.go
Normal file
30
randsmust/randsmust_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package randsmust
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
n int
|
||||
}{
|
||||
{name: "n=0", n: 0},
|
||||
{name: "n=1", n: 1},
|
||||
{name: "n=2", n: 2},
|
||||
{name: "n=7", n: 7},
|
||||
{name: "n=8", n: 8},
|
||||
{name: "n=16", n: 16},
|
||||
{name: "n=32", n: 32},
|
||||
{name: "n=128", n: 128},
|
||||
{name: "n=1024", n: 1024},
|
||||
{name: "n=409600", n: 409600},
|
||||
{name: "n=2394345", n: 2394345},
|
||||
}
|
||||
|
||||
func recoverPanic(f func()) (p interface{}) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
p = r
|
||||
}
|
||||
}()
|
||||
|
||||
f()
|
||||
|
||||
return
|
||||
}
|
||||
192
randsmust/strings.go
Normal file
192
randsmust/strings.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package randsmust
|
||||
|
||||
import (
|
||||
"github.com/jimeh/rands"
|
||||
)
|
||||
|
||||
// Base64 generates a random base64 encoded string of n number of bytes.
|
||||
//
|
||||
// Length of the returned string is about one third greater than the value of n,
|
||||
// and it may contain characters A-Z, a-z, 0-9, "+", "/", and "=".
|
||||
func Base64(n int) string {
|
||||
r, err := rands.Base64(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Base64URL generates a URL-safe un-padded random base64 encoded string of n
|
||||
// number of bytes.
|
||||
//
|
||||
// Length of the returned string is about one third greater than the value of n,
|
||||
// and it may contain characters A-Z, a-z, 0-9, "-", and "_".
|
||||
func Base64URL(n int) string {
|
||||
r, err := rands.Base64URL(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Hex generates a random hexadecimal encoded string of n number of bytes.
|
||||
//
|
||||
// Length of the returned string is twice the value of n, and it may contain
|
||||
// characters 0-9 and a-f.
|
||||
func Hex(n int) string {
|
||||
r, err := rands.Hex(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Alphanumeric generates a random alphanumeric string of n length.
|
||||
//
|
||||
// The returned string may contain A-Z, a-z, and 0-9.
|
||||
func Alphanumeric(n int) string {
|
||||
r, err := rands.Alphanumeric(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Alphabetic generates a random alphabetic string of n length.
|
||||
//
|
||||
// The returned string may contain A-Z, and a-z.
|
||||
func Alphabetic(n int) string {
|
||||
r, err := rands.Alphabetic(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Numeric generates a random numeric string of n length.
|
||||
//
|
||||
// The returned string may contain 0-9.
|
||||
func Numeric(n int) string {
|
||||
r, err := rands.Numeric(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Upper generates a random uppercase alphabetic string of n length.
|
||||
//
|
||||
// The returned string may contain A-Z.
|
||||
func Upper(n int) string {
|
||||
r, err := rands.Upper(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// UpperNumeric generates a random uppercase alphanumeric string of n length.
|
||||
//
|
||||
// The returned string may contain A-Z and 0-9.
|
||||
func UpperNumeric(n int) string {
|
||||
r, err := rands.UpperNumeric(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Lower generates a random lowercase alphabetic string of n length.
|
||||
//
|
||||
// The returned string may contain a-z.
|
||||
func Lower(n int) string {
|
||||
r, err := rands.Lower(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// LowerNumeric generates a random lowercase alphanumeric string of n length.
|
||||
//
|
||||
// The returned string may contain A-Z and 0-9.
|
||||
func LowerNumeric(n int) string {
|
||||
r, err := rands.LowerNumeric(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// String generates a random string of n length using the given ASCII alphabet.
|
||||
//
|
||||
// The specified alphabet determines what characters are used in the returned
|
||||
// random string. The alphabet can only contain ASCII characters, use
|
||||
// UnicodeString() if you need a alphabet with Unicode characters.
|
||||
func String(n int, alphabet string) string {
|
||||
r, err := rands.String(n, alphabet)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// UnicodeString generates a random string of n length using the given Unicode
|
||||
// alphabet.
|
||||
//
|
||||
// The specified alphabet determines what characters are used in the returned
|
||||
// random string. The length of the returned string will be n or greater
|
||||
// depending on the byte-length of characters which were randomly selected from
|
||||
// the alphabet.
|
||||
func UnicodeString(n int, alphabet []rune) string {
|
||||
r, err := rands.UnicodeString(n, alphabet)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// DNSLabel returns a random string of n length in a DNS label compliant format
|
||||
// as defined in RFC 1035, section 2.3.1.
|
||||
//
|
||||
// It also adheres to RFC 5891, section 4.2.3.1.
|
||||
//
|
||||
// In summary, the generated random string will:
|
||||
//
|
||||
// - be between 1 and 63 characters in length, other n values returns a error
|
||||
// - first character will be one of a-z
|
||||
// - last character will be one of a-z or 0-9
|
||||
// - in-between first and last characters consist of a-z, 0-9, or "-"
|
||||
// - potentially contain two or more consecutive "-", except the 3rd and 4th
|
||||
// characters, as that would violate RFC 5891, section 4.2.3.1.
|
||||
func DNSLabel(n int) string {
|
||||
r, err := rands.DNSLabel(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// UUID returns a random UUID v4 in string format as defined by RFC 4122,
|
||||
// section 4.4.
|
||||
func UUID() string {
|
||||
r, err := rands.UUID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
77
randsmust/strings_example_test.go
Normal file
77
randsmust/strings_example_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package randsmust_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jimeh/rands/randsmust"
|
||||
)
|
||||
|
||||
func ExampleBase64() {
|
||||
s := randsmust.Base64(16)
|
||||
fmt.Println(s) // => rGnZOxJunCd5h+piBpOfDA==
|
||||
}
|
||||
|
||||
func ExampleBase64URL() {
|
||||
s := randsmust.Base64URL(16)
|
||||
fmt.Println(s) // => NlXKmutou2knLU8q7Hlp5Q
|
||||
}
|
||||
|
||||
func ExampleHex() {
|
||||
s := randsmust.Hex(16)
|
||||
fmt.Println(s) // => 1013ec67a802be177d3e37f46951e97f
|
||||
}
|
||||
|
||||
func ExampleAlphanumeric() {
|
||||
s := randsmust.Alphanumeric(16)
|
||||
fmt.Println(s) // => mjT119HdPslVfvUE
|
||||
}
|
||||
|
||||
func ExampleAlphabetic() {
|
||||
s := randsmust.Alphabetic(16)
|
||||
fmt.Println(s) // => RLaRaTVqcrxvNkiz
|
||||
}
|
||||
|
||||
func ExampleUpper() {
|
||||
s := randsmust.Upper(16)
|
||||
fmt.Println(s) // => CANJDLMHANPQNXUE
|
||||
}
|
||||
|
||||
func ExampleUpperNumeric() {
|
||||
s := randsmust.UpperNumeric(16)
|
||||
fmt.Println(s) // => EERZHC96KOIRU9DM
|
||||
}
|
||||
|
||||
func ExampleLower() {
|
||||
s := randsmust.Lower(16)
|
||||
fmt.Println(s) // => aoybqdwigyezucjy
|
||||
}
|
||||
|
||||
func ExampleLowerNumeric() {
|
||||
s := randsmust.LowerNumeric(16)
|
||||
fmt.Println(s) // => hs8l2l0750med3g2
|
||||
}
|
||||
|
||||
func ExampleNumeric() {
|
||||
s := randsmust.Numeric(16)
|
||||
fmt.Println(s) // => 3126402104379869
|
||||
}
|
||||
|
||||
func ExampleString() {
|
||||
s := randsmust.String(16, "abcdefABCDEF")
|
||||
fmt.Println(s) // => cbdCAbABECaADcaB
|
||||
}
|
||||
|
||||
func ExampleUnicodeString() {
|
||||
s := randsmust.UnicodeString(16, []rune("九七二人入八力十下三千上口土夕大"))
|
||||
fmt.Println(s) // => 下夕七下千千力入八三力夕千三土七
|
||||
}
|
||||
|
||||
func ExampleDNSLabel() {
|
||||
s := randsmust.DNSLabel(16)
|
||||
fmt.Println(s) // => urqkt-remuwz5083
|
||||
}
|
||||
|
||||
func ExampleUUID() {
|
||||
s := randsmust.UUID()
|
||||
fmt.Println(s) // => 5baa35a6-9a46-49b4-91d0-9530173e118d
|
||||
}
|
||||
558
randsmust/strings_test.go
Normal file
558
randsmust/strings_test.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package randsmust
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jimeh/rands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "0123456789abcdef"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Hex(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n*2)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
|
||||
"0123456789+/="
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Base64(tt.n)
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(got)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, b, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64URL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
|
||||
"0123456789-_"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Base64URL(tt.n)
|
||||
|
||||
b, err := base64.RawURLEncoding.DecodeString(got)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, b, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlphanumeric(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Alphanumeric(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlphabetic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Alphabetic(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumeric(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "0123456789"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Numeric(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpper(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Upper(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpperNumeric(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := UpperNumeric(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLower(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Lower(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLowerNumeric(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowed := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := LowerNumeric(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
assertAllowedChars(t, allowed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var stringTestCases = []struct {
|
||||
name string
|
||||
n int
|
||||
alphabet string
|
||||
panicErrIs error
|
||||
panicStr string
|
||||
}{
|
||||
{
|
||||
name: "greek",
|
||||
n: 32,
|
||||
alphabet: "αβγδεζηθικλμνξοπρστυφχψωςΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΩ" +
|
||||
"άίόύώέϊϋΐΰΆΈΌΏΎΊ",
|
||||
panicErrIs: rands.ErrNonASCIIAlphabet,
|
||||
panicStr: "rands: alphabet contains non-ASCII characters",
|
||||
},
|
||||
{
|
||||
name: "chinese",
|
||||
n: 32,
|
||||
alphabet: "的一是不了人我在有他这为之大来以个中上们到说国和地也子" +
|
||||
"时道出而要于就下得可你年生",
|
||||
panicErrIs: rands.ErrNonASCIIAlphabet,
|
||||
panicStr: "rands: alphabet contains non-ASCII characters",
|
||||
},
|
||||
{
|
||||
name: "japanese",
|
||||
n: 32,
|
||||
alphabet: "一九七二人入八力十下三千上口土夕大女子小山川五天中六円" +
|
||||
"手文日月木水火犬王正出本右四",
|
||||
panicErrIs: rands.ErrNonASCIIAlphabet,
|
||||
panicStr: "rands: alphabet contains non-ASCII characters",
|
||||
},
|
||||
{
|
||||
name: "n=0",
|
||||
n: 0,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=1",
|
||||
n: 1,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=2",
|
||||
n: 2,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=7",
|
||||
n: 7,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=8",
|
||||
n: 8,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=16",
|
||||
n: 16,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=32",
|
||||
n: 32,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=128",
|
||||
n: 128,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=1024",
|
||||
n: 1024,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=409600",
|
||||
n: 409600,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "n=2394345",
|
||||
n: 2394345,
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-",
|
||||
},
|
||||
{
|
||||
name: "uppercase",
|
||||
n: 16,
|
||||
alphabet: "ABCDEFGHJKMNPRSTUVWXYZ",
|
||||
},
|
||||
{
|
||||
name: "lowercase",
|
||||
n: 16,
|
||||
alphabet: "abcdefghjkmnprstuvwxyz",
|
||||
},
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range stringTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got string
|
||||
p := recoverPanic(func() {
|
||||
got = String(tt.n, tt.alphabet)
|
||||
})
|
||||
|
||||
if tt.panicErrIs == nil || tt.panicStr == "" {
|
||||
assert.Len(t, []rune(got), tt.n)
|
||||
assertAllowedChars(t, tt.alphabet, got)
|
||||
}
|
||||
|
||||
if tt.panicErrIs != nil {
|
||||
assert.ErrorIs(t, p.(error), tt.panicErrIs)
|
||||
}
|
||||
|
||||
if tt.panicStr != "" {
|
||||
assert.EqualError(t, p.(error), tt.panicStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var unicodeStringTestCases = []struct {
|
||||
name string
|
||||
n int
|
||||
alphabet string
|
||||
}{
|
||||
{
|
||||
name: "n=0",
|
||||
n: 0,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=1",
|
||||
n: 1,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=2",
|
||||
n: 2,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=7",
|
||||
n: 7,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=8",
|
||||
n: 8,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=16",
|
||||
n: 16,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=32",
|
||||
n: 32,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=128",
|
||||
n: 128,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=1024",
|
||||
n: 1024,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=409600",
|
||||
n: 409600,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "n=2394345",
|
||||
n: 2394345,
|
||||
alphabet: "αβγδεζηθικλμν时道出而要于就下得可你年" +
|
||||
"手文日月木水火犬王正出本右",
|
||||
},
|
||||
{
|
||||
name: "latin",
|
||||
n: 32,
|
||||
alphabet: "ABCDEFGHJKMNPRSTUVWXYZabcdefghjkmnprstuvwxyz",
|
||||
},
|
||||
{
|
||||
name: "greek",
|
||||
n: 32,
|
||||
alphabet: "αβγδεζηθικλμνξοπρστυφχψωςΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΩ" +
|
||||
"άίόύώέϊϋΐΰΆΈΌΏΎΊ",
|
||||
},
|
||||
{
|
||||
name: "chinese",
|
||||
n: 32,
|
||||
alphabet: "的一是不了人我在有他这为之大来以个中上们到说国和地也子" +
|
||||
"时道出而要于就下得可你年生",
|
||||
},
|
||||
{
|
||||
name: "japanese",
|
||||
n: 32,
|
||||
alphabet: "一九七二人入八力十下三千上口土夕大女子小山川五天中六円" +
|
||||
"手文日月木水火犬王正出本右四",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnicodeString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range unicodeStringTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := UnicodeString(tt.n, []rune(tt.alphabet))
|
||||
|
||||
assert.Len(t, []rune(got), tt.n)
|
||||
assertAllowedChars(t, tt.alphabet, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var dnsLabelTestCases = []struct {
|
||||
name string
|
||||
n int
|
||||
panicErrIs error
|
||||
panicStr string
|
||||
}{
|
||||
{
|
||||
name: "n=-128",
|
||||
n: -128,
|
||||
panicErrIs: rands.ErrDNSLabelLength,
|
||||
panicStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{
|
||||
name: "n=0",
|
||||
n: 0,
|
||||
panicErrIs: rands.ErrDNSLabelLength,
|
||||
panicStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{name: "n=1", n: 1},
|
||||
{name: "n=2", n: 2},
|
||||
{name: "n=3", n: 3},
|
||||
{name: "n=4", n: 4},
|
||||
{name: "n=5", n: 5},
|
||||
{name: "n=6", n: 6},
|
||||
{name: "n=7", n: 7},
|
||||
{name: "n=8", n: 8},
|
||||
{name: "n=16", n: 16},
|
||||
{name: "n=32", n: 32},
|
||||
{name: "n=63", n: 63},
|
||||
{
|
||||
name: "n=64",
|
||||
n: 64,
|
||||
panicErrIs: rands.ErrDNSLabelLength,
|
||||
panicStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{
|
||||
name: "n=128",
|
||||
n: 128,
|
||||
panicErrIs: rands.ErrDNSLabelLength,
|
||||
panicStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDNSLabel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range dnsLabelTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// generate lots of labels to increase the chances of catching any
|
||||
// obscure bugs
|
||||
for i := 0; i < 10000; i++ {
|
||||
var got string
|
||||
p := recoverPanic(func() {
|
||||
got = DNSLabel(tt.n)
|
||||
})
|
||||
|
||||
if tt.panicErrIs == nil || tt.panicStr == "" {
|
||||
require.Len(t, got, tt.n)
|
||||
asserDNSLabel(t, got)
|
||||
}
|
||||
|
||||
if tt.panicErrIs != nil {
|
||||
require.ErrorIs(t, p.(error), tt.panicErrIs)
|
||||
}
|
||||
|
||||
if tt.panicStr != "" {
|
||||
require.EqualError(t, p.(error), tt.panicStr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := regexp.MustCompile(
|
||||
`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`,
|
||||
)
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
got := UUID()
|
||||
require.Regexp(t, m, got)
|
||||
|
||||
raw := strings.ReplaceAll(got, "-", "")
|
||||
b := make([]byte, 16)
|
||||
_, err := hex.Decode(b, []byte(raw))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 4, int(b[6]>>4), "version is not 4")
|
||||
require.Equal(t, byte(0x80), b[8]&0xc0,
|
||||
"variant is not RFC 4122",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
var (
|
||||
dnsLabelHeadRx = regexp.MustCompile(`^[a-z]$`)
|
||||
dnsLabelBodyRx = regexp.MustCompile(`^[a-z0-9-]+$`)
|
||||
dnsLabelTailRx = regexp.MustCompile(`^[a-z0-9]$`)
|
||||
)
|
||||
|
||||
func asserDNSLabel(t *testing.T, label string) {
|
||||
require.LessOrEqualf(t, len(label), 63,
|
||||
`DNS label "%s" is longer than 63 characters`, label,
|
||||
)
|
||||
|
||||
require.GreaterOrEqualf(t, len(label), 1,
|
||||
`DNS label "%s" is shorter than 1 character`, label,
|
||||
)
|
||||
|
||||
if len(label) >= 1 {
|
||||
require.Regexpf(t, dnsLabelHeadRx, string(label[0]),
|
||||
`DNS label "%s" must start with a-z`, label,
|
||||
)
|
||||
}
|
||||
if len(label) >= 2 {
|
||||
require.Regexpf(t, dnsLabelTailRx, string(label[len(label)-1]),
|
||||
`DNS label "%s" must end with a-z0-9`, label,
|
||||
)
|
||||
}
|
||||
if len(label) >= 3 {
|
||||
require.Regexpf(t, dnsLabelBodyRx, label[1:len(label)-1],
|
||||
`DNS label "%s" body must only contain a-z0-9-`, label)
|
||||
}
|
||||
if len(label) >= 4 {
|
||||
require.NotEqualf(t, "--", label[2:4],
|
||||
`DNS label "%s" cannot contain "--" as 3rd and 4th char`, label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func assertAllowedChars(t *testing.T, allowed string, s string) {
|
||||
invalid := ""
|
||||
for _, c := range s {
|
||||
if !strings.Contains(allowed, string(c)) &&
|
||||
!strings.Contains(invalid, string(c)) {
|
||||
invalid += string(c)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Truef(
|
||||
t, len(invalid) == 0, "string contains invalid chars: %s", invalid,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user