Compare commits

..

No commits in common. "main" and "v0.7.1" have entirely different histories.
main ... v0.7.1

33 changed files with 510 additions and 3370 deletions

@ -9,7 +9,3 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

@ -38,11 +38,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -53,7 +53,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -67,4 +67,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v1

@ -1,4 +1,4 @@
name: Test
name: Build Status
on: [push, pull_request]
@ -6,13 +6,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
fetch-depth: 2
- uses: actions/setup-go@v5
- uses: actions/setup-go@v2
with:
go-version: '1.18'
- name: Run coverage
run: go test -race -v -coverprofile=coverage.txt -covermode=atomic ./...
run: go test -v -coverprofile=coverage.txt -covermode=atomic ./...
- name: Upload coverage to Codecov
run: bash <(curl -s https://codecov.io/bash)

1
.gitignore vendored

@ -2,4 +2,3 @@
*.swp
*.save
test.*
*.test

@ -1,9 +1,8 @@
# common
[![GoDoc](https://godoc.org/git.tcp.direct/kayos/common?status.svg)](https://pkg.go.dev/git.tcp.direct/kayos/common) [![codecov](https://codecov.io/gh/yunginnanet/common/branch/master/graph/badge.svg?token=vk5frSGqhq)](https://codecov.io/gh/yunginnanet/common)
Welcome to things. Here are some of the aforementioned:
* [common](https://pkg.go.dev/git.tcp.direct/kayos/common)
Welcome to things. Here are some of the aforementioned:
* [hash](https://pkg.go.dev/git.tcp.direct/kayos/common/hash)
@ -13,10 +12,38 @@ Welcome to things. Here are some of the aforementioned:
* [entropy](https://pkg.go.dev/git.tcp.direct/kayos/common/entropy)
* [pool](https://pkg.go.dev/git.tcp.direct/kayos/common/pool)
* [network](https://pkg.go.dev/git.tcp.direct/kayos/common/network)
---
## base
### Dependencies
`import "git.tcp.direct/kayos/common"`
[skeeto](https://github.com/skeeto)'s amazing [RNG](https://github.com/skeeto/rng-go) for optimized [entropy](https://pkg.go.dev/git.tcp.direct/kayos/common/entropy) is the sole dependency save for stdlib++.
## Base Module
#### func Abs
```go
func Abs(n int) int
```
Abs will give you the positive version of a negative integer, quickly.
#### func BytesToFloat64
```go
func BytesToFloat64(bytes []byte) float64
```
BytesToFloat64 will take a slice of bytes and convert it to a float64 type.
#### func Float64ToBytes
```go
func Float64ToBytes(f float64) []byte
```
Float64ToBytes will take a float64 type and convert it to a slice of bytes.
#### func Fprint
```go
func Fprint(w io.Writer, s string)
```
Fprint is fmt.Fprint with error handling.

@ -2,12 +2,60 @@ package common
import (
"errors"
"fmt"
"io"
"os"
"sync"
"testing"
"git.tcp.direct/kayos/common/entropy"
"git.tcp.direct/kayos/common/hash"
"git.tcp.direct/kayos/common/squish"
)
var needle = []byte(entropy.RandStr(16))
func TestBlakeEqualAndB64(t *testing.T) {
var clone = make([]byte, len(needle))
copy(clone, needle)
if !hash.BlakeEqual(needle, clone) {
t.Fatalf("BlakeEqual failed! Values %v and %v should have been equal.\n|---->Lengths: %d and %d",
needle, clone, len(needle), len(clone),
)
}
falseclone := []byte(entropy.RandStr(16))
if hash.BlakeEqual(needle, falseclone) {
t.Fatalf("BlakeEqual failed! Values %v and %v should NOT have been equal.\n|---->Lengths: %d and %d",
needle, clone, len(needle), len(clone),
)
}
var based = [2][]byte{needle, clone}
based[0] = []byte(squish.B64e(based[0]))
based[1] = []byte(squish.B64e(based[0]))
if hash.BlakeEqual(based[0], based[1]) {
t.Fatalf("Base64 encoding failed! Values %v and %v should NOT have been equal.\n|---->Lengths: %d and %d",
based[0], based[1], len(based[0]), len(based[1]),
)
}
// sneakin in some code coverage rq dwai nbd
bogusRd, bogusWrt := io.Pipe()
t.Logf("\n")
go func() {
Fprint(io.MultiWriter(bogusWrt, os.Stdout), fmt.Sprintf("[PASS] based[0] = %s\n[PASS] based[1] = %s", string(based[0]), string(based[1])))
}()
_ = bogusWrt.CloseWithError(io.ErrClosedPipe)
_, err := bogusRd.Read([]byte{})
if err == nil {
t.Fatalf("should have been an error...")
}
}
func TestAbs(t *testing.T) {
var start = int32(entropy.RNG(5))
for start < 1 {
@ -44,6 +92,7 @@ type phonyWriter struct{}
var o = &sync.Once{}
var fprintStatus bool
var pw = new(phonyWriter)
func (p2 phonyWriter) Write(p []byte) (int, error) {
var err = errors.New("closed")
@ -59,7 +108,6 @@ func (p2 phonyWriter) Write(p []byte) (int, error) {
}
func TestFprint(t *testing.T) {
var pw = new(phonyWriter)
Fprint(pw, "asdf")
if fprintStatus != true {
t.Fatal("first Fprint test should have succeeded")
@ -68,15 +116,4 @@ func TestFprint(t *testing.T) {
if fprintStatus != false {
t.Fatal("second Fprint test should not have succeeded")
}
pw = new(phonyWriter)
fprintStatus = false
o = &sync.Once{}
Fprintf(pw, "%s", "asdf")
if fprintStatus != true {
t.Fatal("first Fprint test should have succeeded")
}
Fprintf(pw, "%s", "asdf")
if fprintStatus != false {
t.Fatal("second Fprint test should not have succeeded")
}
}

@ -8,53 +8,13 @@ import (
"time"
"nullprogram.com/x/rng"
"git.tcp.direct/kayos/common/pool"
)
type randPool struct {
sync.Pool
}
func (p *randPool) Get() *rand.Rand {
return p.Pool.Get().(*rand.Rand)
}
func (p *randPool) Put(r *rand.Rand) {
p.Pool.Put(r)
}
var (
lolXD = randPool{
Pool: sync.Pool{
New: func() interface{} {
sm64 := new(rng.SplitMix64)
sm64.Seed(GetCryptoSeed())
prng := rand.New(sm64) //nolint:gosec
return prng
},
},
}
hardLocc = &sync.Mutex{}
sharedRand *rand.Rand
getSharedRand = &sync.Once{}
)
func setSharedRand() {
hardLocc.Lock()
sharedRand = lolXD.Get()
hardLocc.Unlock()
}
func AcquireRand() *rand.Rand {
return lolXD.Get()
}
func ReleaseRand(r *rand.Rand) {
lolXD.Put(r)
r = nil
}
// RandomStrChoice returns a random item from an input slice of strings.
func RandomStrChoice(choice []string) string {
if len(choice) > 0 {
@ -64,59 +24,47 @@ func RandomStrChoice(choice []string) string {
}
// GetCryptoSeed returns a random int64 derived from crypto/rand.
// This can be used as a seed for various PRNGs.
// This can be used as a seed for the math/rand package.
func GetCryptoSeed() int64 {
var seed int64
_ = binary.Read(crip.Reader, binary.BigEndian, &seed)
return seed
}
// GetOptimizedRand returns a pointer to a *new* rand.Rand which uses GetCryptoSeed to seed an rng.SplitMix64.
// GetOptimizedRand returns a pointer to a *new* rand.Rand which uses crypto/rand to seed a splitmix64 rng.
// Does not use the global/shared instance of a splitmix64 rng, but instead creates a new one.
func GetOptimizedRand() *rand.Rand {
r := new(rng.SplitMix64)
r.Seed(GetCryptoSeed())
return rand.New(r) //nolint:gosec
}
// GetSharedRand returns a pointer to our shared optimized rand.Rand which uses crypto/rand to seed a splitmix64 rng.
// WARNING - RACY - This is not thread safe, and should only be used in a single-threaded context.
func GetSharedRand() *rand.Rand {
getSharedRand.Do(func() {
setSharedRand()
})
return sharedRand
return rand.New(r)
}
// RNGUint32 returns a random uint32 using crypto/rand and splitmix64.
func RNGUint32() uint32 {
r := lolXD.Get()
ui := r.Uint32()
lolXD.Put(r)
return ui
getSharedRand.Do(func() {
sharedRand = GetOptimizedRand()
})
return sharedRand.Uint32()
}
/*
RNG returns integer with a maximum amount of 'n' using a global/shared instance of a splitmix64 rng.
- Benchmark_FastRandStr5-24 25205089 47.03 ns/op
/*RNG returns integer with a maximum amount of 'n' using a global/shared instance of a splitmix64 rng.
- Benchmark_FastRandStr5-24 25205089 47.03 ns/op
- Benchmark_FastRandStr25-24 7113620 169.8 ns/op
- Benchmark_FastRandStr55-24 3520297 340.7 ns/op
- Benchmark_FastRandStr500-24 414966 2837 ns/op
- Benchmark_FastRandStr55555-24 3717 315229 ns/op
*/
func RNG(n int) int {
r := lolXD.Get()
i := r.Intn(n)
lolXD.Put(r)
return i
getSharedRand.Do(func() {
sharedRand = GetOptimizedRand()
})
return sharedRand.Intn(n)
}
// OneInA generates a random number with a maximum of 'million' (input int).
// If the resulting random number is equal to 1, then the result is true.
func OneInA(million int) bool {
if million == 1 {
return true
}
return RNG(million) == 1
}
@ -129,54 +77,22 @@ func RandSleepMS(n int) {
const charset = "abcdefghijklmnopqrstuvwxyz1234567890"
const charsetWithUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
var strBufs = pool.NewBufferFactory()
// RandStr generates a random alphanumeric string with a max length of size.
// Alpha charset used is a-z all lowercase.
func RandStr(size int) string {
return randStr(false, size)
buf := make([]byte, size)
for i := 0; i != size; i++ {
buf[i] = charset[uint32(RNG(36))%uint32(len(charset))]
}
return string(buf)
}
// RandStrWithUpper generates a random alphanumeric string with a max length of size.
// Alpha charset used is a-Z mixed case.
func RandStrWithUpper(size int) string {
return randStr(true, size)
}
/*
randStr is an overoptimized (read: dummy fast) random string generator.
using byte buffers gives us a solid 2 alloc/op indefinitely.
using string builders gives us a linear increase in alloc/op, up to 22 alloc/op at 55,555 characters.
at 55,555 characters, we are at:
~57,000 bytes per op with byte builders
vs
~210,000 bytes per op with string builders.
this is felt significantly at ranges as low as 500 chars, where we get:
8 alloc/op and >1,000 bytes/op with string builders
vs
2 alloc/op and ~500 bytes/op with byte buffers.
*/
func randStr(upper bool, size int) string {
buf := strBufs.Get()
r := lolXD.Get()
buf := make([]byte, size)
for i := 0; i != size; i++ {
ui32 := int(r.Uint32())
switch upper {
case true:
_ = buf.WriteByte(charsetWithUpper[ui32%len(charsetWithUpper)])
case false:
_ = buf.WriteByte(charset[ui32%len(charset)])
}
buf[i] = charsetWithUpper[uint32(RNG(62))%uint32(len(charsetWithUpper))]
}
lolXD.Put(r)
s := buf.String()
strBufs.MustPut(buf)
return s
return string(buf)
}

@ -1,33 +1,19 @@
package entropy
import (
"fmt"
"strings"
"sync"
"testing"
)
var dupCount = 0
func check[T comparable](t *testing.T, zero T, one T) {
t.Helper()
func check[T comparable](zero T, one T, t *testing.T) {
if zero == one {
dupCount++
t.Errorf("hit a duplicate! %v == %v", zero, one)
t.Logf("duplicates so far: %d", dupCount)
}
}
func Test_RNG(t *testing.T) {
t.Parallel()
// for coverage
setSharedRand()
RandSleepMS(5)
hardLocc.Lock()
sharedRand = nil
getSharedRand = &sync.Once{}
hardLocc.Unlock()
// - - - - - -
if OneInA(1000000) {
println(string([]byte{
0x66, 0x75, 0x63, 0x6B, 0x68,
@ -36,94 +22,48 @@ func Test_RNG(t *testing.T) {
}))
}
for n := 0; n != 55555; n++ {
check(t, RNG(123454321), RNG(123454321))
check(t, RNGUint32(), RNGUint32())
}
// for coverage
if GetOptimizedRand().Intn(55555) == GetOptimizedRand().Intn(55555) {
t.Errorf("GetOptimizedRand(55555) returned the same value twice!")
}
if GetSharedRand().Intn(55555) == GetSharedRand().Intn(55555) {
t.Errorf("GetSharedRand(55555) returned the same value twice!")
}
r := AcquireRand()
one := r.Intn(55555)
two := r.Intn(55555)
if one == two {
t.Errorf("AcquireRand() returned the same value twice!")
}
ReleaseRand(r)
r = AcquireRand()
one1 := r.Intn(55555)
two1 := r.Intn(55555)
if one1 == two1 {
t.Errorf("AcquireRand() returned the same value twice!")
}
if one == one1 {
t.Errorf("AcquireRand()[2] returned the same value twice!")
}
if two == two1 {
t.Errorf("AcquireRand()[2] returned the same value twice!")
for n := 0; n != 500; n++ {
check(RNG(55555), RNG(55555), t)
check(RNGUint32(), RNGUint32(), t)
}
}
func Test_OneInA(t *testing.T) {
t.Parallel()
for n := 0; n < 100; n++ {
yes := ""
if OneInA(1) {
yes = "hello"
}
if yes != "hello" {
t.Fatalf("OneInA failed to trigger when provided '1' as an argument")
}
}
}
func randStrChecks(t *testing.T, zero, one string, intendedLength int) {
t.Helper()
func randStrChecks(zero, one string, t *testing.T, intendedLength int) {
if len(zero) != len(one) {
t.Fatalf("RandStr output length inconsistency, len(zero) is %d but wanted len(one) which is %d", len(zero), len(one))
}
if len(zero) != intendedLength || len(one) != intendedLength {
t.Fatalf(
"RandStr output length inconsistency, "+
"len(zero) is %d and len(one) is %d, but both should have been 55", len(zero), len(one))
t.Fatalf("RandStr output length inconsistency, len(zero) is %d and len(one) is %d, but both should have been 55", len(zero), len(one))
}
check(t, zero, one)
check(zero, one, t)
}
func Test_RandStr(t *testing.T) {
t.Parallel()
for n := 0; n != 500; n++ {
zero := RandStr(55)
one := RandStr(55)
t.Logf("Random0: %s Random1: %s", zero, one)
randStrChecks(t, zero, one, 55)
randStrChecks(zero, one, t, 55)
}
t.Logf("[SUCCESS] RandStr had no collisions")
}
func Test_RandStrWithUpper(t *testing.T) {
t.Parallel()
for n := 0; n != 500; n++ {
zero := RandStrWithUpper(15)
one := RandStrWithUpper(15)
t.Logf("Random0: %s Random1: %s", zero, one)
randStrChecks(t, zero, one, 15)
randStrChecks(zero, one, t, 15)
}
t.Logf("[SUCCESS] RandStr had no collisions")
}
func Test_RandStr_Entropy(t *testing.T) {
t.Parallel()
var totalScore = 0
for n := 0; n != 500; n++ {
zero := RandStr(55)
one := RandStr(55)
randStrChecks(t, zero, one, 55)
randStrChecks(zero, one, t, 55)
zeroSplit := strings.Split(zero, "")
oneSplit := strings.Split(one, "")
var similarity = 0
@ -132,10 +72,10 @@ func Test_RandStr_Entropy(t *testing.T) {
continue
}
similarity++
// t.Logf("[-] zeroSplit[%d] is the same as oneSplit[%d] (%s)", i, i, char)
}
if similarity*4 > 55 {
t.Errorf("[ENTROPY FAILURE] more than a quarter of the string is the same!\n "+
"zero: %s \n one: %s \nTotal similar: %d",
t.Errorf("[ENTROPY FAILURE] more than a quarter of the string is the same!\n zero: %s \n one: %s \nTotal similar: %d",
zero, one, similarity)
}
// t.Logf("[ENTROPY] Similarity score (lower is better): %d", similarity)
@ -145,7 +85,6 @@ func Test_RandStr_Entropy(t *testing.T) {
}
func Test_RandomStrChoice(t *testing.T) {
t.Parallel()
if RandomStrChoice([]string{}) != "" {
t.Fatalf("RandomStrChoice returned a value when given an empty slice")
}
@ -153,30 +92,72 @@ func Test_RandomStrChoice(t *testing.T) {
for n := 0; n != 500; n++ {
slice = append(slice, RandStr(555))
}
check(t, RandomStrChoice(slice), RandomStrChoice(slice))
check(RandomStrChoice(slice), RandomStrChoice(slice), t)
}
func Test_RNGUint32(t *testing.T) {
t.Parallel()
// start globals fresh, just for coverage.
setSharedRand()
hardLocc.Lock()
sharedRand = GetOptimizedRand()
getSharedRand = &sync.Once{}
hardLocc.Unlock()
RNGUint32()
}
func Benchmark_RandStr(b *testing.B) {
toTest := []int{5, 25, 55, 500, 55555}
for _, ln := range toTest {
for i := 1; i != 5; i++ {
b.Run(fmt.Sprintf("len%d/run%d", ln, i), func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for tn := 0; tn != b.N; tn++ {
RandStr(ln)
}
})
}
func Benchmark_RandStr5(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStr(5)
}
}
func Benchmark_RandStr25(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStr(25)
}
}
func Benchmark_RandStr55(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStr(55)
}
}
func Benchmark_RandStr500(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStr(500)
}
}
func Benchmark_RandStr55555(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStr(55555)
}
}
func Benchmark_RandStrWithUpper5(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStrWithUpper(5)
}
}
func Benchmark_RandStrWithUpper25(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStrWithUpper(25)
}
}
func Benchmark_RandStrWithUpper55(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStrWithUpper(55)
}
}
func Benchmark_RandStrWithUpper500(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStrWithUpper(500)
}
}
func Benchmark_RandStrWithUpper55555(b *testing.B) {
for n := 0; n != b.N; n++ {
RandStrWithUpper(55555)
}
}

17
go.mod

@ -3,14 +3,17 @@ module git.tcp.direct/kayos/common
go 1.19
require (
golang.org/x/crypto v0.24.0
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.27.0
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
nullprogram.com/x/rng v1.1.0
)
require golang.org/x/sys v0.21.0 // indirect
retract (
v0.9.1 // premature (race condition)
v0.9.0 // premature
v0.0.0-20220210125455-40e3d2190a52
require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
)

50
go.sum

@ -1,6 +1,48 @@
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
nullprogram.com/x/rng v1.1.0 h1:SMU7DHaQSWtKJNTpNFIFt8Wd/KSmOuSDPXrMFp/UMro=
nullprogram.com/x/rng v1.1.0/go.mod h1:glGw6V87vyfawxCzqOABL3WfL95G65az9Z2JZCylCkg=

@ -1,19 +1,17 @@
package hash
import (
"bytes"
"crypto/md5" //nolint:gosec
"crypto/sha1" //nolint:gosec
"crypto/sha256"
"crypto/sha512"
"errors"
"fmt"
"hash"
"hash/crc32"
"hash/crc64"
"io"
"os"
"sync"
"github.com/pkg/errors"
"golang.org/x/crypto/blake2b"
)
@ -26,41 +24,8 @@ const (
TypeSHA256
TypeSHA512
TypeMD5
TypeCRC32
TypeCRC64ISO
TypeCRC64ECMA
)
var typeToString = map[Type]string{
TypeNull: "null", TypeBlake2b: "blake2b", TypeSHA1: "sha1",
TypeSHA256: "sha256", TypeSHA512: "sha512",
TypeMD5: "md5", TypeCRC32: "crc32",
TypeCRC64ISO: "crc64-iso", TypeCRC64ECMA: "crc64-ecma",
}
var stringToType = map[string]Type{
"null": TypeNull, "blake2b": TypeBlake2b, "sha1": TypeSHA1,
"sha256": TypeSHA256, "sha512": TypeSHA512,
"md5": TypeMD5, "crc32": TypeCRC32,
"crc64-iso": TypeCRC64ISO, "crc64-ecma": TypeCRC64ECMA,
}
func StringToType(s string) Type {
t, ok := stringToType[s]
if !ok {
return TypeNull
}
return t
}
func (t Type) String() string {
s, ok := typeToString[t]
if !ok {
return "unknown"
}
return s
}
var (
sha1Pool = &sync.Pool{
New: func() interface{} {
@ -88,24 +53,6 @@ var (
return h
},
}
crc32Pool = &sync.Pool{
New: func() interface{} {
return crc32.NewIEEE()
},
}
crc64ISOPool = &sync.Pool{
New: func() interface{} {
// ISO and ECMA are pre-computed in the stdlib, so Make is just fetching them, not computing them.
h := crc64.New(crc64.MakeTable(crc64.ISO))
return h
},
}
crc64ECMAPool = &sync.Pool{
New: func() interface{} {
h := crc64.New(crc64.MakeTable(crc64.ECMA))
return h
},
}
)
func Sum(ht Type, b []byte) []byte {
@ -126,16 +73,6 @@ func Sum(ht Type, b []byte) []byte {
case TypeMD5:
h = md5Pool.Get().(hash.Hash)
defer md5Pool.Put(h)
case TypeCRC32:
h = crc32Pool.Get().(hash.Hash)
defer crc32Pool.Put(h)
case TypeCRC64ISO:
h = crc64ISOPool.Get().(hash.Hash)
defer crc64ISOPool.Put(h)
case TypeCRC64ECMA:
h = crc64ECMAPool.Get().(hash.Hash)
defer crc64ECMAPool.Put(h)
default:
return nil
}
@ -145,8 +82,13 @@ func Sum(ht Type, b []byte) []byte {
return sum
}
// SumFile will attempt to calculate a blake2b checksum of the given file path's contents.
func SumFile(ht Type, path string) (buf []byte, err error) {
// Blake2bSum ignores all errors and gives you a blakae2b 64 hash value as a byte slice. (or panics somehow)
func Blake2bSum(b []byte) []byte {
return Sum(TypeBlake2b, b)
}
// BlakeFileChecksum will attempt to calculate a blake2b checksum of the given file path's contents.
func BlakeFileChecksum(path string) (buf []byte, err error) {
var f *os.File
f, err = os.Open(path)
if err != nil {
@ -155,7 +97,7 @@ func SumFile(ht Type, path string) (buf []byte, err error) {
defer func() {
if closeErr := f.Close(); err != nil {
err = fmt.Errorf("failed to close file during BlakeFileChecksum: %w", closeErr)
err = errors.Wrapf(err, "failed to close file: %s", closeErr)
}
}()
@ -164,5 +106,10 @@ func SumFile(ht Type, path string) (buf []byte, err error) {
return nil, errors.New("file is empty")
}
return Sum(ht, buf), nil
return Sum(TypeBlake2b, buf), nil
}
// BlakeEqual will take in two byte slices, hash them with blake2b, and tell you if the resulting checksums match.
func BlakeEqual(a []byte, b []byte) bool {
return bytes.Equal(Blake2bSum(a), Blake2bSum(b))
}

@ -4,136 +4,101 @@ import (
"bytes"
"encoding/base64"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"git.tcp.direct/kayos/common/entropy"
"git.tcp.direct/kayos/common/squish"
)
const (
kayosBlake2b = "Kr+6ONDx+cq/WvhHpQE/4LVuJYi9QHz1TztHNTWwa9KJWqHxfTNLKF3YxrcLptA3wO0KHm83Lq7gpBWgCQzPag=="
kayosMD5 = "aoNjwileNitB208vOwpIow=="
kayosSHA1 = "M23ElC0sQYAK+MaMZVmza2L8mss="
kayosSHA256 = "BagY0TmoGR3O7t80BGm4K6UHPlqEg6HJirwQmhrPK4U="
kayosSHA512 = "xiuo2na76acrWXCTTR++O1pPZabOhyj8nbfb5Go3e1pEq9VJYIsOioTXalf2GCuERmFecWkmaL5QI8mIXXWpNA=="
kayosCRC32 = "xtig5w=="
kayosCRC64ISO = "YVx8IpQawAA="
kayosCRC64ECMA = "Nn5+vneo4j4="
kayosBlake2b = "Kr+6ONDx+cq/WvhHpQE/4LVuJYi9QHz1TztHNTWwa9KJWqHxfTNLKF3YxrcLptA3wO0KHm83Lq7gpBWgCQzPag=="
kayosMD5 = "aoNjwileNitB208vOwpIow=="
kayosSHA1 = "M23ElC0sQYAK+MaMZVmza2L8mss="
kayosSHA256 = "BagY0TmoGR3O7t80BGm4K6UHPlqEg6HJirwQmhrPK4U="
kayosSHA512 = "xiuo2na76acrWXCTTR++O1pPZabOhyj8nbfb5Go3e1pEq9VJYIsOioTXalf2GCuERmFecWkmaL5QI8mIXXWpNA=="
)
var kayosByteSlice = []byte{107, 97, 121, 111, 115, 10}
var (
ogsha1, _ = base64.StdEncoding.DecodeString(kayosSHA1)
ogsha256, _ = base64.StdEncoding.DecodeString(kayosSHA256)
ogsha512, _ = base64.StdEncoding.DecodeString(kayosSHA512)
ogmd5, _ = base64.StdEncoding.DecodeString(kayosMD5)
ogBlake2b, _ = base64.StdEncoding.DecodeString(kayosBlake2b)
ogCRC32, _ = base64.StdEncoding.DecodeString(kayosCRC32)
ogCRC64ISO, _ = base64.StdEncoding.DecodeString(kayosCRC64ISO)
ogCRC64ECMA, _ = base64.StdEncoding.DecodeString(kayosCRC64ECMA)
valids = map[Type][]byte{
TypeSHA1: ogsha1,
TypeSHA256: ogsha256,
TypeSHA512: ogsha512,
TypeMD5: ogmd5,
TypeCRC32: ogCRC32,
TypeCRC64ISO: ogCRC64ISO,
TypeCRC64ECMA: ogCRC64ECMA,
TypeBlake2b: ogBlake2b,
func TestBlake2bsum(t *testing.T) {
og := squish.B64d(kayosBlake2b)
newc := Blake2bSum([]byte("kayos\n"))
if !bytes.Equal(newc, og) {
t.Fatalf("wanted: %v, got %v", kayosBlake2b, squish.B64e(newc))
}
)
if !BlakeEqual([]byte("kayos\n"), []byte{107, 97, 121, 111, 115, 10}) {
t.Fatalf("BlakeEqual should have been true. %s should == %s", []byte("kayos\n"), []byte{107, 97, 121, 111, 115, 92, 110})
}
t.Logf("[blake2bSum] success: %s", kayosBlake2b)
}
func TestBlakeFileChecksum(t *testing.T) {
path := t.TempDir() + "/blake2b.dat"
err := os.WriteFile(path, []byte{107, 97, 121, 111, 115, 10}, os.ModePerm)
if err != nil {
t.Errorf("[FAIL] failed to write test fle for TestBlakeFileChecksum: %s", err.Error())
}
filecheck, err2 := BlakeFileChecksum(path)
if err2 != nil {
t.Errorf("[FAIL] failed to read test fle for TestBlakeFileChecksum: %s", err2.Error())
}
if len(filecheck) == 0 {
t.Errorf("[FAIL] got nil output from BlakeFileChecksum")
}
if !bytes.Equal(filecheck, squish.B64d(kayosBlake2b)) {
t.Fatalf("[FAIL] wanted: %v, got %v", kayosBlake2b, squish.B64e(filecheck))
}
badfile, err3 := BlakeFileChecksum(t.TempDir() + "/" + entropy.RandStr(50))
if err3 == nil {
t.Errorf("[FAIL] shouldn't have been able to read phony file")
}
if len(badfile) != 0 {
t.Errorf("[FAIL] got non-nil output from bogus file: %v", badfile)
}
if !bytes.Equal(filecheck, squish.B64d(kayosBlake2b)) {
t.Fatalf("[FAIL] wanted: %v, got %v", kayosBlake2b, squish.B64e(filecheck))
}
err = os.WriteFile(path+".empty", []byte{}, os.ModePerm)
if err != nil {
t.Errorf("[FAIL] failed to write test fle for TestBlakeFileChecksum: %s", err.Error())
}
_, err4 := BlakeFileChecksum(path + ".empty")
if err4 == nil {
t.Fatalf("[FAIL] should have failed to read empty file")
}
}
func TestSum(t *testing.T) {
t.Parallel()
if Sum(TypeNull, []byte("yeet")) != nil {
t.Fatal("Sum(TypeNull, []byte(\"yeet\")) should have returned nil")
}
for k, v := range valids {
typeToTest := k
valueToTest := v
t.Run(typeToTest.String()+"/string_check", func(t *testing.T) {
t.Parallel()
if !strings.EqualFold(typeToTest.String(), StringToType(typeToTest.String()).String()) {
t.Errorf("[FAIL] %s: wanted %s, got %s",
typeToTest.String(), typeToTest.String(), StringToType(typeToTest.String()).String(),
)
}
})
t.Run(typeToTest.String()+"/static_check", func(t *testing.T) {
t.Parallel()
mySum := Sum(typeToTest, kayosByteSlice)
if !bytes.Equal(mySum, valueToTest) {
t.Errorf("[FAIL] %s: wanted %v, got %v", typeToTest.String(), valueToTest, mySum)
}
})
t.Run(typeToTest.String()+"/file_check", func(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), typeToTest.String()) // for coverage
if err := os.WriteFile(path, kayosByteSlice, os.ModePerm); err != nil {
t.Fatalf("[FAIL] failed to write test fle for TestSum: %s", err.Error())
}
res, err := SumFile(typeToTest, path)
if err != nil {
t.Fatalf("[FAIL] failed to read test fle for TestSum: %s", err.Error())
}
if !bytes.Equal(res, valueToTest) {
t.Errorf("[FAIL] %s: wanted %v, got %v", typeToTest.String(), valueToTest, res)
}
})
}
t.Run("bad file", func(t *testing.T) {
t.Parallel()
_, err := SumFile(TypeSHA1, "/dev/null")
if err == nil {
t.Fatal("SumFile should have returned an error")
}
if _, err = SumFile(TypeSHA1, entropy.RandStrWithUpper(500)); err == nil {
t.Fatal("SumFile should have returned an error")
}
})
t.Run("unknown type", func(t *testing.T) {
t.Parallel()
if Type(uint8(94)).String() != "unknown" {
t.Fatal("Type(uint(9453543)).String() should have returned \"unknown\"")
}
if StringToType(entropy.RandStr(10)) != TypeNull {
t.Fatal("bogus string should have returned TypeNull")
}
})
}
var (
ogsha1, _ = base64.StdEncoding.DecodeString(kayosSHA1)
ogsha256, _ = base64.StdEncoding.DecodeString(kayosSHA256)
ogsha512, _ = base64.StdEncoding.DecodeString(kayosSHA512)
ogmd5, _ = base64.StdEncoding.DecodeString(kayosMD5)
newsha1 = Sum(TypeSHA1, []byte("kayos\n"))
newsha256 = Sum(TypeSHA256, []byte("kayos\n"))
newsha512 = Sum(TypeSHA512, []byte("kayos\n"))
newmd5 = Sum(TypeMD5, []byte("kayos\n"))
)
func BenchmarkSum(b *testing.B) {
runIt := func(length int) {
dat := []byte(entropy.RandStrWithUpper(length))
b.Run(strconv.Itoa(length)+"char", func(b *testing.B) {
for sumType := range valids {
b.Run(sumType.String(), func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.SetBytes(int64(len(dat)))
Sum(sumType, dat)
}
})
}
})
if !bytes.Equal(newsha1, ogsha1) {
t.Fatalf("[sha1] wanted: %v, got %v", ogsha1, newsha1)
}
for i := 0; i != 5; i++ {
mult := 5
if i == 0 {
runIt(50)
continue
}
if i > 1 {
mult = (mult * 10) * i
}
if i > 3 {
mult = (mult * 100) * i
}
runIt(i * mult)
t.Logf("[sha1] success: %s", kayosSHA1)
if !bytes.Equal(newsha256, ogsha256) {
t.Fatalf("[sha256] wanted: %v, got %v", ogsha256, newsha256)
}
t.Logf("[sha256] success: %s", kayosSHA256)
if !bytes.Equal(newsha512, ogsha512) {
t.Fatalf("[sha512] wanted: %v, got %v", ogsha512, newsha512)
}
t.Logf("[sha512] success: %s", kayosSHA512)
if !bytes.Equal(newmd5, ogmd5) {
t.Fatalf("[md5] wanted: %v, got %v", ogmd5, newmd5)
}
t.Logf("[md5] success: %s", kayosMD5)
}

@ -1,8 +0,0 @@
package common
import "net"
// Dialer is an interface that should exist in stdlib honestly. Make it make sense that it doesn't.
type Dialer interface {
Dial(network, address string) (net.Conn, error)
}

@ -1,16 +0,0 @@
package common
import (
"net"
"testing"
)
func needsDialer(t *testing.T, d any) {
if _, ok := d.(Dialer); !ok {
t.Fatal("d is not a Dialer")
}
}
func TestDialer(t *testing.T) {
needsDialer(t, &net.Dialer{})
}

@ -1,107 +0,0 @@
//go:build linux
package linux
import (
"syscall"
"time"
)
/*
some interesting information on RAM, sysinfo, and /proc/meminfo
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
- https://github.com/mmalecki/procps/blob/master/proc/sysinfo.c
in the second link we see that even procps is parsing /proc/meminfo to get the RAM information
i'd really like to avoid this, and I may end up estimating the available memory myself...
the whole idea of this package is to focus on system calls and not on parsing files
for now I'll just add a note to the RAMInfo struct definition.
tldr; sysinfo is a bit incomplete due to it's lack of available memory calculation
*/
// from https://man7.org/linux/man-pages/man2/sysinfo.2.html
/*
struct sysinfo {
long uptime; // Seconds since boot
unsigned long loads[3]; // 1, 5, and 15 minute load averages
unsigned long totalram; // Total usable main memory size
unsigned long freeram; // Available memory size
unsigned long sharedram; // Amount of shared memory
unsigned long bufferram; // Memory used by buffers
unsigned long totalswap; // Total swap space size
unsigned long freeswap; // Swap space still available
unsigned short procs; // Number of current processes
unsigned long totalhigh; // Total high memory size
unsigned long freehigh; // Available high memory size
unsigned int mem_unit; // Memory unit size in bytes
char _f[20-2*sizeof(long)-sizeof(int)]; // Padding to 64 bytes
};
*/
type SystemInfo struct {
// Uptime is the time since the system was booted.
Uptime time.Duration
// Loads is a 3 element array containing the 1, 5, and 15 minute load averages.
Loads [3]uint64
// RAM is a struct containing information about the system's RAM. See notes on this.
RAM RAMInfo
// Procs is the number of current processes.
Procs uint16
}
// RAMInfo is a struct that contains information about the running system's memory.
// Please see important notes in the struct definition.
type RAMInfo struct {
Total int64
// Free is the amount of memory that is not being used.
// This does not take into account memory that is being used for caching.
// For more information, see the comments at the top of this file.
Free int64
Used int64
Shared int64
Buffers int64
Cached int64
SwapTotal int64
SwapFree int64
// unit is the memory unit size multiple in bytes.
// It is used to calculate the above values when the struct is initialized.q
unit uint32
}
// Sysinfo returns a SystemInfo struct containing information about the running system.
// For memory, please see important notes in the RAMInfo struct definition and the top of this file.
// Be sure to check err before using the returned value.
func Sysinfo() (systemInfo *SystemInfo, err error) {
sysinf := syscall.Sysinfo_t{}
err = syscall.Sysinfo(&sysinf)
unit := uint64(sysinf.Unit)
systemInfo = &SystemInfo{
Uptime: time.Duration(sysinf.Uptime) * time.Second,
Loads: [3]uint64{sysinf.Loads[0], sysinf.Loads[1], sysinf.Loads[2]},
RAM: RAMInfo{
Total: int64(sysinf.Totalram * unit),
Free: int64(sysinf.Freeram * unit),
Used: int64((sysinf.Totalram - sysinf.Freeram) * unit),
Shared: int64(sysinf.Sharedram * unit),
Buffers: int64(sysinf.Bufferram * unit),
Cached: int64((sysinf.Totalram - sysinf.Freeram - sysinf.Bufferram) * unit),
SwapTotal: int64(sysinf.Totalswap * unit),
SwapFree: int64(sysinf.Freeswap * unit),
unit: sysinf.Unit,
},
Procs: sysinf.Procs,
}
return
}
// Uptime returns the time since the system was booted.
// Be sure to check err before using the returned value.
func Uptime() (utime time.Duration, err error) {
var sysinf *SystemInfo
sysinf, err = Sysinfo()
return sysinf.Uptime, err
}

@ -1,89 +0,0 @@
//go:build linux
package linux
import (
"io"
"os"
"strconv"
"strings"
"testing"
"time"
)
// getUptimeControlValue from /proc/uptime to compare against our syscall value.
func getUptimeControlValue(t *testing.T) time.Duration {
f, err := os.Open("/proc/uptime")
if err != nil {
t.Fatalf("failed to open /proc/uptime with error: %v", err)
}
buf, err := io.ReadAll(f)
if err != nil {
t.Fatalf("failed to read /proc/uptime with error: %v", err)
}
t.Logf("read %d bytes from /proc/uptime: %s", len(buf), buf)
controlSeconds, err := strconv.ParseInt(string(buf[:strings.IndexByte(string(buf), '.')]), 10, 64)
if err != nil {
t.Fatalf("failed to parse /proc/uptime with error: %e", err)
}
if controlSeconds < 1 {
t.Fatalf("failed to parse /proc/uptime (zero value)")
}
return time.Duration(controlSeconds) * time.Second
}
func TestUptime(t *testing.T) {
uptimeCtrl := getUptimeControlValue(t)
t.Logf("control uptime: %v", uptimeCtrl)
uptime, err := Uptime()
if err != nil {
t.Fatalf("failed to get uptime with error: %e", err)
}
if uptime < 1 {
t.Fatalf("failed to get uptime (zero value)")
}
t.Logf("uptime: %v", uptime)
matching := uptime == uptimeCtrl
// if somehow the uptime is less than the control, which was called first
// then this has failed terribly.
// If it's greater, then it's possible we are within an acceptable tolerance.
if !matching && uptime < uptimeCtrl {
t.Fatalf("uptime does not match control uptime (uptime < uptimeCtrl)!!")
}
if !matching {
t.Logf("no match, allowing for a 1 second tolerance...")
matching = uptime == uptimeCtrl+time.Second
if matching {
t.Logf("success! (uptime == uptimeCtrl+time.Second)")
}
}
if !matching {
t.Errorf("uptime does not match control uptime: %v != %v", uptime, uptimeCtrl)
}
}
func TestSysinfo(t *testing.T) {
t.Parallel()
si, err := Sysinfo()
if err != nil {
t.Fatalf("failed to get sysinfo with error: %e", err)
}
if si == nil {
t.Fatalf("failed to get sysinfo (nil)")
}
t.Logf("sysinfo: %v", si)
}

@ -49,7 +49,7 @@ const (
// GetUname uses system calls to retrieve the same values as the uname linux command
func GetUname(unameFlags string) (un string, err error) {
ub := &syscall.Utsname{}
err = syscall.Uname(ub)
_ = syscall.Uname(ub)
var targets []*[65]int8
for _, n := range unameFlags {
var flag = [1]string{string(n)}
@ -76,7 +76,7 @@ func GetUname(unameFlags string) (un string, err error) {
return "", errors.New("no valid uname targets in string")
}
var uns = make([]string, len(targets))
var uns []string
for _, target := range targets {
var sub []string
for _, r := range target {

@ -1,5 +1,3 @@
//go:build linux
package linux
import "testing"

@ -1,310 +0,0 @@
// Package list implements a locking list.l
package list
import (
"container/list"
"errors"
"sync"
)
var (
ErrElementNotInList = errors.New("element not in list")
ErrMarkNotInList = errors.New("mark not in list")
ErrNilValue = errors.New("nil element")
ErrUninitialized = errors.New("uninitialized list")
)
type LockingList struct {
l *list.List
*sync.RWMutex
}
func (ll *LockingList) Lock() error {
if ll == nil || ll.RWMutex == nil {
return ErrUninitialized
}
ll.RWMutex.Lock()
return nil
}
func (ll *LockingList) Unlock() {
if ll.RWMutex != nil {
ll.RWMutex.Unlock()
}
}
func (ll *LockingList) RLock() error {
switch {
case
ll == nil,
ll.RWMutex == nil,
ll.l == nil:
return ErrUninitialized
default:
//
}
ll.RWMutex.RLock()
return nil
}
func (ll *LockingList) RUnlock() {
if ll.RWMutex != nil {
ll.RWMutex.RUnlock()
}
}
func New() *LockingList {
ll := &LockingList{
l: list.New(),
RWMutex: &sync.RWMutex{},
}
return ll
}
func (ll *LockingList) wrapElement(e *list.Element) *Element {
if e == nil {
return nil
}
return &Element{
list: ll,
Element: e,
}
}
// Init initializes or clears list l.
func (ll *LockingList) Init() *LockingList {
if ll.l != nil {
_ = ll.Lock()
ll.l.Init()
ll.Unlock()
return ll
}
ll.l = list.New()
ll.RWMutex = &sync.RWMutex{}
return ll
}
func (ll *LockingList) InsertAfter(v any, mark *Element) (*Element, error) {
if err := ll.check(v, mark, true); err != nil {
return nil, err
}
_ = ll.Lock()
res := ll.l.InsertAfter(v, mark.Element)
ll.Unlock()
return ll.wrapElement(res), nil
}
func (ll *LockingList) InsertBefore(v any, mark *Element) (*Element, error) {
if err := ll.check(v, mark, true); err != nil {
return nil, err
}
_ = ll.Lock()
res := ll.wrapElement(
ll.l.InsertBefore(v, mark.Element),
)
ll.Unlock()
return res, nil
}
func (ll *LockingList) Len() int {
_ = ll.RLock()
l := ll.l.Len()
ll.RUnlock()
return l
}
func (ll *LockingList) MoveAfter(e, mark *Element) error {
_ = ll.Lock()
ll.l.MoveAfter(e.Element, mark.Element)
ll.Unlock()
return nil
}
func (ll *LockingList) Front() *Element {
_ = ll.RLock()
e := ll.l.Front()
ll.RUnlock()
return ll.wrapElement(e)
}
func (ll *LockingList) Back() *Element {
_ = ll.RLock()
e := ll.l.Back()
ll.RUnlock()
return ll.wrapElement(e)
}
func (ll *LockingList) MoveToBack(e *Element) error {
_ = ll.Lock()
ll.l.MoveToBack(e.Element)
ll.Unlock()
return nil
}
func (ll *LockingList) MoveToFront(e *Element) error {
_ = ll.Lock()
ll.l.MoveToFront(e.Element)
ll.Unlock()
return nil
}
func (ll *LockingList) PushBack(v any) *Element {
if ll.l == nil {
ll.Init()
}
_ = ll.Lock()
e := ll.l.PushBack(v)
ll.Unlock()
return ll.wrapElement(e)
}
func (ll *LockingList) PushFront(v any) *Element {
if ll.l == nil {
ll.Init()
}
if err := ll.Lock(); err != nil {
return nil
}
e := ll.l.PushFront(v)
ll.Unlock()
return ll.wrapElement(e)
}
func (ll *LockingList) Remove(elm *Element) error {
if ll.l == nil {
return ErrUninitialized
}
if err := ll.check(elm, nil, false); err != nil {
return err
}
_ = ll.Lock()
_ = ll.l.Remove(elm.Element)
elm.list = nil // avoid memory leaks
elm.Element = nil // avoid memory leaks
ll.Unlock()
return nil
}
// Rotate moves the first element to the back of the list and returns it.
func (ll *LockingList) Rotate() *Element {
if ll.l == nil {
ll.Init()
}
if ll.Len() < 1 {
return nil
}
_ = ll.Lock()
e := ll.l.Front()
ll.l.MoveToBack(e)
ll.Unlock()
return ll.wrapElement(e)
}
func (ll *LockingList) Push(item any) (err error) {
if ll.l == nil {
ll.Init()
}
if err = ll.Lock(); err != nil {
return err
}
ll.l.PushBack(item)
ll.Unlock()
return nil
}
func (ll *LockingList) Pop() any {
if ll.Len() < 1 {
return nil
}
_ = ll.Lock()
e := ll.l.Front()
ll.l.Remove(e)
ll.Unlock()
return e.Value
}
func (ll *LockingList) PushBackList(other *LockingList) error {
if ll.l == nil {
ll.Init()
}
_ = ll.Lock()
ll.l.PushBackList(other.l)
ll.Unlock()
return nil
}
func (ll *LockingList) PushFrontList(other *LockingList) error {
if ll.l == nil {
ll.Init()
}
_ = ll.Lock()
ll.l.PushFrontList(other.l)
ll.Unlock()
return nil
}
type Element struct {
*list.Element
list *LockingList
}
func (e *Element) Value() any {
if e == nil {
return nil
}
if e.Element == nil {
return nil
}
return e.Element.Value
}
func (e *Element) Next() *Element {
if e == nil {
return nil
}
if err := e.list.RLock(); err != nil {
return nil
}
ne := e.list.wrapElement(e.Element.Next())
e.list.RUnlock()
return ne
}
func (e *Element) Prev() *Element {
if e == nil {
return nil
}
if err := e.list.RLock(); err != nil {
return nil
}
pe := e.list.wrapElement(e.Element.Prev())
e.list.RUnlock()
return pe
}
func (ll *LockingList) check(item any, mark *Element, needsMark bool) (err error) {
var elm *Element
var isElement bool
elm, isElement = item.(*Element)
if err = ll.RLock(); err != nil {
return err
}
switch {
case
needsMark && mark == nil,
needsMark && mark.list != ll:
err = ErrMarkNotInList
case
isElement && elm.Element == nil,
isElement && elm.list != ll:
err = ErrElementNotInList
default:
err = nil
}
ll.RUnlock()
return
}

@ -1,562 +0,0 @@
package list
import (
"container/list"
"errors"
"reflect"
"sync"
"testing"
"unsafe"
)
// Adapted from golang source
func checkListLen(t *testing.T, l *LockingList, length int) bool {
t.Helper()
if n := l.Len(); n != length {
t.Errorf("l.Len() = %d, want %d", n, length)
return false
}
return true
}
// Adapted from golang source
func checkList(t *testing.T, l *LockingList, es []any) {
t.Helper()
if !checkListLen(t, l, len(es)) {
return
}
i := 0
for e := l.Front(); e != nil; e = e.Next() {
le := e.Value()
if le != es[i] {
t.Errorf("\uF630 elt[%d].Value = %v, want %v", i, le, es[i])
}
// t.Logf("\uF634 elt[%d].Value = %v, want %v", i, le, es[i])
i++
}
}
func GetUnexportedField(field reflect.Value) interface{} {
return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface()
}
func checkListPointers(t *testing.T, ll *LockingList, es []*list.Element) {
if !checkListLen(t, ll, len(es)) {
return
}
newList := reflect.New(reflect.TypeOf(*ll.l)).Elem()
root := newList.FieldByName("root")
// zero length lists must be the zero value or properly initialized (sentinel circle)
if len(es) == 0 {
next := root.FieldByName("next")
prev := root.FieldByName("prev")
if !next.IsNil() && next != root || !prev.IsNil() && prev != root {
t.Errorf("l.root.next = %v; should be nil or %v",
next, root.Type(),
)
}
if !prev.IsNil() && next != root || !prev.IsNil() && prev != root {
t.Errorf("l.root.prev = %p; should be nil or %v",
prev.Type(), root.Type(),
)
}
return
}
}
// Adapted from golang source
func TestExtending(t *testing.T) { //nolint:funlen,gocyclo
t.Parallel()
l1 := New()
l2 := New()
l1.PushBack(1)
l1.PushBack(2)
l1.PushBack(3)
l2.PushBack(4)
l2.PushBack(5)
l3 := New()
if err := l3.PushBackList(l1); err != nil {
t.Error(errors.New("PushBackList failed"))
}
checkList(t, l3, []any{1, 2, 3})
if err := l3.PushBackList(l2); err != nil {
t.Error(errors.New("PushBackList failed"))
}
checkList(t, l3, []any{1, 2, 3, 4, 5})
l3 = New()
if err := l3.PushFrontList(l2); err != nil {
t.Error(errors.New("PushFrontList failed"))
}
checkList(t, l3, []any{4, 5})
if err := l3.PushFrontList(l1); err != nil {
t.Error(errors.New("PushFrontList failed"))
}
checkList(t, l3, []any{1, 2, 3, 4, 5})
checkList(t, l1, []any{1, 2, 3})
checkList(t, l2, []any{4, 5})
l3 = New()
if err := l3.PushBackList(l1); err != nil {
t.Error(errors.New("PushBackList failed"))
}
checkList(t, l3, []any{1, 2, 3})
if err := l3.PushBackList(l3); err != nil {
t.Error(errors.New("PushBackList failed"))
}
checkList(t, l3, []any{1, 2, 3, 1, 2, 3})
l3 = New()
if err := l3.PushFrontList(l1); err != nil {
return
}
checkList(t, l3, []any{1, 2, 3})
if err := l3.PushFrontList(l3); err != nil {
t.Error(errors.New("PushFrontList failed"))
}
checkList(t, l3, []any{1, 2, 3, 1, 2, 3})
l3 = New()
if err := l1.PushBackList(l3); err != nil {
t.Error(errors.New("PushBackList failed"))
}
checkList(t, l1, []any{1, 2, 3})
if err := l1.PushFrontList(l3); err != nil {
t.Error(errors.New("PushFrontList failed"))
}
checkList(t, l1, []any{1, 2, 3})
}
// Adapted from golang source
func TestIssue4103(t *testing.T) {
t.Parallel()
l1 := New()
l1.PushBack(1)
l1.PushBack(2)
l2 := New()
l2.PushBack(3)
l2.PushBack(4)
e := l1.Front()
err := l2.Remove(e)
if err == nil {
t.Errorf("l2.Remove(e) = %v, want ErrElementNotInList", err)
}
if !errors.Is(err, ErrElementNotInList) {
t.Errorf("l2.Remove(e) = %v, want ErrElementNotInList", err)
}
// l2 should not change because e is not an element of l2
if n := l2.Len(); n != 2 {
t.Errorf("l2.Len() = %d, want 2", n)
}
var ne *Element
if ne, err = l1.InsertBefore(8, e); err != nil {
t.Errorf("l1.InsertBefore(8, e) = %v, want nil", err)
}
//goland:noinspection GoCommentLeadingSpace (lol)
if ne == nil { // nolint:SA5011
t.Fatalf("l1.InsertBefore(8, e) = nil, want non-nil")
}
//goland:noinspection GoCommentLeadingSpace (lol)
if ne.Element == nil { // nolint:SA5011
t.Errorf("l1.InsertBefore(8, e) = nil, want non-nil")
}
if ne.Value() != 8 {
t.Errorf("l1.InsertBefore(8, e) = %v, want 8", ne.Value())
}
if n := l1.Len(); n != 3 {
t.Errorf("l1.Len() = %d, want 3", n)
}
}
// Adapted from golang source
func TestIssue6349(t *testing.T) {
t.Parallel()
l := New()
l.PushBack(1)
l.PushBack(2)
e := l.Front()
if e.Value() != 1 {
t.Errorf("e.value = %d, want 1", e.Value())
}
if err := l.Remove(e); err != nil {
t.Errorf("l.Remove(e) = %v, want nil", err)
}
if e.Next() != nil {
t.Errorf("e.Next() != nil")
}
if e.Prev() != nil {
t.Errorf("e.Prev() != nil")
}
}
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
// Adapted from golang source.
func TestZeroList(t *testing.T) {
t.Parallel()
var l1 = new(LockingList)
l1.PushFront(1)
checkList(t, l1, []any{1})
var l2 = new(LockingList)
l2.PushBack(1)
checkList(t, l2, []any{1})
var l3 = new(LockingList)
if err := l3.PushFrontList(l1); err != nil {
t.Errorf("l3.PushFrontList(l1) = %v, want nil", err)
}
checkList(t, l3, []any{1})
var l4 = new(LockingList)
if err := l4.PushBackList(l2); err != nil {
t.Errorf("l4.PushBackList(l2) = %v, want nil", err)
}
checkList(t, l4, []any{1})
}
// Test that a list l is not modified when calling InsertBefore with a mark that is not an element of l.
// Adapted from golang source.
func TestInsertBeforeUnknownMark(t *testing.T) {
t.Parallel()
var l = New()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
_, err := l.InsertBefore(1, new(Element))
if err == nil || !errors.Is(err, ErrMarkNotInList) {
t.Errorf("l.InsertBefore(1, new(Element)) = %v, want ErrMarkNotInList", err)
}
checkList(t, l, []any{1, 2, 3})
}
// TestList tests the list implementation.
// Mostly adapted from golang source
func TestList(t *testing.T) {
l := New()
checkListPointers(t, l, []*list.Element{})
// Single element list
e := l.PushFront("a")
checkListPointers(t, l, []*list.Element{e.Element})
if err := l.MoveToFront(e); err != nil {
t.Errorf("MoveToFront(e) = %v, want nil", err)
}
if err := l.MoveAfter(e, e); err != nil {
t.Errorf("MoveAfter(e, e) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e.Element})
if err := l.MoveToBack(e); err != nil {
t.Errorf("MoveToBack(e) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e.Element})
if err := l.Remove(e); err != nil {
t.Errorf("Remove(e) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{})
// Bigger list
e2 := l.PushFront(2)
e1 := l.PushFront(1)
e3 := l.PushBack(3)
e4 := l.PushBack("banana")
checkListPointers(t, l, []*list.Element{e1.Element, e2.Element, e3.Element, e4.Element})
var err error
if err = l.Remove(e2); err != nil {
t.Errorf("Remove(e2) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e3.Element, e4.Element})
// move from middle
if err = l.MoveToFront(e3); err != nil {
t.Errorf("MoveToFront(e3) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e3.Element, e1.Element, e4.Element})
if err = l.MoveToFront(e1); err != nil {
t.Errorf("MoveToFront(e1) = %v, want nil", err)
}
// move from middle
if err = l.MoveToBack(e3); err != nil {
t.Errorf("MoveToBack(e3) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e3.Element})
// move from back
if err = l.MoveToFront(e3); err != nil {
t.Errorf("MoveToFront(e3) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e3.Element, e1.Element, e4.Element})
// should be no-op
if err = l.MoveToFront(e3); err != nil {
t.Errorf("MoveToFront(e3) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e3.Element, e1.Element, e4.Element})
// move from front
if err = l.MoveToBack(e3); err != nil {
t.Errorf("MoveToBack(e3) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e3.Element})
// should be no-op
if err = l.MoveToBack(e3); err != nil {
t.Errorf("MoveToBack(e3) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e3.Element})
// insert before front
if e2, err = l.InsertBefore(2, e1); err != nil {
t.Errorf("InsertBefore(2, e1) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e2.Element, e1.Element, e4.Element, e3.Element})
if err = l.Remove(e2); err != nil {
t.Errorf("Remove(e2) = %v, want nil", err)
}
// insert before middle
if e2, err = l.InsertBefore(2, e4); err != nil {
t.Errorf("InsertBefore(2, e4) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e2.Element, e4.Element, e3.Element})
if err = l.Remove(e2); err != nil {
t.Errorf("Remove(e2) = %v, want nil", err)
}
// insert before back
if e2, err = l.InsertBefore(2, e3); err != nil {
t.Errorf("InsertBefore(2, e3) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e2.Element, e3.Element})
if err = l.Remove(e2); err != nil {
t.Errorf("Remove(e2) = %v, want nil", err)
}
// insert after front
if e2, err = l.InsertAfter(2, e1); err != nil {
t.Errorf("InsertAfter(2, e1) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e2.Element, e4.Element, e3.Element})
if err = l.Remove(e2); err != nil {
t.Errorf("Remove(e2) = %v, want nil", err)
}
// insert after middle
if e2, err = l.InsertAfter(2, e4); err != nil {
t.Errorf("InsertAfter(2, e4) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e2.Element, e3.Element})
if err = l.Remove(e2); err != nil {
t.Errorf("Remove(e2) = %v, want nil", err)
}
// insert after back
if e2, err = l.InsertAfter(2, e3); err != nil {
t.Errorf("InsertAfter(2, e3) = %v, want nil", err)
}
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e3.Element, e2.Element})
if err = l.Remove(e2); err != nil {
t.Errorf("Remove(e2) = %v, want nil", err)
}
// Check standard iteration.
sum := 0
for e = l.Front(); e != nil; e = e.Next() {
if i, ok := e.Element.Value.(int); ok {
sum += i
}
}
if sum != 4 {
t.Errorf("sum over l = %d, want 4", sum)
}
}
func TestThePlanet(t *testing.T) {
t.Parallel()
var err error
var nl = New()
if nl.Len() != 0 {
t.Errorf("Init() failed to reset list length to 0")
}
nl.PushFront(1)
if nl.Pop() != 1 {
t.Errorf("Pop() failed to return first element")
}
if nl.Len() != 0 {
t.Errorf("Pop() failed to remove first element")
}
if err = nl.Push(1); err != nil {
t.Errorf("Push(1) = %v, want nil", err)
}
nl.l = nil
if err = nl.Push(1); err != nil {
t.Errorf("Push(1) = %v, want %v", err, nil)
}
if nl.Pop() != 1 {
t.Errorf("Pop() = %v, want %v", err, 1)
}
if nl.Pop() != nil {
t.Errorf("Pop() = %v, want %v", err, nil)
}
t.Run("PushPop", func(t *testing.T) {
t.Parallel()
pl := New()
for i := 1; i < 5; i++ {
if err = pl.Push(i); err != nil {
t.Errorf("Push(%d) = %v, want nil", i, err)
}
}
for i := 1; i < 5; i++ {
if got := pl.Pop(); got != i {
t.Errorf("Pop() = %d, want %d", got, i)
}
}
for i := 1; i < 5; i++ {
if err = pl.Push(i); err != nil {
t.Errorf("Push(%d) = %v, want nil", i, err)
}
}
})
t.Run("Concurrent", func(t *testing.T) {
t.Parallel()
cl := New()
wg := &sync.WaitGroup{}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(n int) {
if cerr := cl.Push(n); cerr != nil {
t.Errorf("Push(%d) err = %v, want nil", n, cerr)
}
wg.Done()
}(i)
}
wg.Wait()
if cl.Len() != 1000 {
t.Errorf("Len() = %d, want 1000", cl.Len())
}
for i := 0; i < 1000; i++ {
if cl.Pop() == nil {
t.Errorf("Pop() = %d, want %d", cl.Pop(), i)
}
if cl.Len() != 999-i {
t.Errorf("Len() = %d, want %d", cl.Len(), 999-i)
}
}
})
nl.Init()
for i := 1; i < 5; i++ {
if err = nl.Push(i); err != nil {
t.Errorf("Push(%d) = %v, want nil", i, err)
}
}
for i := 1; i < 100; i++ {
for j := 1; j < 5; j++ {
res := nl.Rotate()
t.Logf("Rotate() = %d", res.Value())
if res.Value() != j {
t.Errorf("Rotate() = %d, want %d", res.Value(), j)
}
if j > 4 && res.Next() == nil {
t.Fatalf("%d.Next is nil", res.Value())
}
if j < 2 && res.Prev() == nil {
t.Errorf("Prev is nil")
}
if res.Next() != nil && res.Next().Value() != j+1 {
t.Errorf("Next = %d, want %d", res.Next().Value(), j+1)
}
if res.Prev() != nil && j-1 != 0 && res.Prev().Value() != j-1 {
t.Errorf("Prev = %d, want %d", res.Prev().Value(), j-1)
}
}
}
t.Run("CoverageStuff", func(t *testing.T) {
nl.RWMutex = nil
if !errors.Is(nl.Lock(), ErrUninitialized) {
t.Errorf("Lock() = %v, want %v", err, ErrUninitialized)
}
var yeet *Element
if yeet, err = nl.InsertAfter(1, nil); !errors.Is(err, ErrUninitialized) {
t.Errorf("InsertAfter(1, nil) = %v, want %v", err, ErrUninitialized)
}
if yeet.Next() != nil {
t.Errorf("Next() = %v, want %v", yeet.Next(), nil)
}
if yeet.Prev() != nil {
t.Errorf("Prev() = %v, want %v", yeet.Prev(), nil)
}
if _, err = nl.InsertBefore(1, nil); !errors.Is(err, ErrUninitialized) {
t.Errorf("InsertAfter(1, nil) = %v, want %v", err, ErrUninitialized)
}
if err = nl.Remove(nil); !errors.Is(err, ErrUninitialized) {
t.Errorf("Remove(nil) = %v, want %v", err, ErrUninitialized)
}
if err = nl.Push(1); !errors.Is(err, ErrUninitialized) {
t.Errorf("Push(1) = %v, want %v", err, ErrUninitialized)
}
if e := nl.PushFront(1); e != nil {
t.Errorf("PushFront(1) = %v, want %v", err, ErrUninitialized)
}
nl.l = nil
nl.Rotate()
if nl.wrapElement(nil) != nil {
t.Errorf("wrapElement(e) = %v, want nil", err)
}
if nl.wrapElement(nil).Value() != nil {
t.Errorf("wrapElement(e).Value() = %v, want nil", err)
}
el := &Element{}
if el.Value() != nil {
t.Errorf("el.Value() = %v, want nil", err)
}
if _, err = nl.InsertAfter(1, nil); err == nil {
t.Errorf("InsertAfter(1, nil) = %v, want error", err)
}
nl.Init()
nl.Init()
nl.l = nil
if !errors.Is(nl.Remove(nil), ErrUninitialized) {
t.Errorf("Remove(nil) = %v, want %v", err, ErrUninitialized)
}
nl.Init()
_ = nl.Push(1)
_ = nl.Push(2)
if nl.Back().Value() == 1 {
t.Errorf("Back() = %v, want %v", err, 1)
}
})
// Clear all elements by iterating
for nl.Len() > 0 {
nl.Pop()
}
checkListPointers(t, nl, []*list.Element{})
}

45
network/range.go Normal file

@ -0,0 +1,45 @@
package network
import ipa "inet.af/netaddr"
/*IterateNetRange will ingest:
- an inet.af/netaddr.Range
- an inet.af/netaddr.Prefix
- or a string to be parsed as either of the above options
- examples: (192.168.69.0/24) (192.168.69.0-192.168.69.254)
then it returns a channel that will stream all the individual netaddr.IP types within the given range or prefix.
if the input is invalid this function will return nil.*/
func IterateNetRange(ips interface{}) chan ipa.IP {
var addrs ipa.IPRange
switch ips.(type) {
case string:
strefix, prefixErr := ipa.ParseIPPrefix(ips.(string))
strange, rangeErr := ipa.ParseIPRange(ips.(string))
switch {
case rangeErr == nil:
addrs = strange
case prefixErr == nil:
addrs = strefix.Range()
default:
return nil
}
case ipa.IPRange:
addrs = ips.(ipa.IPRange)
case ipa.IPPrefix:
addrs = ips.(ipa.IPPrefix).Range()
default:
return nil
}
ch := make(chan ipa.IP)
go func(ret chan ipa.IP) {
for head := addrs.From(); head != addrs.To(); head = head.Next() {
if !head.IsUnspecified() {
ret <- head
}
}
}(ch)
return ch
}

109
network/range_test.go Normal file

@ -0,0 +1,109 @@
package network
import (
"bufio"
"strings"
"testing"
ipa "inet.af/netaddr"
)
var testdata29 string = `
192.168.69.240
192.168.69.241
192.168.69.242
192.168.69.243
192.168.69.244
192.168.69.245
192.168.69.246
192.168.69.247
`
var test29str = "192.168.69.240/29"
var test29rangestr = "192.168.69.240-192.168.69.247"
var test29 []*ipa.IP
func init() {
test29 = nil
xerox := bufio.NewScanner(strings.NewReader(testdata29))
for xerox.Scan() {
if line := xerox.Text(); len(line) < 1 {
continue
}
ip := ipa.MustParseIP(xerox.Text())
test29 = append(test29, &ip)
}
}
func TestIterateNetRange(t *testing.T) {
type args struct {
ips interface{}
}
type test struct {
name string
args args
want []*ipa.IP
}
var tests = []test{
{
name: "prefix",
args: args{ips: ipa.MustParseIPPrefix(test29str)},
want: test29,
},
{
name: "range",
args: args{ips: ipa.MustParseIPRange(test29rangestr)},
want: test29,
},
{
name: "stringprefix",
args: args{ips: test29str},
want: test29,
},
{
name: "stringrange",
args: args{ips: test29rangestr},
want: test29,
},
{
name: "bogus",
args: args{ips: "whatever, man. I'm just trynt'a vibe."},
want: nil,
},
{
name: "evenboguser",
args: args{ips: int(5)},
want: nil,
},
}
for _, tt := range tests {
index := 0
retchan := IterateNetRange(tt.args.ips)
if tt.want == nil && retchan != nil {
t.Fatalf("return should have been nil, it was %v", retchan)
}
if retchan == nil {
continue
}
t.Logf("test: %s", tt.name)
mainloop:
for {
select {
case ip := <-retchan:
if ip.String() != test29[index].String() {
t.Errorf("[%s] failed, wanted %s, got %s", tt.name, tt.want[index].String(), ip.String())
} else {
t.Logf("[%s] success (%s == %s)", tt.name, tt.want[index].String(), ip.String())
}
index++
default:
if index == 7 {
break mainloop
}
}
}
}
}

@ -1,98 +0,0 @@
# pool
`import git.tcp.direct/kayos/common/pool`
## Overview
pool contains two components, both of which are forms of buffer pools.
### *BufferFactory*
BufferFactory is a potentially safer sync.Pool of [bytes.Buffer](https://pkg.go.dev/bytes#Buffer) types that will not allow you to accidentally re-use buffers after you return them to the pool.
- `func NewBufferFactory() BufferFactory`
- `func NewSizedBufferFactory(size int) BufferFactory`
- `func (cf BufferFactory) Get() *Buffer`
- `func (cf BufferFactory) MustPut(buf *Buffer)`
- `func (cf BufferFactory) Put(buf *Buffer) error`
### *StringFactory*
StringFactory is very much like BufferFactory, except for it's a pool of [strings.Builder](https://pkg.go.dev/strings#Builder) types instead.
- `func NewStringFactory() StringFactory`
- `func (sf StringFactory) Get() *String`
- `func (sf StringFactory) MustPut(buf *String)`
- `func (sf StringFactory) Put(buf *String) error`
## Benchmarks
#### foreword
In some usecases, this package will actually allocate slightly more than if one were not using it. That is because each Buffer or String type has the additional header of a [sync.Once](https://pkg.go.dev/sync#Once). The overhead of this is minimal, and in the benchmarks I've put together that attempt to emulate a more realistic use case, the benefits are easy to see. This is comparing it to use without buffer pools at all. Using [sync.Pool](https://pkg.go.dev/sync#Pool) alone without this package will always be ever-so-slightly more efficient, but the consequences of mis-using it could be catastrophic. That is the trade-off we make.
_Note: "bytes" here refers to the size of buffer pre-allocation. Either with NewSizedBufferFactory or bytes.NewBuffer_
---
### Using this package
```
BenchmarkBufferFactory/SingleProc-64-bytes-24 410792 3467 ns/op 24685 B/op 5 allocs/op
BenchmarkBufferFactory/SingleProc-1024-bytes-24 376539 3668 ns/op 24687 B/op 5 allocs/op
BenchmarkBufferFactory/SingleProc-4096-bytes-24 281402 3599 ns/op 24688 B/op 5 allocs/op
BenchmarkBufferFactory/SingleProc-65536-bytes-24 340872 3591 ns/op 24830 B/op 5 allocs/op
```
```
BenchmarkBufferFactory/Concurrent-x2-64-bytes-24 498634 2076 ns/op 24678 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x2-1024-bytes-24 569001 2128 ns/op 24684 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x2-4096-bytes-24 602946 2131 ns/op 24688 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x2-65536-bytes-24 779770 1522 ns/op 24747 B/op 5 allocs/op
```
```
BenchmarkBufferFactory/Concurrent-x4-64-bytes-24 319677 3271 ns/op 24695 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x4-1024-bytes-24 597859 2005 ns/op 24677 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x4-4096-bytes-24 586940 2092 ns/op 24685 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x4-65536-bytes-24 861326 1420 ns/op 24719 B/op 5 allocs/op
```
```
BenchmarkBufferFactory/Concurrent-x8-64-bytes-24 621253 1893 ns/op 24672 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x8-1024-bytes-24 626067 2155 ns/op 24678 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x8-4096-bytes-24 644078 2030 ns/op 24682 B/op 5 allocs/op
BenchmarkBufferFactory/Concurrent-x8-65536-bytes-24 878794 1382 ns/op 24718 B/op 5 allocs/op
```
---
### Allocating new bytes.Buffers
```
BenchmarkNotUsingPackage/SingleProc-64-bytes-24 121978 9401 ns/op 52000 B/op 5 allocs/op
BenchmarkNotUsingPackage/SingleProc-1024-bytes-24 129132 9004 ns/op 52000 B/op 5 allocs/op
BenchmarkNotUsingPackage/SingleProc-4096-bytes-24 138590 8894 ns/op 52000 B/op 5 allocs/op
BenchmarkNotUsingPackage/SingleProc-65536-bytes-24 134730 8891 ns/op 52000 B/op 5 allocs/op
```
```
BenchmarkNotUsingPackage/Concurrent-x2-64-bytes-24 150890 8488 ns/op 52225 B/op 8 allocs/op
BenchmarkNotUsingPackage/Concurrent-x2-1024-bytes-24 174734 8924 ns/op 55937 B/op 7 allocs/op
BenchmarkNotUsingPackage/Concurrent-x2-4096-bytes-24 101695 11032 ns/op 65537 B/op 6 allocs/op
BenchmarkNotUsingPackage/Concurrent-x2-65536-bytes-24 43708 28977 ns/op 286724 B/op 5 allocs/op
```
```
BenchmarkNotUsingPackage/Concurrent-x4-64-bytes-24 189124 6314 ns/op 52224 B/op 8 allocs/op
BenchmarkNotUsingPackage/Concurrent-x4-1024-bytes-24 144324 7010 ns/op 55937 B/op 7 allocs/op
BenchmarkNotUsingPackage/Concurrent-x4-4096-bytes-24 129194 9325 ns/op 65537 B/op 6 allocs/op
BenchmarkNotUsingPackage/Concurrent-x4-65536-bytes-24 43641 28730 ns/op 286723 B/op 5 allocs/op
```
```
BenchmarkNotUsingPackage/Concurrent-x8-64-bytes-24 184123 5911 ns/op 52224 B/op 8 allocs/op
BenchmarkNotUsingPackage/Concurrent-x8-1024-bytes-24 172016 6602 ns/op 55937 B/op 7 allocs/op
BenchmarkNotUsingPackage/Concurrent-x8-4096-bytes-24 129916 9030 ns/op 65537 B/op 6 allocs/op
BenchmarkNotUsingPackage/Concurrent-x8-65536-bytes-24 45729 26344 ns/op 286723 B/op 5 allocs/op
```

@ -1,455 +0,0 @@
package pool
import (
"bytes"
"errors"
"io"
"sync"
)
// BufferFactory is a factory for creating and reusing bytes.Buffers.
// BufferFactory tries to be safer than using a sync.Pool directly by ensuring that the buffer is not returned twice.
type BufferFactory struct {
pool *sync.Pool
}
// NewBufferFactory creates a new BufferFactory that creates new buffers on demand.
func NewBufferFactory() BufferFactory {
return BufferFactory{
pool: &sync.Pool{
New: func() any { return new(bytes.Buffer) },
},
}
}
// NewSizedBufferFactory creates a new BufferFactory that creates new buffers of the given size on demand.
func NewSizedBufferFactory(size int) BufferFactory {
return BufferFactory{
pool: &sync.Pool{
New: func() any { return bytes.NewBuffer(make([]byte, size)) },
},
}
}
// Put returns the buffer to the pool. It returns an error if the buffer has already been returned to the pool.
func (cf BufferFactory) Put(buf *Buffer) error {
var err = ErrBufferReturned
buf.o.Do(func() {
_ = buf.Reset()
cf.pool.Put(buf.Buffer)
buf.Buffer = nil
err = nil
})
return err
}
// MustPut is the same as Put but panics if the buffer has already been returned to the pool.
func (cf BufferFactory) MustPut(buf *Buffer) {
if err := cf.Put(buf); err != nil {
panic(err)
}
}
// Get returns a buffer from the pool.
func (cf BufferFactory) Get() *Buffer {
return &Buffer{
Buffer: cf.pool.Get().(*bytes.Buffer),
o: &sync.Once{},
}
}
// Buffer is a wrapper around bytes.Buffer that can only be returned to a pool once.
type Buffer struct {
*bytes.Buffer
o *sync.Once
co *sync.Once
p *BufferFactory
}
// WithParent sets the parent of the buffer. This is useful for chaining factories, and for facilitating
// in-line buffer return with functions like Buffer.Close(). Be mindful, however, that this adds a bit of overhead.
func (c Buffer) WithParent(p *BufferFactory) *Buffer {
c.p = p
c.co = &sync.Once{}
return &c
}
// Bytes returns a slice of length b.Len() holding the unread portion of the buffer.
// The slice is valid for use only until the next buffer modification (that is,
// only until the next call to a method like Read, Write, Reset, or Truncate).
// The slice aliases the buffer content at least until the next buffer modification,
// so immediate changes to the slice will affect the result of future reads.
//
// *This is from the bytes.Buffer docs.*
func (c Buffer) Bytes() []byte {
if c.Buffer == nil {
return nil
}
return c.Buffer.Bytes()
}
// MustBytes is the same as Bytes but panics if the buffer has already been returned to the pool.
func (c Buffer) MustBytes() []byte {
if c.Buffer == nil {
panic(ErrBufferReturned)
}
return c.Buffer.Bytes()
}
// String returns the contents of the unread portion of the buffer
// as a string. If the Buffer is a nil pointer, it returns "<nil>".
//
// To build strings more efficiently, see the strings.Builder type.
//
// *This is from the bytes.Buffer docs.*
func (c Buffer) String() string {
if c.Buffer == nil {
return ""
}
return c.Buffer.String()
}
// MustString is the same as String but panics if the buffer has already been returned to the pool.
func (c Buffer) MustString() string {
if c.Buffer == nil {
panic(ErrBufferReturned)
}
return c.Buffer.String()
}
// Reset resets the buffer to be empty,
// but it retains the underlying storage for use by future writes.
// Reset is the same as Truncate(0).
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) Reset() error {
if c.Buffer == nil {
return ErrBufferReturned
}
c.Buffer.Reset()
return nil
}
// MustReset is the same as Reset but panics if the buffer has already been returned to the pool.
func (c Buffer) MustReset() {
if err := c.Reset(); err != nil {
panic(err)
}
c.Buffer.Reset()
}
// Len returns the number of bytes of the unread portion of the buffer;
// b.Len() == len(b.Bytes()).
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns 0 if the buffer has already been returned to the pool.
func (c Buffer) Len() int {
if c.Buffer == nil {
return 0
}
return c.Buffer.Len()
}
// MustLen is the same as Len but panics if the buffer has already been returned to the pool.
func (c Buffer) MustLen() int {
if c.Buffer == nil {
panic(ErrBufferReturned)
}
return c.Buffer.Len()
}
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) Write(p []byte) (int, error) {
if c.Buffer == nil {
return 0, ErrBufferReturned
}
return c.Buffer.Write(p)
}
// MustWrite is the same as Write but panics if the buffer has already been returned to the pool.
func (c Buffer) MustWrite(p []byte) {
if _, err := c.Write(p); err != nil {
panic(err)
}
}
// WriteRune appends the UTF-8 encoding of Unicode code point r to the
// buffer, returning its length and an error, which is always nil but is
// included to match bufio.Writer's WriteRune. The buffer is grown as needed;
// if it becomes too large, WriteRune will panic with ErrTooLarge.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) WriteRune(r rune) (int, error) {
if c.Buffer == nil {
return 0, ErrBufferReturned
}
return c.Buffer.WriteRune(r)
}
// MustWriteRune is the same as WriteRune but panics if the buffer has already been returned to the pool.
func (c Buffer) MustWriteRune(r rune) {
if _, err := c.WriteRune(r); err != nil {
panic(err)
}
}
// WriteByte appends the byte c to the buffer, growing the buffer as needed.
// The returned error is always nil, but is included to match bufio.Writer's
// WriteByte. If the buffer becomes too large, WriteByte will panic with
// ErrTooLarge.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) WriteByte(cyte byte) error {
if c.Buffer == nil {
return ErrBufferReturned
}
return c.Buffer.WriteByte(cyte)
}
// MustWriteByte is the same as WriteByte but panics if the buffer has already been returned to the pool.
func (c Buffer) MustWriteByte(cyte byte) {
if err := c.WriteByte(cyte); err != nil {
panic(err)
}
}
// WriteString appends the contents of s to the buffer, growing the buffer as
// needed. The return value n is the length of s; err is always nil. If the
// buffer becomes too large, WriteString will panic with ErrTooLarge.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) WriteString(str string) (int, error) {
if c.Buffer == nil {
return 0, ErrBufferReturned
}
return c.Buffer.WriteString(str)
}
// Grow grows the buffer's capacity, if necessary, to guarantee space for another n bytes.
// After Grow(n), at least n bytes can be written to the buffer without another allocation.
// If n is negative, Grow will panic. If the buffer can't grow it will panic with ErrTooLarge.
//
// If the buffer has already been returned to the pool, Grow will return ErrBufferReturned.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) Grow(n int) error {
if c.Buffer == nil {
return ErrBufferReturned
}
c.Buffer.Grow(n)
return nil
}
// Cap returns the capacity of the buffer's underlying byte slice, that is, the
// total space allocated for the buffer's data.
//
// *This is from the bytes.Buffer docs.*
// If the buffer has already been returned to the pool, Cap will return 0.
func (c Buffer) Cap() int {
if c.Buffer == nil {
return 0
}
return c.Buffer.Cap()
}
// Truncate discards all but the first n unread bytes from the buffer
// but continues to use the same allocated storage.
// It panics if n is negative or greater than the length of the buffer.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) Truncate(n int) error {
if c.Buffer == nil {
return ErrBufferReturned
}
c.Buffer.Truncate(n)
return nil
}
// MustTruncate is the same as Truncate but panics if the buffer has already been returned to the pool.
func (c Buffer) MustTruncate(n int) {
if err := c.Truncate(n); err != nil {
panic(err)
}
}
// ReadFrom reads data from r until EOF and appends it to the buffer, growing
// the buffer as needed. The return value n is the number of bytes read. Any
// error except io.EOF encountered during the read is also returned. If the
// buffer becomes too large, ReadFrom will panic with ErrTooLarge.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) ReadFrom(r io.Reader) (int64, error) {
if c.Buffer == nil {
return 0, ErrBufferReturned
}
return c.Buffer.ReadFrom(r)
}
// MustReadFrom is the same as ReadFrom but panics if the buffer has already been returned to the pool.
func (c Buffer) MustReadFrom(r io.Reader) {
if _, err := c.ReadFrom(r); err != nil {
panic(err)
}
}
// WriteTo writes data to w until the buffer is drained or an error occurs.
// The return value n is the number of bytes written; it always fits into an
// int, but it is int64 to match the io.WriterTo interface. Any error
// encountered during the write is also returned.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) WriteTo(w io.Writer) (int64, error) {
if c.Buffer == nil {
return 0, ErrBufferReturned
}
return c.Buffer.WriteTo(w)
}
// MustWriteTo is the same as WriteTo but panics if the buffer has already been returned to the pool.
func (c Buffer) MustWriteTo(w io.Writer) {
if _, err := c.WriteTo(w); err != nil {
panic(err)
}
}
// Read reads the next len(p) bytes from the buffer or until the buffer
// is drained. The return value n is the number of bytes read. If the
// buffer has no data to return, err is io.EOF (unless len(p) is zero);
// otherwise it is nil.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) Read(p []byte) (int, error) {
if c.Buffer == nil {
return 0, ErrBufferReturned
}
return c.Buffer.Read(p)
}
// ReadByte reads and returns the next byte from the buffer.
// If no byte is available, it returns error io.EOF.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) ReadByte() (byte, error) {
if c.Buffer == nil {
return 0, ErrBufferReturned
}
return c.Buffer.ReadByte()
}
// ReadRune reads and returns the next UTF-8-encoded
// Unicode code point from the buffer.
// If no bytes are available, the error returned is io.EOF.
// If the bytes are an erroneous UTF-8 encoding, it
// consumes one byte and returns U+FFFD, 1.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) ReadRune() (rune, int, error) {
if c.Buffer == nil {
return 0, 0, ErrBufferReturned
}
return c.Buffer.ReadRune()
}
// UnreadByte unreads the last byte returned by the most recent successful
// read operation that read at least one byte. If a write has happened since
// the last read, if the last read returned an error, or if the read read zero
// bytes, UnreadByte returns an error.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) UnreadByte() error {
if c.Buffer == nil {
return ErrBufferReturned
}
return c.Buffer.UnreadByte()
}
// UnreadRune unreads the last rune returned by ReadRune.
// If the most recent read or write operation on the buffer was
// not a successful ReadRune, UnreadRune returns an error. (In this regard
// it is stricter than UnreadByte, which will unread the last byte
// from any read operation.)
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) UnreadRune() error {
if c.Buffer == nil {
return ErrBufferReturned
}
return c.Buffer.UnreadRune()
}
// ReadBytes reads until the first occurrence of delim in the input,
// returning a slice containing the data up to and including the delimiter.
// If ReadBytes encounters an error before finding a delimiter,
// it returns the data read before the error and the error itself (often io.EOF).
// ReadBytes returns err != nil if and only if the returned data does not end in
// delim.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns an error if the buffer has already been returned to the pool.
func (c Buffer) ReadBytes(delim byte) ([]byte, error) {
if c.Buffer == nil {
return nil, ErrBufferReturned
}
return c.Buffer.ReadBytes(delim)
}
// Next returns a slice containing the next n bytes from the buffer,
// advancing the buffer as if the bytes had been returned by Read.
// If there are fewer than n bytes in the buffer, Next returns the entire buffer.
// The slice is only valid until the next call to a read or write method.
//
// *This is from the bytes.Buffer docs.*
// This wrapper returns nil if the buffer has already been returned to the pool.
func (c Buffer) Next(n int) []byte {
if c.Buffer == nil {
return nil
}
return c.Buffer.Next(n)
}
// IsClosed returns true if the buffer has been returned to the pool.
func (c Buffer) IsClosed() bool {
var closed = true
if c.co == nil {
c.co = &sync.Once{}
}
c.co.Do(func() {
closed = false
})
return closed
}
// Close implements io.Closer. It returns the buffer to the pool. This
func (c Buffer) Close() error {
if c.Buffer == nil {
return errors.New("buffer already returned to pool")
}
if c.p == nil {
return errors.New(
"buffer does not know it's parent pool and therefore cannot return itself, use Buffer.WithParent",
)
}
var err = ErrBufferReturned
c.co.Do(func() {
err = c.p.Put(&c)
})
return err
}

@ -1,173 +0,0 @@
package pool
import (
"bytes"
"fmt"
"os"
"strings"
"testing"
)
// =========================================================================
// BenchmarkBufferFactory tries to emulate real world usage of a buffer pool.
// It creates a buffer, writes to it, and then returns it to the pool.
//
// Then it repeats this process b.N times.
// This should be a decent way to test the performance of a buffer pool.
//
// See bytes_bench.go for more information.
func BenchmarkBufferFactory(b *testing.B) {
benchmarkBufferFactory(b)
}
// BenchmarkNotUsingPackage is a benchmark that does not use git.tcp.direct/kayos/common/pool.
// It mimics the behavior of the BufferFactory benchmark, but does not use a buffer pool.
//
// See bytes_test.go for more information.
func BenchmarkNotUsingPackage(b *testing.B) {
benchmarkNewBytesBuffer(b)
}
// =========================================================================
const (
hello64 = `its me ur new best friend tell me a buf i tell you where it ends`
hello = `hello world, it's me, your new best friend. tell me the buffer and i'll tell you where it ends.`
lip = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ante sit amet purus blandit auctor. Nullam ornare enim sed nibh consequat molestie. Duis est lectus, vestibulum vel felis vel, convallis cursus ex. Morbi nec placerat orci. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent a erat sit amet libero convallis ornare a venenatis dolor. Pellentesque euismod risus et metus porttitor, vel consectetur lacus tempus. Integer elit arcu, condimentum quis nisi eget, dapibus imperdiet nulla. Cras sit amet ante in urna varius tempus. Integer tristique sagittis nunc vel tincidunt. Integer non suscipit ligula, et fermentum sem. Duis id odio lorem. Sed id placerat urna, eu vehicula risus. Duis porttitor hendrerit risus. Curabitur id tellus ac arcu aliquet finibus. Pellentesque et nisl ante. Mauris sapien nisl, pretium in ligula tempus, posuere mattis turpis. Proin et tempus enim. Nullam at diam est. Vivamus ut lectus hendrerit, interdum ex id, ultricies sapien. Praesent rhoncus turpis dolor, quis lobortis tortor pellentesque id. Pellentesque eget nisi laoreet, fringilla augue eu, cursus risus. Integer consectetur ornare laoreet. Praesent ligula sem, tincidunt at ligula at, condimentum venenatis tortor. Nam laoreet enim leo, sed finibus lorem egestas vel. Maecenas varius a leo non placerat. Donec scelerisque, risus vel finibus ornare, arcu ligula interdum justo, in ultricies urna mi et neque. Curabitur sed sem dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas eget laoreet nisi. Nam rhoncus sapien ac interdum sagittis. Nulla fermentum sem nec tellus dignissim lacinia. Curabitur ornare lectus non dictum laoreet. Praesent tempor risus at tortor tempor finibus. Cras id dolor mi. Mauris ut mi quis est vehicula molestie. Mauris eu varius urna. Integer sodales nunc at risus rutrum eleifend. In sed bibendum lectus. Morbi ipsum sapien, blandit in dignissim eu, ultrices non odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eget volutpat ligula, at elementum dui. Aliquam sed enim scelerisque, facilisis magna vitae, dignissim enim. Pellentesque non ultricies urna. Proin fermentum erat semper efficitur auctor. Vestibulum posuere non tortor vitae tincidunt.`
)
var lipTenIcedTea = strings.Repeat(lip, 10)
func poolbench(f BufferFactory) {
buf := f.Get()
buf.MustWrite([]byte(hello64))
f.MustPut(buf)
buf = f.Get()
buf.MustWrite([]byte(hello))
f.MustPut(buf)
buf = f.Get()
buf.MustWrite([]byte(lip))
f.MustPut(buf)
buf = f.Get()
buf.MustWrite([]byte(lipTenIcedTea))
f.MustPut(buf)
}
func parabench(pb *testing.PB, f BufferFactory) {
for pb.Next() {
poolbench(f)
}
}
func sized(b *testing.B, size int, para int) {
b.ReportAllocs()
f := NewBufferFactory()
if size != 0 {
f = NewSizedBufferFactory(size)
}
if para != 0 {
b.SetParallelism(para)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { parabench(pb, f) })
return
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
poolbench(f)
}
}
// ------------------------------------------------------------
// ------------------------------------------------------------
// ------------------------------------------------------------
func sizedbytesbench(initial func() []byte) {
buf := bytes.NewBuffer(initial())
buf.Write([]byte(hello64))
buf = bytes.NewBuffer(initial())
buf.Write([]byte(hello))
buf = bytes.NewBuffer(initial())
buf.Write([]byte(lip))
buf = bytes.NewBuffer(initial())
buf.Write([]byte(lipTenIcedTea))
}
func parabytesbench(pb *testing.PB, size int) {
for pb.Next() {
if size != 0 {
sizedbytesbench(func() []byte { return make([]byte, 0, size) })
} else {
sizedbytesbench(func() []byte { return nil })
}
}
}
func bytessized(b *testing.B, size int, para int) {
b.ReportAllocs()
if para != 0 {
b.SetParallelism(para)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { parabytesbench(pb, size) })
return
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
sizedbytesbench(func() []byte { return nil })
}
}
var divier = []byte(strings.Repeat("-", 130) + "\n")
func bigDivide(n int) {
if n > 1 {
_, _ = os.Stdout.Write([]byte{'\n'})
}
for i := 0; i < n; i++ {
_, _ = os.Stdout.Write(divier)
}
_, _ = os.Stdout.Write([]byte{'\n'})
}
func benchmarkBufferFactory(b *testing.B) {
b.ReportAllocs()
concurrency := []int{0, 2, 4, 8}
size := []int{64, 1024, 4096, 65536}
defer bigDivide(2)
for _, c := range concurrency {
for _, s := range size {
label := fmt.Sprintf("Concurrent-x%d-%d-bytes", c, s)
if c == 0 {
label = fmt.Sprintf("SingleProc-%d-bytes", s)
}
b.Run(label, func(b *testing.B) { sized(b, s, c) })
}
_, _ = os.Stdout.Write(divier)
}
}
func benchmarkNewBytesBuffer(b *testing.B) {
b.ReportAllocs()
concurrency := []int{0, 2, 4, 8}
size := []int{64, 1024, 4096, 65536}
defer bigDivide(1)
for _, c := range concurrency {
for _, s := range size {
label := fmt.Sprintf("Concurrent-x%d-%d-bytes", c, s)
if c == 0 {
label = fmt.Sprintf("SingleProc-%d-bytes", s)
}
b.Run(label, func(b *testing.B) { bytessized(b, s, c) })
}
_, _ = os.Stdout.Write(divier)
}
}

@ -1,584 +0,0 @@
package pool
import (
"bytes"
"io"
"strings"
"testing"
)
func TestNewBufferFactory(t *testing.T) {
bf := NewBufferFactory()
if bf.pool == nil {
t.Fatalf("The pool is nil")
}
}
func TestBufferFactory(t *testing.T) {
bf := NewBufferFactory()
t.Run("BufferPut", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
if err := bf.Put(buf); err != nil {
t.Fatalf("The buffer was not returned: %v", err)
}
if err := bf.Put(buf); err == nil {
t.Fatalf("The buffer was returned twice")
}
})
t.Run("BufferMustPut", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
bf.MustPut(buf)
assertPanic(t, func() {
bf.MustPut(buf)
})
})
t.Run("BufferFactoryGet", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
if buf.Buffer == nil {
t.Fatalf("The buffer is nil")
}
if buf.o == nil {
t.Fatalf("The once is nil")
}
})
t.Run("BufferBytes", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
if len(buf.Bytes()) != 0 {
t.Fatalf("The bytes are not nil: %v", buf.Bytes())
}
buf.MustWrite([]byte("hello world"))
if !bytes.Equal(buf.MustBytes(), []byte("hello world")) {
t.Fatalf("The bytes are wrong")
}
bf.MustPut(buf)
if buf.Bytes() != nil {
t.Fatalf("The bytes are not nil")
}
})
t.Run("BufferMustBytes", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
_, err := buf.Write([]byte("hello"))
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf.MustBytes(), []byte("hello")) {
t.Fatalf("The bytes are not equal")
}
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustBytes()
})
})
t.Run("BufferString", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
if buf.String() != "" {
t.Fatalf("The string is not empty")
}
bf.MustPut(buf)
if buf.String() != "" {
t.Fatalf("The string is not empty")
}
})
t.Run("BufferMustString", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
_ = buf.MustString()
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustString()
})
})
t.Run("BufferLen", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
if buf.Len() != 0 {
t.Fatalf("The length is not zero")
}
bf.MustPut(buf)
if buf.Len() != 0 {
t.Fatalf("The length is not zero")
}
})
t.Run("BufferMustLen", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
_ = buf.MustLen()
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustLen()
})
})
t.Run("BufferCap", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
_ = buf.Cap()
bf.MustPut(buf)
if buf.Cap() != 0 {
t.Fatalf("The capacity is not zero")
}
})
t.Run("BufferReset", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
err := buf.Reset()
if err != nil {
t.Fatal(err)
}
if buf.Len() != 0 {
t.Fatalf("The length is not zero")
}
bf.MustPut(buf)
})
t.Run("BufferMustReset", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
buf.MustReset()
if buf.Len() != 0 {
t.Fatalf("The length is not zero")
}
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustReset()
})
})
t.Run("BufferWrite", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
_, err := buf.Write([]byte("hello"))
if err != nil {
t.Fatal(err)
}
if buf.Len() != 5 {
t.Fatalf("The length is not five")
}
bf.MustPut(buf)
written, werr := buf.Write([]byte("hello"))
if written != 0 {
t.Fatalf("The written is not zero")
}
if werr == nil {
t.Fatalf("The error is nil")
}
})
t.Run("BufferMustWrite", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
if buf.Len() != 5 {
t.Fatalf("The length is not five")
}
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustWrite([]byte("hello"))
})
})
t.Run("BufferWriteByte", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
err := buf.WriteByte('h')
if err != nil {
t.Fatal(err)
}
if buf.Len() != 1 {
t.Fatalf("The length is not one")
}
bf.MustPut(buf)
werr := buf.WriteByte('h')
if werr == nil {
t.Fatalf("The error is nil")
}
})
t.Run("BufferMustWriteByte", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWriteByte('h')
if buf.Len() != 1 {
t.Fatalf("The length is not one")
}
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustWriteByte('h')
})
})
t.Run("BufferWriteRune", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
_, err := buf.WriteRune('h')
if err != nil {
t.Fatal(err)
}
if buf.Len() != 1 {
t.Fatalf("The length is not one")
}
bf.MustPut(buf)
written, werr := buf.WriteRune('h')
if written != 0 {
t.Fatalf("The written is not zero")
}
if werr == nil {
t.Fatalf("The error is nil")
}
})
t.Run("BufferMustWriteRune", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWriteRune('h')
if buf.Len() != 1 {
t.Fatalf("The length is not one")
}
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustWriteRune('h')
})
})
t.Run("BufferWriteString", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
_, err := buf.WriteString("hello")
if err != nil {
t.Fatal(err)
}
if buf.Len() != 5 {
t.Fatalf("The length is not five")
}
bf.MustPut(buf)
written, werr := buf.WriteString("hello")
if written != 0 {
t.Fatalf("The written is not zero")
}
if werr == nil {
t.Fatalf("The error is nil")
}
})
t.Run("BufferGrow", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
err := buf.Grow(5)
if buf.Cap() < 5 {
t.Fatalf("The capacity is less than five: %d", buf.Cap())
}
if err != nil {
t.Fatal(err)
}
bf.MustPut(buf)
if buf.Cap() != 0 {
t.Fatalf("The capacity is not zero")
}
if err = buf.Grow(1); err == nil {
t.Fatal("The error is nil")
}
})
t.Run("BufferTruncate", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
err := buf.Truncate(3)
if err != nil {
t.Fatal(err)
}
if buf.Len() != 3 {
t.Fatalf("The length is not three")
}
if buf.String() != "hel" {
t.Fatalf("The string is not hel")
}
bf.MustPut(buf)
})
t.Run("BufferMustTruncate", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
buf.MustTruncate(3)
if buf.Len() != 3 {
t.Fatalf("The length is not three")
}
if buf.String() != "hel" {
t.Fatalf("The string is not hel")
}
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustTruncate(3)
})
})
t.Run("BufferRead", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
p := make([]byte, 5)
n, err := buf.Read(p)
if err != nil {
t.Fatal(err)
}
if n != 5 {
t.Fatalf("The n is not five")
}
if string(p) != "hello" {
t.Fatalf("The string is not hello")
}
bf.MustPut(buf)
if _, err = buf.Read(p); err == nil {
t.Fatal("The error is nil after returning the buffer")
}
})
t.Run("BufferReadByte", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
b, err := buf.ReadByte()
if err != nil {
t.Fatal(err)
}
if b != 'h' {
t.Fatalf("The byte is not h")
}
bf.MustPut(buf)
if _, err = buf.ReadByte(); err == nil {
t.Fatal("The error is nil after returning the buffer")
}
})
t.Run("BufferReadRune", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
r, size, err := buf.ReadRune()
if err != nil {
t.Fatal(err)
}
if r != 'h' {
t.Fatalf("The rune is not h")
}
if size != 1 {
t.Fatalf("The size is not one")
}
bf.MustPut(buf)
if _, _, err = buf.ReadRune(); err == nil {
t.Fatal("The error is nil after returning the buffer")
}
})
t.Run("BufferUnreadByte", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
b, err := buf.ReadByte()
if err != nil {
t.Fatal(err)
}
if b != 'h' {
t.Fatalf("The byte is not h")
}
err = buf.UnreadByte()
if err != nil {
t.Fatal(err)
}
b, err = buf.ReadByte()
if err != nil {
t.Fatal(err)
}
if b != 'h' {
t.Fatalf("The byte is not h")
}
bf.MustPut(buf)
if err = buf.UnreadByte(); err == nil {
t.Fatal("The error is nil after returning the buffer")
}
})
t.Run("BufferUnreadRune", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
r, size, err := buf.ReadRune()
if err != nil {
t.Fatal(err)
}
if r != 'h' {
t.Fatalf("The rune is not h")
}
if size != 1 {
t.Fatalf("The size is not one")
}
err = buf.UnreadRune()
if err != nil {
t.Fatal(err)
}
r, size, err = buf.ReadRune()
if err != nil {
t.Fatal(err)
}
if r != 'h' {
t.Fatalf("The rune is not h")
}
if size != 1 {
t.Fatalf("The size is not one")
}
bf.MustPut(buf)
if err = buf.UnreadRune(); err == nil {
t.Fatal("The error is nil after returning the buffer")
}
})
t.Run("BufferReadBytes", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello!"))
p, err := buf.ReadBytes('o')
if err != nil {
t.Fatal(err)
}
if string(p) != "hello" {
t.Fatalf("The string is not hello: %v", string(p))
}
bf.MustPut(buf)
if _, err = buf.ReadBytes('l'); err == nil {
t.Fatal("The error is nil after returning the buffer")
}
})
t.Run("BufferReadFrom", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
_, err := buf.ReadFrom(strings.NewReader("hello"))
if err != nil {
t.Fatal(err)
}
if buf.Len() != 5 {
t.Fatalf("The length is not five")
}
bf.MustPut(buf)
if _, err = buf.ReadFrom(strings.NewReader("hello")); err == nil {
t.Fatal("The error is nil trying to use a returned buffer")
}
buf = bf.Get()
buf.MustReadFrom(strings.NewReader("hello"))
buf.MustTruncate(5)
if buf.Len() != 5 {
t.Fatalf("The length is not five")
}
bf.MustPut(buf)
assertPanic(t, func() {
buf.MustReadFrom(strings.NewReader("hello"))
})
})
t.Run("BufferWriteTo", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
n, err := buf.WriteTo(io.Discard)
if err != nil {
t.Fatal(err)
}
if n != 5 {
t.Fatalf("The number of bytes is not five: %d", n)
}
bf.MustPut(buf)
if _, err = buf.WriteTo(io.Discard); err == nil {
t.Fatal("The error is nil trying to use a returned buffer")
}
assertPanic(t, func() {
buf.MustWriteTo(io.Discard)
})
})
t.Run("BufferNext", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
p := buf.Next(5)
if string(p) != "hello" {
t.Fatalf("The string is not hello")
}
bf.MustPut(buf)
if p = buf.Next(5); p != nil {
t.Fatalf("The slice is not nil")
}
})
t.Run("NewSizedBufferFactory", func(t *testing.T) {
t.Parallel()
sized := NewSizedBufferFactory(4)
buf := sized.Get()
defer sized.MustPut(buf)
if buf.Cap() != 4 {
t.Errorf("Expected sized buffer from fresh factory to be cap == 4, got: %d", buf.Cap())
}
})
t.Run("BufferWithParent", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
buf.MustWrite([]byte("world"))
buf.MustWrite([]byte("!"))
if buf.p != nil {
t.Fatalf("The parent is not nil without assignment")
}
buf = buf.WithParent(&bf)
if buf.p == nil {
t.Errorf("The parent is nil after assignment")
}
if buf.p != &bf {
t.Errorf("The parent is not the buffer factory")
}
if buf.String() != "helloworld!" {
t.Fatalf("The string is not 'helloworld!': %v", buf.String())
}
bf.MustPut(buf)
})
t.Run("BufferClose", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf.MustWrite([]byte("hello"))
buf.MustWrite([]byte("world"))
buf.MustWrite([]byte("!"))
if buf.IsClosed() {
t.Fatalf("The buffer is closed before closing")
}
if err := buf.Close(); err == nil {
t.Fatal("The error is nil after closing the buffer with no parent")
}
if buf.IsClosed() {
t.Fatalf("The buffer is closed after failing to close")
}
if buf.String() != "helloworld!" {
t.Fatalf("The string is not 'helloworld!' after unsuccessful close: %v", buf.String())
}
buf = buf.WithParent(&bf)
if err := buf.Close(); err != nil {
t.Fatal(err)
}
if !buf.IsClosed() {
t.Fatalf("The buffer is not closed after closing")
}
if err := buf.Close(); err == nil {
t.Fatal("The error is nil after closing an already closed buffer")
}
})
t.Run("BufferCannotClose", func(t *testing.T) {
t.Parallel()
buf := bf.Get()
buf = buf.WithParent(&bf)
buf.MustWrite([]byte("hello"))
buf.MustWrite([]byte("world"))
buf.MustWrite([]byte("!"))
if buf.String() != "helloworld!" {
t.Fatalf("The string is not 'helloworld!': %v", buf.String())
}
bf.MustPut(buf)
if buf.Close() == nil {
t.Fatal("The error is nil after closing an already returned buffer with parent")
}
defer func() {
if r := recover(); r == nil {
t.Fatal("The panic is not nil")
}
}()
buf = nil
if buf.Close() == nil {
t.Fatal("The error is nil after closing a nil buffer")
}
})
}

@ -1,5 +0,0 @@
package pool
import "errors"
var ErrBufferReturned = errors.New("buffer already returned")

@ -1,19 +0,0 @@
package pool
type Pool[T any] interface {
Get() T
Put(T)
}
type WithPutError[T any] interface {
Get() T
Put(T) error
}
type BufferFactoryInterfaceCompat struct {
BufferFactory
}
func (b BufferFactoryInterfaceCompat) Put(buf *Buffer) {
_ = b.BufferFactory.Put(buf)
}

@ -1,68 +0,0 @@
package pool
import (
"bytes"
"sync"
"testing"
)
// ensure compatibility with interface
func TestInterfaces(t *testing.T) {
t.Parallel()
defer func() {
if r := recover(); r != nil {
t.Error("interface not implemented")
}
}()
var (
bf any = NewBufferFactory()
bfCompat any = BufferFactoryInterfaceCompat{NewBufferFactory()}
sPool any = &sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
)
if _, ok := sPool.(Pool[any]); !ok {
t.Fatal("Pool[any] not implemented by sync.Pool")
}
testMe1, ok1 := bfCompat.(Pool[*Buffer])
if !ok1 {
t.Fatal("Pool[*Buffer] not implemented")
}
t.Run("Pool", func(t *testing.T) {
t.Parallel()
b := testMe1.Get()
if _, err := b.WriteString("test"); err != nil {
t.Fatal(err)
}
testMe1.Put(b)
b = testMe1.Get()
if b.Len() != 0 {
t.Fatal("buffer not reset")
}
testMe1.Put(b)
})
t.Run("PoolWithPutError", func(t *testing.T) {
t.Parallel()
testMe2, ok2 := bf.(WithPutError[*Buffer])
if !ok2 {
t.Error("PoolWithPutError[*Buffer] not implemented")
}
b := testMe2.Get()
if _, err := b.WriteString("test"); err != nil {
t.Fatal(err)
}
if err := testMe2.Put(b); err != nil {
t.Fatal(err)
}
b = testMe2.Get()
if b.Len() != 0 {
t.Fatal("buffer not reset")
}
if err := testMe2.Put(b); err != nil {
t.Fatal(err)
}
})
}

@ -1,160 +0,0 @@
package pool
import (
"strings"
"sync"
)
type StringFactory struct {
pool *sync.Pool
}
// NewStringFactory creates a new strings.Builder pool.
func NewStringFactory() StringFactory {
return StringFactory{
pool: &sync.Pool{
New: func() any { return new(strings.Builder) },
},
}
}
// Put returns a strings.Builder back into to the pool after resetting it.
func (sf StringFactory) Put(buf *String) error {
var err = ErrBufferReturned
buf.o.Do(func() {
_ = buf.Reset()
sf.pool.Put(buf.Builder)
buf.Builder = nil
err = nil
})
return err
}
func (sf StringFactory) MustPut(buf *String) {
if err := sf.Put(buf); err != nil {
panic(err)
}
}
// Get returns a strings.Builder from the pool.
func (sf StringFactory) Get() *String {
return &String{
sf.pool.Get().(*strings.Builder),
&sync.Once{},
}
}
type String struct {
*strings.Builder
o *sync.Once
}
func (s String) String() string {
if s.Builder == nil {
return ""
}
return s.Builder.String()
}
func (s String) MustString() string {
if s.Builder == nil {
panic(ErrBufferReturned)
}
return s.Builder.String()
}
func (s String) Reset() error {
if s.Builder == nil {
return ErrBufferReturned
}
s.Builder.Reset()
return nil
}
func (s String) MustReset() {
if err := s.Reset(); err != nil {
panic(err)
}
s.Builder.Reset()
}
func (s String) WriteString(str string) (int, error) {
if str == "" {
return 0, nil
}
if s.Builder == nil {
return 0, ErrBufferReturned
}
return s.Builder.WriteString(str)
}
// MustWriteString means Must Write String, like WriteString but will panic on error.
func (s String) MustWriteString(str string) {
if str == "" {
return
}
if s.Builder == nil {
panic(ErrBufferReturned)
}
/* if str == "" {
panic("nil string")
}*/
_, _ = s.Builder.WriteString(str)
}
func (s String) Write(p []byte) (int, error) {
if s.Builder == nil {
return 0, ErrBufferReturned
}
return s.Builder.Write(p) //nolint:wrapcheck
}
func (s String) WriteRune(r rune) (int, error) {
if s.Builder == nil {
return 0, ErrBufferReturned
}
return s.Builder.WriteRune(r) //nolint:wrapcheck
}
func (s String) WriteByte(c byte) error {
if s.Builder == nil {
return ErrBufferReturned
}
return s.Builder.WriteByte(c) //nolint:wrapcheck
}
func (s String) Len() int {
if s.Builder == nil {
return 0
}
return s.Builder.Len()
}
func (s String) MustLen() int {
if s.Builder == nil {
panic(ErrBufferReturned)
}
return s.Builder.Len()
}
func (s String) Grow(n int) error {
if s.Builder == nil {
return ErrBufferReturned
}
s.Builder.Grow(n)
return nil
}
func (s String) MustGrow(n int) {
if s.Builder == nil {
panic(ErrBufferReturned)
}
s.Builder.Grow(n)
}
func (s String) Cap() int {
if s.Builder == nil {
return 0
}
return s.Builder.Cap()
}

@ -1,247 +0,0 @@
package pool
import (
"testing"
"time"
)
func assertPanic(t *testing.T, f func()) {
t.Helper()
defer func() {
t.Helper()
if r := recover(); r == nil {
t.Errorf("The code did not panic")
}
}()
f()
}
func TestStringFactoryPanic(t *testing.T) {
t.Parallel()
sf := NewStringFactory()
t.Run("StringsMustWrite", func(t *testing.T) {
buf := sf.Get()
buf.MustWriteString("hello world")
if buf.Len() == 0 {
t.Fatalf("The buffer is empty after we wrote to it")
}
if buf.String() != "hello world" {
t.Fatalf("The buffer has the wrong content")
}
})
t.Run("StringsMustWritePanic", func(t *testing.T) {
t.Parallel()
var badString *string = nil
buf := sf.Get()
assertPanic(t, func() {
buf.MustWriteString(*badString)
})
/* assertPanic(t, func() {
buf.MustWriteString("")
})*/
if err := sf.Put(buf); err != nil {
t.Fatalf("The buffer was not returned: %v", err)
}
})
t.Run("StringsMustString", func(t *testing.T) {
t.Parallel()
buf := sf.Get()
buf.MustWriteString("hello world")
if buf.MustString() != "hello world" {
t.Fatalf("The buffer has the wrong content")
}
sf.MustPut(buf)
assertPanic(t, func() {
buf.MustString()
})
})
t.Run("StringsMust", func(t *testing.T) {
t.Parallel()
buf := sf.Get()
buf.MustReset()
_ = buf.MustLen()
buf.MustGrow(10)
err := sf.Put(buf)
if err != nil {
t.Fatalf("The buffer was not returned: %v", err)
}
assertPanic(t, func() {
sf.MustPut(buf)
})
assertPanic(t, func() {
buf.MustWriteString("hello")
})
assertPanic(t, func() {
buf.MustGrow(10)
})
assertPanic(t, func() {
buf.MustLen()
})
assertPanic(t, func() {
buf.MustReset()
})
})
}
func TestStringFactory(t *testing.T) {
t.Parallel()
s := NewStringFactory()
t.Run("StringPoolHelloWorld", func(t *testing.T) {
t.Parallel()
buf := s.Get()
if _, err := buf.WriteString("hello"); err != nil {
t.Fatal(err)
}
if buf.String() != "hello" {
t.Fatal("unexpected string")
}
if err := buf.WriteByte(' '); err != nil {
t.Fatal(err)
}
if buf.String() != "hello " {
t.Fatalf("unexpected string: %s", buf.String())
}
if _, err := buf.WriteRune('w'); err != nil {
t.Fatal(err)
}
if buf.String() != "hello w" {
t.Fatalf("unexpected string: %s", buf.String())
}
if _, err := buf.Write([]byte("orld")); err != nil {
t.Fatal(err)
}
if err := buf.Grow(1); err != nil {
t.Fatal(err)
}
if buf.Cap() == 0 {
t.Fatal("expected capacity, got 0")
}
if err := buf.Reset(); err != nil {
t.Fatal(err)
}
if buf.String() != "" {
t.Fatalf("unexpected string: %s", buf.String())
}
if err := s.Put(buf); err != nil {
t.Fatal(err)
}
if err := s.Put(buf); err == nil {
t.Fatal("expected error")
}
})
t.Run("StringPoolCheckGetLength", func(t *testing.T) {
t.Parallel()
buf := s.Get()
if buf.Len() > 0 {
t.Fatalf("StringFactory.Put() did not reset the buffer")
}
if err := s.Put(buf); err != nil {
t.Fatal(err)
}
if err := s.Put(buf); err == nil {
t.Fatalf("StringFactory.Put() should have returned an error after already returning the buffer")
}
})
t.Run("StringPoolGrowBuffer", func(t *testing.T) {
t.Parallel()
buf := s.Get()
if err := buf.Grow(1); err != nil {
t.Fatal(err)
}
if buf.Cap() != 1 {
t.Fatalf("expected capacity of 1, got %d", buf.Cap())
}
if err := s.Put(buf); err != nil {
t.Fatal(err)
}
if err := buf.Grow(10); err == nil {
t.Fatalf("StringFactory.Grow() should not work after returning the buffer")
}
if buf.Cap() != 0 {
t.Fatalf("StringFactory.Cap() should return 0 after returning the buffer")
}
})
t.Run("StringPoolCleanBuffer", func(t *testing.T) {
t.Parallel()
time.Sleep(25 * time.Millisecond)
got := s.Get()
if got.String() != "" {
t.Fatalf("should have gotten a clean buffer")
}
if err := s.Put(got); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
t.Run("StringPoolWriteStringToReturnedBuffer", func(t *testing.T) {
t.Parallel()
got := s.Get()
s.MustPut(got)
if _, err := got.WriteString("a"); err == nil {
t.Fatalf("should not be able to write to a returned buffer")
}
})
t.Run("StringPoolWriteRuneToReturnedBuffer", func(t *testing.T) {
t.Parallel()
got := s.Get()
s.MustPut(got)
if _, err := got.WriteRune('a'); err == nil {
t.Fatalf("should not be able to write to a returned buffer")
}
})
t.Run("StringPoolWriteByteToReturnedBuffer", func(t *testing.T) {
t.Parallel()
got := s.Get()
s.MustPut(got)
if err := got.WriteByte('a'); err == nil {
t.Fatalf("should not be able to write to a returned buffer")
}
})
t.Run("StringPoolWriteToReturnedBuffer", func(t *testing.T) {
t.Parallel()
got := s.Get()
s.MustPut(got)
if _, err := got.Write([]byte("a")); err == nil {
t.Fatalf("should not be able to write to a returned buffer")
}
})
t.Run("StringPoolResetReturnedBuffer", func(t *testing.T) {
t.Parallel()
got := s.Get()
s.MustPut(got)
if err := got.Reset(); err == nil {
t.Fatalf("should not be able to reset a returned buffer")
}
if str := got.String(); str != "" {
t.Fatalf("should not be able to get string from a returned buffer")
}
if got.Len() != 0 {
t.Fatalf("should not be able to write to a returned buffer")
}
})
t.Run("StringFactoryMustNotPanicOnEmptyString", func(t *testing.T) {
t.Parallel()
got := s.Get()
n, err := got.WriteString("")
if err != nil {
t.Fatal(err)
}
if n != 0 {
t.Fatalf("expected 0, got %d", n)
}
if str := got.String(); str != "" {
t.Fatalf("expected empty string, got %s", str)
}
if err := s.Put(got); err != nil {
t.Fatal(err)
}
got = s.Get()
defer func() {
if r := recover(); r != nil {
t.Fatalf("unexpected panic: %v", r)
}
}()
got.MustWriteString("")
s.MustPut(got)
})
}

@ -8,7 +8,8 @@ import (
"git.tcp.direct/kayos/common/entropy"
)
const lip string = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ante sit amet purus blandit auctor. Nullam ornare enim sed nibh consequat molestie. Duis est lectus, vestibulum vel felis vel, convallis cursus ex. Morbi nec placerat orci. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent a erat sit amet libero convallis ornare a venenatis dolor. Pellentesque euismod risus et metus porttitor, vel consectetur lacus tempus. Integer elit arcu, condimentum quis nisi eget, dapibus imperdiet nulla. Cras sit amet ante in urna varius tempus. Integer tristique sagittis nunc vel tincidunt.
const lip string = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ante sit amet purus blandit auctor. Nullam ornare enim sed nibh consequat molestie. Duis est lectus, vestibulum vel felis vel, convallis cursus ex. Morbi nec placerat orci. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent a erat sit amet libero convallis ornare a venenatis dolor. Pellentesque euismod risus et metus porttitor, vel consectetur lacus tempus. Integer elit arcu, condimentum quis nisi eget, dapibus imperdiet nulla. Cras sit amet ante in urna varius tempus. Integer tristique sagittis nunc vel tincidunt.
Integer non suscipit ligula, et fermentum sem. Duis id odio lorem. Sed id placerat urna, eu vehicula risus. Duis porttitor hendrerit risus. Curabitur id tellus ac arcu aliquet finibus. Pellentesque et nisl ante. Mauris sapien nisl, pretium in ligula tempus, posuere mattis turpis.
@ -16,9 +17,8 @@ Proin et tempus enim. Nullam at diam est. Vivamus ut lectus hendrerit, interdum
Nam laoreet enim leo, sed finibus lorem egestas vel. Maecenas varius a leo non placerat. Donec scelerisque, risus vel finibus ornare, arcu ligula interdum justo, in ultricies urna mi et neque. Curabitur sed sem dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas eget laoreet nisi. Nam rhoncus sapien ac interdum sagittis. Nulla fermentum sem nec tellus dignissim lacinia. Curabitur ornare lectus non dictum laoreet. Praesent tempor risus at tortor tempor finibus. Cras id dolor mi.
Mauris ut mi quis est vehicula molestie. Mauris eu varius urna. Integer sodales nunc at risus rutrum eleifend. In sed bibendum lectus. Morbi ipsum sapien, blandit in dignissim eu, ultrices non odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eget volutpat ligula, at elementum dui. Aliquam sed enim scelerisque, facilisis magna vitae, dignissim enim. Pellentesque non ultricies urna. Proin fermentum erat semper efficitur auctor. Vestibulum posuere non tortor vitae tincidunt.`
const lipGzd string = `H4sIAAAAAAACA7VWS47cOAzd5xQ8gFEXyCpINgPMBAEGmD1LYlVxIEtufQo5/jxKssvdmW1W7bZkku9Dsv5MWVbSrbSVfAopU9FKvEpdyKVYxFWpLRN73bQ4jXeSoPVCf4snJo5Vji9oa7kVugaO3l41V1O+0PcWAq+UcuQsJFFXKvg46vUxUrw1rrSmIKWqXOhb00J4poDkrSz0tINrCyjxKYFuKKDYU6/wycH+dS0X5JafF/or5atSFEdbYCcZwVN2eqEfEoKg4PLWhB581Yrykdiu16xIYgdFYs9LABRlPqyM6hp7phuQFmJHYGWzQu+ojguiZxZ8C9zUcx6sBL1KTqdaJxMMDFEiV7zq1H+oUMDDmjyhsllEr2ZLuVYFs0tn4ywS4OJClXVrKOgPBLpL7noRZ9c6X15XZACVb0Zz1KIGAWp73kByIV03yV4NvQl3oa+ZywtNV1wjNYCgJ2f9n4wnMvmu1QDGFl2vt2p06lusl0+f9vsxRQJGB4sZXfcWeDHAN8mz2CLrNIZ6Sl4TBTPusCFeHUpbWfi2IddDHQIN+ubHB3f0kOizZOSb519bNkeARYSr0KF1lY034mBgUI9Go+ijTtVoDJ0ZuI8bIgL4phL7wUJblqpAAdoGuskYTlJpAi+s3FkangIzP3LCZUQeF3vXHJ0EmF7xF8a70D/65BU32t4vL2gLElZoiczyE7AWagHSOJW9vpNr8yPF7p5h627IZZgkpCt4s/LwB9xtZ/TqP9JxH4Qo7AiNzFq3jLmhqB0z4d6dvewNO9nfnXB282yTGeVU6uQQnlhedjJW5gHX905/tdkAAH6/g74ZeEykIGnpY2lqPPy1d7c516QVh0Bltz3bV928u/1gs2SDpzgJUMD4WGb/9sk1Yw9kyzDXLPqQ6t9Walp6ix1q9WZbdYwkBD3b1YoGFeTbbx9xBwFd450/0xreBKO7h6b5EeFAtQ+CaeL3rd2H9ew5r3cELCYJY90on7HulhjVG/NenQX51STWOPDqIB/emN6dr49O7sMNHT9236rwxuxgNBQYf9uX0TFPXotqXrRhMwxhMr28XJI3Ssfo4zloKLearSGD6A2Nate7hFdsiWhMDXD7GhubeRC6HKtV44kn66ZhFRmc2HzEYHhtzTGze6Qt62pTNELl5qYbHXq17YY4Qu2zybWrBmXrWJbPU7/ugGcKrW78mtl4BLApbDfkFxudPDb+WP3n1rhBYmxEFLTy3fYJ/IrXJ2x97r1ztSF83xmmuo3Ll6fGAhbbZCS3G6723zDzB8mJmR2jBZ0O6TWc1tR/eFfmFyIJAAA=`
Mauris ut mi quis est vehicula molestie. Mauris eu varius urna. Integer sodales nunc at risus rutrum eleifend. In sed bibendum lectus. Morbi ipsum sapien, blandit in dignissim eu, ultrices non odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eget volutpat ligula, at elementum dui. Aliquam sed enim scelerisque, facilisis magna vitae, dignissim enim. Pellentesque non ultricies urna. Proin fermentum erat semper efficitur auctor. Vestibulum posuere non tortor vitae tincidunt.
`
func TestGzip(t *testing.T) {
gsUp := Gzip([]byte(lip))
@ -150,37 +150,19 @@ func TestUnpackStr(t *testing.T) { //nolint:cyclop
if !bytes.Equal(sanity2, []byte(lip)) {
t.Fatalf("Bytes not equal after Gzip: %v != %v", sanity2, []byte(lip))
}
testUnpack := func(data string, t *testing.T) {
unpacked, err := UnpackStr(data)
switch {
case err != nil:
t.Errorf("[FAIL] %s", err.Error())
case unpacked != lip:
t.Fatalf("[FAIL] unpackstr decided to not work, who knows why. If you see this than I have already become a janitor.\n"+
"unpacked: %v != packed: %v", []byte(unpacked), []byte(lip))
default:
t.Logf("[PASS] " + t.Name())
}
unpacked, err := UnpackStr(packed)
switch {
case err != nil:
t.Errorf("[FAIL] %s", err.Error())
case unpacked != lip:
t.Fatalf("[FAIL] unpackstr decided to not work, who knows why. If you see this than I have already become a janitor.\n"+
"unpacked: %s != packed: %s", unpacked, lip)
default:
t.Logf("[PASS] TestUnpackStr")
}
_, nilerr := UnpackStr("")
if nilerr == nil {
t.Fatalf("[FAIL] unpackstr didn't fail on empty input")
}
t.Run("TestUnpackFailOnEmpty", func(t *testing.T) {
_, nilerr := UnpackStr("")
if nilerr == nil {
t.Fatalf("[FAIL] unpackstr didn't fail on empty input")
}
})
t.Run("TestUnpack", func(t *testing.T) {
for n := 0; n != 5; n++ {
testUnpack(packed, t)
}
})
t.Run("TestUnpackPrePacked", func(t *testing.T) {
for n := 0; n != 5; n++ {
testUnpack(lipGzd, t)
}
})
}

12
util.go

@ -5,21 +5,15 @@ import (
"fmt"
"io"
"math"
"github.com/rs/zerolog/log"
)
// Fprint is fmt.Fprint with error handling.
func Fprint(w io.Writer, s string) {
_, err := fmt.Fprint(w, s)
if err != nil {
println("common.Fprint failed: " + err.Error())
}
}
// Fprintf is fmt.Fprintf with error handling.
func Fprintf(w io.Writer, format string, items ...any) {
_, err := fmt.Fprintf(w, format, items...)
if err != nil {
println("common.Fprintf failed: " + err.Error())
log.Error().Str("data", s).Err(err).Msg("Fprint failed!")
}
}