mirror of
https://github.com/jimeh/rands.git
synced 2026-02-19 03:16:39 +00:00
Merge pull request #1 from jimeh/initial-implementation
feat(rands): initial implementation
This commit is contained in:
139
.github/workflows/ci.yml
vendored
Normal file
139
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
name: CI
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: v1.35
|
||||
env:
|
||||
VERBOSE: "true"
|
||||
|
||||
tidy:
|
||||
name: Tidy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Check if mods are tidy
|
||||
run: make check-tidy
|
||||
|
||||
benchmark:
|
||||
name: Benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref != 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Run benchmarks
|
||||
run: make bench | tee output.raw
|
||||
- name: Fix benchmark names
|
||||
run: >-
|
||||
perl -pe 's/^(Benchmark.+?)\/(\S+)(-\d+)(\s+)/\1__\2\4/' output.raw |
|
||||
tr '-' '_' | tee output.txt
|
||||
- name: Announce benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
tool: "go"
|
||||
output-file-path: output.txt
|
||||
fail-on-alert: true
|
||||
comment-on-alert: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: false
|
||||
|
||||
cov:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Publish coverage
|
||||
uses: paambaati/codeclimate-action@v2.7.4
|
||||
env:
|
||||
VERBOSE: "true"
|
||||
GOMAXPROCS: 4
|
||||
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
||||
with:
|
||||
coverageCommand: make cov
|
||||
prefix: github.com/${{ github.repository }}
|
||||
coverageLocations: |
|
||||
${{ github.workspace }}/coverage.out:gocov
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Run tests
|
||||
run: make test
|
||||
env:
|
||||
VERBOSE: "true"
|
||||
|
||||
benchmark-store:
|
||||
name: Store benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Run benchmarks
|
||||
run: make bench | tee output.raw
|
||||
- name: Fix benchmark names
|
||||
run: >-
|
||||
perl -pe 's/^(Benchmark.+?)\/(\S+)(-\d+)(\s+)/\1__\2\4/' output.raw |
|
||||
tr '-' '_' | tee output.txt
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
tool: "go"
|
||||
output-file-path: output.txt
|
||||
github-token: ${{ secrets.GH_PUSH_TOKEN }}
|
||||
comment-on-alert: true
|
||||
auto-push: true
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/*.tidy-check
|
||||
/bin/*
|
||||
/coverage.out
|
||||
/output.txt
|
||||
78
.golangci.yml
Normal file
78
.golangci.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
linters-settings:
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 150
|
||||
gocyclo:
|
||||
min-complexity: 20
|
||||
golint:
|
||||
min-confidence: 0
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable-all: true
|
||||
lll:
|
||||
line-length: 80
|
||||
tab-width: 4
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
- dupl
|
||||
- errcheck
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- goerr113
|
||||
- goimports
|
||||
- golint
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nlreturn
|
||||
- noctx
|
||||
- nolintlint
|
||||
- scopelint
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
include:
|
||||
# - EXC0002 # disable excluding of issues about comments from golint
|
||||
exclude:
|
||||
- Using the variable on range scope `tt` in function literal
|
||||
- Using the variable on range scope `tc` in function literal
|
||||
exclude-rules:
|
||||
- path: "_test\\.go"
|
||||
linters:
|
||||
- funlen
|
||||
- dupl
|
||||
- source: "^//go:generate "
|
||||
linters:
|
||||
- lll
|
||||
- source: "`json:"
|
||||
linters:
|
||||
- lll
|
||||
|
||||
run:
|
||||
timeout: 2m
|
||||
allow-parallel-runners: true
|
||||
modules-download-mode: readonly
|
||||
20
LICENSE
Normal file
20
LICENSE
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Jim Myhrberg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
173
Makefile
Normal file
173
Makefile
Normal file
@@ -0,0 +1,173 @@
|
||||
GOMODNAME := $(shell grep 'module' go.mod | sed -e 's/^module //')
|
||||
SOURCES := $(shell find . -name "*.go" -or -name "go.mod" -or -name "go.sum" \
|
||||
-or -name "Makefile")
|
||||
|
||||
# Verbose output
|
||||
ifdef VERBOSE
|
||||
V = -v
|
||||
endif
|
||||
|
||||
#
|
||||
# Environment
|
||||
#
|
||||
|
||||
BINDIR := bin
|
||||
TOOLDIR := $(BINDIR)/tools
|
||||
|
||||
# Global environment variables for all targets
|
||||
SHELL ?= /bin/bash
|
||||
SHELL := env \
|
||||
GO111MODULE=on \
|
||||
GOBIN=$(CURDIR)/$(TOOLDIR) \
|
||||
CGO_ENABLED=1 \
|
||||
PATH='$(CURDIR)/$(BINDIR):$(CURDIR)/$(TOOLDIR):$(PATH)' \
|
||||
$(SHELL)
|
||||
|
||||
#
|
||||
# Defaults
|
||||
#
|
||||
|
||||
# Default target
|
||||
.DEFAULT_GOAL := test
|
||||
|
||||
#
|
||||
# Tools
|
||||
#
|
||||
|
||||
TOOLS += $(TOOLDIR)/gobin
|
||||
gobin: $(TOOLDIR)/gobin
|
||||
$(TOOLDIR)/gobin:
|
||||
GO111MODULE=off go get -u github.com/myitcv/gobin
|
||||
|
||||
# external tool
|
||||
define tool # 1: binary-name, 2: go-import-path
|
||||
TOOLS += $(TOOLDIR)/$(1)
|
||||
|
||||
.PHONY: $(1)
|
||||
$(1): $(TOOLDIR)/$(1)
|
||||
|
||||
$(TOOLDIR)/$(1): $(TOOLDIR)/gobin Makefile
|
||||
gobin $(V) "$(2)"
|
||||
endef
|
||||
|
||||
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc))
|
||||
$(eval $(call tool,gofumports,mvdan.cc/gofumpt/gofumports))
|
||||
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.35))
|
||||
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod))
|
||||
|
||||
.PHONY: tools
|
||||
tools: $(TOOLS)
|
||||
|
||||
#
|
||||
# Development
|
||||
#
|
||||
|
||||
TEST ?= $$(go list ./... | grep -v 'vendor')
|
||||
BENCH ?= .
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(TOOLS)
|
||||
rm -f ./coverage.out ./go.mod.tidy-check ./go.sum.tidy-check
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test $(V) -count=1 $(TESTARGS) $(TEST)
|
||||
|
||||
.PHONY: test-deps
|
||||
test-deps:
|
||||
go test all
|
||||
|
||||
.PHONY: lint
|
||||
lint: golangci-lint
|
||||
GOGC=off golangci-lint $(V) run
|
||||
|
||||
.PHONY: format
|
||||
format: gofumports
|
||||
gofumports -w .
|
||||
|
||||
.SILENT: bench
|
||||
.PHONY: bench
|
||||
bench:
|
||||
go test $(V) -count=1 -bench=$(BENCH) $(TESTARGS) $(TEST)
|
||||
|
||||
#
|
||||
# Coverage
|
||||
#
|
||||
|
||||
.PHONY: cov
|
||||
cov: coverage.out
|
||||
|
||||
.PHONY: cov-html
|
||||
cov-html: coverage.out
|
||||
go tool cover -html=./coverage.out
|
||||
|
||||
.PHONY: cov-func
|
||||
cov-func: coverage.out
|
||||
go tool cover -func=./coverage.out
|
||||
|
||||
coverage.out: $(SOURCES)
|
||||
go test $(V) -covermode=count -coverprofile=./coverage.out ./...
|
||||
|
||||
#
|
||||
# Dependencies
|
||||
#
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
$(info Downloading dependencies)
|
||||
go mod download
|
||||
|
||||
.PHONY: deps-update
|
||||
deps-update:
|
||||
$(info Downloading dependencies)
|
||||
go get -u ./...
|
||||
|
||||
.PHONY: deps-analyze
|
||||
deps-analyze: gomod
|
||||
gomod analyze
|
||||
|
||||
.PHONY: tidy
|
||||
tidy:
|
||||
go mod tidy $(V)
|
||||
|
||||
.PHONY: verify
|
||||
verify:
|
||||
go mod verify
|
||||
|
||||
.SILENT: check-tidy
|
||||
.PHONY: check-tidy
|
||||
check-tidy:
|
||||
cp go.mod go.mod.tidy-check
|
||||
cp go.sum go.sum.tidy-check
|
||||
go mod tidy
|
||||
( \
|
||||
diff go.mod go.mod.tidy-check && \
|
||||
diff go.sum go.sum.tidy-check && \
|
||||
rm -f go.mod go.sum && \
|
||||
mv go.mod.tidy-check go.mod && \
|
||||
mv go.sum.tidy-check go.sum \
|
||||
) || ( \
|
||||
rm -f go.mod go.sum && \
|
||||
mv go.mod.tidy-check go.mod && \
|
||||
mv go.sum.tidy-check go.sum; \
|
||||
exit 1 \
|
||||
)
|
||||
|
||||
#
|
||||
# Documentation
|
||||
#
|
||||
|
||||
# Serve docs
|
||||
.PHONY: docs
|
||||
docs: godoc
|
||||
$(info serviing docs on http://127.0.0.1:6060/pkg/$(GOMODNAME)/)
|
||||
@godoc -http=127.0.0.1:6060
|
||||
|
||||
#
|
||||
# Release
|
||||
#
|
||||
|
||||
.PHONY: new-version
|
||||
new-version:
|
||||
npx standard-version
|
||||
82
README.md
82
README.md
@@ -1 +1,81 @@
|
||||
# rands
|
||||
<h1 align="center">
|
||||
rands
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>
|
||||
Go package providing a suite of functions that use `crypto/rand` to generate
|
||||
cryptographically secure random strings in various formats, as well as ints
|
||||
and bytes.
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://pkg.go.dev/github.com/jimeh/rands">
|
||||
<img src="https://img.shields.io/badge/%E2%80%8B-reference-387b97.svg?logo=go&logoColor=white"
|
||||
alt="Go Reference">
|
||||
</a>
|
||||
<a href="https://github.com/jimeh/rands/releases">
|
||||
<img src="https://img.shields.io/github/v/tag/jimeh/rands?label=release" alt="GitHub tag (latest SemVer)">
|
||||
</a>
|
||||
<a href="https://github.com/jimeh/rands/actions">
|
||||
<img src="https://img.shields.io/github/workflow/status/jimeh/rands/CI.svg?logo=github" alt="Actions Status">
|
||||
</a>
|
||||
<a href="https://codeclimate.com/github/jimeh/rands">
|
||||
<img src="https://img.shields.io/codeclimate/coverage/jimeh/rands.svg?logo=code%20climate" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://github.com/jimeh/rands/issues">
|
||||
<img src="https://img.shields.io/github/issues-raw/jimeh/rands.svg?style=flat&logo=github&logoColor=white"
|
||||
alt="GitHub issues">
|
||||
</a>
|
||||
<a href="https://github.com/jimeh/rands/pulls">
|
||||
<img src="https://img.shields.io/github/issues-pr-raw/jimeh/rands.svg?style=flat&logo=github&logoColor=white" alt="GitHub pull requests">
|
||||
</a>
|
||||
<a href="https://github.com/jimeh/rands/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/jimeh/rands.svg?style=flat" alt="License Status">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
```go
|
||||
s, _ := rands.Base64(16) // => CYxqEdUB1Rzno3SyZu2g/g==
|
||||
s, _ := rands.Base64URL(16) // => zlqw9aFqcFggbk2asn3_aQ
|
||||
s, _ := rands.Hex(16) // => 956e2ec9e7f19ddd58bb935826926531
|
||||
s, _ := rands.Alphanumeric(16) // => Fvk1PkrmG5crgOjT
|
||||
s, _ := rands.Alphabetic(16) // => XEJIzcZufHkuUmRM
|
||||
s, _ := rands.Upper(16) // => UMAGAFPPNDRGLUPZ
|
||||
s, _ := rands.UpperNumeric(16) // => DF0CQS0TK9CPUO3E
|
||||
s, _ := rands.Lower(16) // => ocsmggykzrxzfwgt
|
||||
s, _ := rands.LowerNumeric(16) // => rwlv7a1p7klqffs5
|
||||
s, _ := rands.Numeric(16) // => 9403373143598295
|
||||
|
||||
s, _ := rands.String(16, "abcdefABCDEF") // => adCDCaDEdeffeDeb
|
||||
s, _ := rands.UnicodeString(16, []rune("九七二人入八力十下三千上口土夕大")) // => 下下口九力下土夕下土八上二夕大三
|
||||
|
||||
s, _ := rands.DNSLabel(16) // => z0ij9o8qkbs0ru-h
|
||||
|
||||
n, _ := rands.Int(2147483647) // => 1334400235
|
||||
n, _ := rands.Int64(int64(9223372036854775807)) // => 8256935979116161233
|
||||
|
||||
b, _ := rands.Bytes(8) // => [0 220 137 243 135 204 34 63]
|
||||
```
|
||||
|
||||
## Import
|
||||
|
||||
```
|
||||
import "github.com/jimeh/rands"
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Please see the
|
||||
[Go Reference](https://pkg.go.dev/github.com/jimeh/rands#section-documentation)
|
||||
for documentation and examples.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Benchmark reports and graphs are available here:
|
||||
https://jimeh.me/rands/dev/bench/
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/jimeh/rands/blob/master/LICENSE)
|
||||
|
||||
14
bytes.go
Normal file
14
bytes.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package rands
|
||||
|
||||
import "crypto/rand"
|
||||
|
||||
// Bytes generates a byte slice of n number of random bytes.
|
||||
func Bytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
12
bytes_example_test.go
Normal file
12
bytes_example_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package rands_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jimeh/rands"
|
||||
)
|
||||
|
||||
func ExampleBytes() {
|
||||
b, _ := rands.Bytes(8)
|
||||
fmt.Printf("%+v\n", b) // => [0 220 137 243 135 204 34 63]
|
||||
}
|
||||
27
bytes_test.go
Normal file
27
bytes_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package rands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBytes(t *testing.T) {
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, _ := Bytes(tt.n)
|
||||
|
||||
assert.Len(t, got, tt.n)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBytes(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Bytes(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/jimeh/rands
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/stretchr/testify v1.7.0
|
||||
11
go.sum
Normal file
11
go.sum
Normal file
@@ -0,0 +1,11 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
37
ints.go
Normal file
37
ints.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package rands
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var errInvalidMaxInt = fmt.Errorf("%w: max cannot be less than 1", errBase)
|
||||
|
||||
// Int generates a random int ranging between 0 and max.
|
||||
func Int(max int) (int, error) {
|
||||
if max < 1 {
|
||||
return 0, errInvalidMaxInt
|
||||
}
|
||||
|
||||
r, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(r.Int64()), nil
|
||||
}
|
||||
|
||||
// Int64 generates a random int64 ranging between 0 and max.
|
||||
func Int64(max int64) (int64, error) {
|
||||
if max < 1 {
|
||||
return 0, errInvalidMaxInt
|
||||
}
|
||||
|
||||
r, err := rand.Int(rand.Reader, big.NewInt(max))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return r.Int64(), nil
|
||||
}
|
||||
17
ints_example_test.go
Normal file
17
ints_example_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package rands_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jimeh/rands"
|
||||
)
|
||||
|
||||
func ExampleInt() {
|
||||
n, _ := rands.Int(2147483647)
|
||||
fmt.Printf("%d\n", n) // => 1334400235
|
||||
}
|
||||
|
||||
func ExampleInt64() {
|
||||
n, _ := rands.Int64(int64(9223372036854775807))
|
||||
fmt.Printf("%d\n", n) // => 8256935979116161233
|
||||
}
|
||||
154
ints_test.go
Normal file
154
ints_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package rands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testIntCases = []struct {
|
||||
name string
|
||||
max int
|
||||
errIs error
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
name: "n=-2394345",
|
||||
max: -2394345,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-409600",
|
||||
max: -409600,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-1024",
|
||||
max: -1024,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-128",
|
||||
max: -128,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-32",
|
||||
max: -32,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-16",
|
||||
max: -16,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-8",
|
||||
max: -8,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-7",
|
||||
max: -7,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-2",
|
||||
max: -2,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=-1",
|
||||
max: -1,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "rands: max cannot be less than 1",
|
||||
},
|
||||
{
|
||||
name: "n=0",
|
||||
max: 0,
|
||||
errIs: errInvalidMaxInt,
|
||||
errStr: "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) {
|
||||
for _, tt := range testIntCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Int(tt.max)
|
||||
|
||||
if tt.errIs == nil || tt.errStr == "" {
|
||||
assert.GreaterOrEqual(t, got, 0)
|
||||
assert.LessOrEqual(t, got, tt.max)
|
||||
}
|
||||
|
||||
if tt.errIs != nil {
|
||||
assert.True(t, errors.Is(err, errInvalidMaxInt))
|
||||
}
|
||||
|
||||
if tt.errStr != "" {
|
||||
assert.EqualError(t, err, tt.errStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt(b *testing.B) {
|
||||
for _, tt := range testIntCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Int(tt.max)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64(t *testing.T) {
|
||||
for _, tt := range testIntCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Int64(int64(tt.max))
|
||||
|
||||
if tt.errIs == nil || tt.errStr == "" {
|
||||
assert.GreaterOrEqual(t, got, int64(0))
|
||||
assert.LessOrEqual(t, got, int64(tt.max))
|
||||
}
|
||||
|
||||
if tt.errIs != nil {
|
||||
assert.True(t, errors.Is(err, errInvalidMaxInt))
|
||||
}
|
||||
|
||||
if tt.errStr != "" {
|
||||
assert.EqualError(t, err, tt.errStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt64(b *testing.B) {
|
||||
for _, tt := range testIntCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Int64(int64(tt.max))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
11
rands.go
Normal file
11
rands.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Package rands 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.
|
||||
package rands
|
||||
|
||||
import "errors"
|
||||
|
||||
var errBase = errors.New("rands")
|
||||
18
rands_test.go
Normal file
18
rands_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package rands
|
||||
|
||||
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},
|
||||
}
|
||||
246
strings.go
Normal file
246
strings.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package rands
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
lowerChars = "abcdefghijklmnopqrstuvwxyz"
|
||||
numericChars = "0123456789"
|
||||
lowerNumericChars = lowerChars + numericChars
|
||||
upperNumericChars = upperChars + numericChars
|
||||
alphabeticChars = upperChars + lowerChars
|
||||
alphanumericChars = alphabeticChars + numericChars
|
||||
dnsLabelChars = lowerNumericChars + "-"
|
||||
)
|
||||
|
||||
var (
|
||||
errNonASCIIAlphabet = fmt.Errorf(
|
||||
"%w: alphabet contains non-ASCII characters", errBase,
|
||||
)
|
||||
|
||||
errDNSLabelLength = fmt.Errorf(
|
||||
"%w: DNS labels must be between 1 and 63 characters in length", errBase,
|
||||
)
|
||||
)
|
||||
|
||||
// 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, error) {
|
||||
b, err := Bytes(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
b, err := Bytes(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
b, err := Bytes(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
return String(n, alphanumericChars)
|
||||
}
|
||||
|
||||
// Alphabetic generates a random alphabetic string of n length.
|
||||
//
|
||||
// The returned string may contain A-Z, and a-z.
|
||||
func Alphabetic(n int) (string, error) {
|
||||
return String(n, alphabeticChars)
|
||||
}
|
||||
|
||||
// Numeric generates a random numeric string of n length.
|
||||
//
|
||||
// The returned string may contain 0-9.
|
||||
func Numeric(n int) (string, error) {
|
||||
return String(n, numericChars)
|
||||
}
|
||||
|
||||
// Upper generates a random uppercase alphabetic string of n length.
|
||||
//
|
||||
// The returned string may contain A-Z.
|
||||
func Upper(n int) (string, error) {
|
||||
return String(n, upperChars)
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
return String(n, upperNumericChars)
|
||||
}
|
||||
|
||||
// Lower generates a random lowercase alphabetic string of n length.
|
||||
//
|
||||
// The returned string may contain a-z.
|
||||
func Lower(n int) (string, error) {
|
||||
return String(n, lowerChars)
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
return String(n, lowerNumericChars)
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
if !isASCII(alphabet) {
|
||||
return "", errNonASCIIAlphabet
|
||||
}
|
||||
|
||||
l := big.NewInt(int64(len(alphabet)))
|
||||
b := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
index, err := rand.Int(rand.Reader, l)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b[i] = alphabet[index.Int64()]
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
l := big.NewInt(int64(len(alphabet)))
|
||||
b := make([]rune, n)
|
||||
for i := 0; i < n; i++ {
|
||||
index, err := rand.Int(rand.Reader, l)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b[i] = alphabet[index.Int64()]
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// DNSLabel returns a random string of n length in a DNS label compliant format
|
||||
// as defined in RFC 1035, section 2.3.1, Preferred name syntax:
|
||||
// https://tools.ietf.org/html/rfc1035#section-2.3.1
|
||||
//
|
||||
// It also adheres to RFC 5891, section 4.2.3.1, Hyphen Restrictions:
|
||||
// https://tools.ietf.org/html/rfc5891#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.
|
||||
func DNSLabel(n int) (string, error) {
|
||||
switch {
|
||||
case n < 1 || n > 63:
|
||||
return "", errDNSLabelLength
|
||||
case n == 1:
|
||||
return String(1, lowerChars)
|
||||
default:
|
||||
// First character of a DNS label allows only a-z characters.
|
||||
head, err := String(1, lowerChars)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Last character of a DNS label allows only a-z and 0-9 characters.
|
||||
tail, err := String(1, lowerNumericChars)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if n < 3 {
|
||||
return head + tail, nil
|
||||
}
|
||||
|
||||
// The middle of a DNS label allows only a-z, 0-9, and "-" characters.
|
||||
bodyLen := n - 2
|
||||
body := make([]byte, bodyLen)
|
||||
var last byte
|
||||
var l *big.Int
|
||||
|
||||
for i := 0; i < bodyLen; i++ {
|
||||
// Prevent two consecutive hyphens characters in positions 3 and 4,
|
||||
// in accordance RFC 5891, section 4.2.3.1, Hyphen Restrictions:
|
||||
// https://tools.ietf.org/html/rfc5891#section-4.2.3.1
|
||||
if i == 2 && last == byte(45) {
|
||||
l = big.NewInt(int64(len(lowerNumericChars)))
|
||||
} else {
|
||||
l = big.NewInt(int64(len(dnsLabelChars)))
|
||||
}
|
||||
|
||||
index, err := rand.Int(rand.Reader, l)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if i == 2 && last == byte(45) {
|
||||
last = lowerNumericChars[index.Int64()]
|
||||
} else {
|
||||
last = dnsLabelChars[index.Int64()]
|
||||
}
|
||||
|
||||
body[i] = last
|
||||
}
|
||||
|
||||
return head + string(body) + tail, nil
|
||||
}
|
||||
}
|
||||
|
||||
func isASCII(s string) bool {
|
||||
for _, c := range s {
|
||||
if c > unicode.MaxASCII {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
72
strings_example_test.go
Normal file
72
strings_example_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package rands_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jimeh/rands"
|
||||
)
|
||||
|
||||
func ExampleBase64() {
|
||||
s, _ := rands.Base64(16)
|
||||
fmt.Println(s) // => CYxqEdUB1Rzno3SyZu2g/g==
|
||||
}
|
||||
|
||||
func ExampleBase64URL() {
|
||||
s, _ := rands.Base64URL(16)
|
||||
fmt.Println(s) // => zlqw9aFqcFggbk2asn3_aQ
|
||||
}
|
||||
|
||||
func ExampleHex() {
|
||||
s, _ := rands.Hex(16)
|
||||
fmt.Println(s) // => 956e2ec9e7f19ddd58bb935826926531
|
||||
}
|
||||
|
||||
func ExampleAlphanumeric() {
|
||||
s, _ := rands.Alphanumeric(16)
|
||||
fmt.Println(s) // => Fvk1PkrmG5crgOjT
|
||||
}
|
||||
|
||||
func ExampleAlphabetic() {
|
||||
s, _ := rands.Alphabetic(16)
|
||||
fmt.Println(s) // => XEJIzcZufHkuUmRM
|
||||
}
|
||||
|
||||
func ExampleUpper() {
|
||||
s, _ := rands.Upper(16)
|
||||
fmt.Println(s) // => UMAGAFPPNDRGLUPZ
|
||||
}
|
||||
|
||||
func ExampleUpperNumeric() {
|
||||
s, _ := rands.UpperNumeric(16)
|
||||
fmt.Println(s) // => DF0CQS0TK9CPUO3E
|
||||
}
|
||||
|
||||
func ExampleLower() {
|
||||
s, _ := rands.Lower(16)
|
||||
fmt.Println(s) // => ocsmggykzrxzfwgt
|
||||
}
|
||||
|
||||
func ExampleLowerNumeric() {
|
||||
s, _ := rands.LowerNumeric(16)
|
||||
fmt.Println(s) // => rwlv7a1p7klqffs5
|
||||
}
|
||||
|
||||
func ExampleNumeric() {
|
||||
s, _ := rands.Numeric(16)
|
||||
fmt.Println(s) // => 9403373143598295
|
||||
}
|
||||
|
||||
func ExampleString() {
|
||||
s, _ := rands.String(16, "abcdefABCDEF")
|
||||
fmt.Println(s) // => adCDCaDEdeffeDeb
|
||||
}
|
||||
|
||||
func ExampleUnicodeString() {
|
||||
s, _ := rands.UnicodeString(16, []rune("九七二人入八力十下三千上口土夕大"))
|
||||
fmt.Println(s) // => 下下口九力下土夕下土八上二夕大三
|
||||
}
|
||||
|
||||
func ExampleDNSLabel() {
|
||||
s, _ := rands.DNSLabel(16)
|
||||
fmt.Println(s) // => z0ij9o8qkbs0ru-h
|
||||
}
|
||||
623
strings_test.go
Normal file
623
strings_test.go
Normal file
@@ -0,0 +1,623 @@
|
||||
package rands
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHex(t *testing.T) {
|
||||
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 BenchmarkHex(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Hex(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64(t *testing.T) {
|
||||
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 BenchmarkBase64(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Base64(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64URL(t *testing.T) {
|
||||
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 BenchmarkBase64URL(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Base64URL(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlphanumeric(t *testing.T) {
|
||||
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 BenchmarkAlphanumeric(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Alphanumeric(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlphabetic(t *testing.T) {
|
||||
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 BenchmarkAlphabetic(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Alphabetic(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumeric(t *testing.T) {
|
||||
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 BenchmarkNumeric(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Numeric(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpper(t *testing.T) {
|
||||
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 BenchmarkUpper(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Upper(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpperNumeric(t *testing.T) {
|
||||
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 BenchmarkUpperNumeric(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = UpperNumeric(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLower(t *testing.T) {
|
||||
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 BenchmarkLower(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Lower(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLowerNumeric(t *testing.T) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLowerNumeric(b *testing.B) {
|
||||
for _, tt := range testCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = LowerNumeric(tt.n)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var stringTestCases = []struct {
|
||||
name string
|
||||
n int
|
||||
alphabet string
|
||||
errIs error
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
name: "greek",
|
||||
n: 32,
|
||||
alphabet: "αβγδεζηθικλμνξοπρστυφχψωςΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΩ" +
|
||||
"άίόύώέϊϋΐΰΆΈΌΏΎΊ",
|
||||
errIs: errNonASCIIAlphabet,
|
||||
errStr: "rands: alphabet contains non-ASCII characters",
|
||||
},
|
||||
{
|
||||
name: "chinese",
|
||||
n: 32,
|
||||
alphabet: "的一是不了人我在有他这为之大来以个中上们到说国和地也子" +
|
||||
"时道出而要于就下得可你年生",
|
||||
errIs: errNonASCIIAlphabet,
|
||||
errStr: "rands: alphabet contains non-ASCII characters",
|
||||
},
|
||||
{
|
||||
name: "japanese",
|
||||
n: 32,
|
||||
alphabet: "一九七二人入八力十下三千上口土夕大女子小山川五天中六円" +
|
||||
"手文日月木水火犬王正出本右四",
|
||||
errIs: errNonASCIIAlphabet,
|
||||
errStr: "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) {
|
||||
for _, tt := range stringTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := String(tt.n, tt.alphabet)
|
||||
|
||||
if tt.errIs == nil || tt.errStr == "" {
|
||||
assert.Len(t, []rune(got), tt.n)
|
||||
assertAllowedChars(t, tt.alphabet, got)
|
||||
}
|
||||
|
||||
if tt.errIs != nil {
|
||||
assert.True(t, errors.Is(err, errNonASCIIAlphabet))
|
||||
}
|
||||
|
||||
if tt.errStr != "" {
|
||||
assert.EqualError(t, err, tt.errStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkString(b *testing.B) {
|
||||
for _, tt := range stringTestCases {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = String(tt.n, tt.alphabet)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnicodeString(b *testing.B) {
|
||||
for _, tt := range stringTestCases {
|
||||
alphabet := []rune(tt.alphabet)
|
||||
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = UnicodeString(tt.n, alphabet)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
n int
|
||||
errIs error
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
name: "n=-128",
|
||||
n: -128,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{
|
||||
name: "n=0",
|
||||
n: 0,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "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,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
{
|
||||
name: "n=128",
|
||||
n: 128,
|
||||
errIs: errDNSLabelLength,
|
||||
errStr: "rands: DNS labels must be between 1 and 63 characters " +
|
||||
"in length",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
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++ {
|
||||
got, err := DNSLabel(tt.n)
|
||||
|
||||
if tt.errIs == nil || tt.errStr == "" {
|
||||
require.Len(t, got, tt.n)
|
||||
asserDNSLabel(t, got)
|
||||
}
|
||||
|
||||
if tt.errIs != nil {
|
||||
require.True(t, errors.Is(err, errDNSLabelLength))
|
||||
}
|
||||
|
||||
if tt.errStr != "" {
|
||||
require.EqualError(t, err, tt.errStr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 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