Compare commits

...

80 Commits
v0.7.0 ... main

Author SHA1 Message Date
dependabot[bot] 67f76830a4
Bump golang.org/x/crypto from 0.21.0 to 0.22.0 (#30)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-06 00:12:08 -07:00
dependabot[bot] ba45bee638
Bump golang.org/x/crypto from 0.20.0 to 0.21.0 (#29)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-08 21:11:07 -08:00
dependabot[bot] c76c7965e9
Bump golang.org/x/crypto from 0.19.0 to 0.20.0 (#28)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-29 23:42:32 -08:00
dependabot[bot] 37488a3804
Bump golang.org/x/crypto from 0.18.0 to 0.19.0 (#27)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-09 14:13:19 -08:00
kayos@tcp.direct 5f109b6caf
Feat[hash]: Add crc64 with 2 polynomial options 2024-01-28 22:44:23 -08:00
dependabot[bot] 44e5d2d424
Bump golang.org/x/crypto from 0.17.0 to 0.18.0 (#26)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/crypto/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-09 14:01:04 -08:00
dependabot[bot] ec984c1a8a
Bump actions/setup-go from 4 to 5 (#23)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-30 13:48:35 -08:00
dependabot[bot] 5e01e28a17
Bump golang.org/x/crypto from 0.16.0 to 0.17.0 (#25)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-26 16:44:35 -08:00
dependabot[bot] 3aff8ba749
Bump github/codeql-action from 2 to 3 (#24)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 15:38:37 -08:00
dependabot[bot] 40fe053b45
Bump golang.org/x/crypto from 0.15.0 to 0.16.0 (#22)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 20:01:58 -08:00
kayos@tcp.direct 4e59e82d82
Fix[hash]: Fix benchmarking 2023-11-14 14:37:35 -08:00
kayos 22bad87f21
Feat/Refactor[hash]: !!BREAKING!! add CRC32 and general refactor !!BREAKING!! (#21) 2023-11-14 14:31:35 -08:00
kayos@tcp.direct 9ecc1d1677
Fix[entropy]: Fix typo in docs and benchmark code 2023-11-13 04:59:39 -08:00
dependabot[bot] b4aea38e0b
Bump golang.org/x/crypto from 0.14.0 to 0.15.0 (#20)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-09 09:00:16 -08:00
kayos 90e435641d
Feat[pool]: Add new interfaces for compatibility (#19) 2023-10-29 20:20:46 -07:00
kayos@tcp.direct 7b5cdbe351
Chore[build]: Missed a build tag directive 2023-10-29 19:19:27 -07:00
kayos@tcp.direct 513277750a
Feat: Dialer interface (why is this not in stdlib) 2023-10-28 14:28:00 -07:00
dependabot[bot] f2f4b51076
Bump golang.org/x/crypto from 0.13.0 to 0.14.0 (#18)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/crypto/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 02:41:36 -07:00
dependabot[bot] 110c3e8256
Bump golang.org/x/crypto from 0.12.0 to 0.13.0 (#16)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/crypto/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-07 21:04:16 -07:00
kayos cb6e9c71a6
entropy: fix race condition, massive performance boost (#17)
* Fix[entropy]: Implement rand pool to remediate splitmix64 race condition

* Perf[entropy]: Use recycled byte buffers for random strings

* Refactor[entropy][testing][bench]: clean up test code and report allocations

* Refactor[entropy]: nil out rand pointer after we put it back into the pool

* Testing[entropy]: fix coverage

* Fix[entropy]: Fix race condition during testing conditions

* gomod: retract premature tags
2023-09-06 23:45:44 -07:00
kayos ba99ee5f4c
Merge pull request #15 from yunginnanet/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2023-09-05 12:09:13 -07:00
dependabot[bot] 689c0f943c
Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-05 10:14:16 +00:00
kayos 4e0eb94d7b
Merge pull request #14 from yunginnanet/dependabot/go_modules/golang.org/x/crypto-0.12.0
Bump golang.org/x/crypto from 0.11.0 to 0.12.0
2023-08-07 15:39:54 -07:00
dependabot[bot] 63a7acc349
Bump golang.org/x/crypto from 0.11.0 to 0.12.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/crypto/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 10:08:18 +00:00
kayos 43bc3b2a80
Merge pull request #13 from yunginnanet/dependabot/go_modules/golang.org/x/crypto-0.11.0
Bump golang.org/x/crypto from 0.10.0 to 0.11.0
2023-07-07 21:17:35 -07:00
dependabot[bot] dbfa6a5a00
Bump golang.org/x/crypto from 0.10.0 to 0.11.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/crypto/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-06 10:52:23 +00:00
kayos 3a04f404a4
Merge pull request #12 from yunginnanet/dependabot/go_modules/golang.org/x/crypto-0.10.0
Bump golang.org/x/crypto from 0.9.0 to 0.10.0
2023-06-14 12:17:06 -07:00
dependabot[bot] b3331c4f31
Bump golang.org/x/crypto from 0.9.0 to 0.10.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/crypto/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-14 11:04:47 +00:00
kayos@tcp.direct 85943b8762
Feat[pool]: IsClosed() function 2023-05-25 10:56:42 -07:00
kayos@tcp.direct 22a512b953
Feat[pool]: Implement io.Closer for bytes buffers 2023-05-22 14:54:04 -07:00
kayos 83b47e8775
Merge pull request #10 from yunginnanet/dependabot/go_modules/golang.org/x/crypto-0.9.0
Bump golang.org/x/crypto from 0.8.0 to 0.9.0
2023-05-09 10:48:26 -07:00
dependabot[bot] ee4d5e7d2b
Bump golang.org/x/crypto from 0.8.0 to 0.9.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/crypto/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-09 11:01:01 +00:00
kayos@tcp.direct 0ac392a432
!!! BREAKING !!! [list]: Pop() pops from the front of the list, not the back 2023-05-05 14:04:33 -07:00
kayos@tcp.direct d30cacd32d
Chore[deps]: update go.sum 2023-05-05 14:03:54 -07:00
kayos@tcp.direct 6a77f2d7eb
Chore: go tidy 2023-05-02 22:23:11 -07:00
kayos@tcp.direct b2113f7be5
Reduce: remove external `github.com/pkg/errors` package dependency 2023-05-02 22:21:49 -07:00
kayos@tcp.direct df124a9303
Fix[pool][strings]: no reason to panic when inputting empty strings 2023-05-02 20:39:11 -07:00
kayos 4aaeee92ba
Merge pull request #9 from yunginnanet/dependabot/go_modules/golang.org/x/crypto-0.8.0 2023-04-07 04:52:01 -07:00
dependabot[bot] a5784b4cd3
Bump golang.org/x/crypto from 0.7.0 to 0.8.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-07 11:01:50 +00:00
kayos 4cd7f8bb8e
Merge pull request #7 from yunginnanet/dependabot/github_actions/actions/checkout-3 2023-04-01 11:13:26 -07:00
dependabot[bot] 87080cb407
Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 18:11:40 +00:00
kayos c2d21555f2
Merge pull request #8 from yunginnanet/dependabot/github_actions/github/codeql-action-2 2023-04-01 11:11:16 -07:00
kayos 445c7d92e1
Merge pull request #6 from yunginnanet/dependabot/github_actions/actions/setup-go-4 2023-04-01 11:11:06 -07:00
dependabot[bot] 0e006387b8
Bump github/codeql-action from 1 to 2
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 18:08:05 +00:00
dependabot[bot] be50818f8e
Bump actions/setup-go from 2 to 4
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 18:07:43 +00:00
kayos 6da3fef18d
Update dependabot.yml 2023-04-01 11:07:24 -07:00
kayos@tcp.direct 57e49566da
Chore: tidy 2023-03-23 00:46:12 -07:00
kayos@tcp.direct c167129c6d
Feat: LockableList (pushpop) 2023-03-23 00:43:53 -07:00
kayos c7b1d5eba5
Merge pull request #5 from yunginnanet/dependabot/go_modules/golang.org/x/crypto-0.7.0 2023-03-06 08:59:26 -08:00
dependabot[bot] 1d62f4b007
Bump golang.org/x/crypto from 0.6.0 to 0.7.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 11:12:21 +00:00
kayos 7bfc7ea5a7
Merge pull request #4 from yunginnanet/dependabot/go_modules/golang.org/x/crypto-0.6.0 2023-02-24 12:18:00 -08:00
dependabot[bot] 54298d0a71
Bump golang.org/x/crypto from 0.0.0-20220817201139-bc19a97f63c8 to 0.6.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20220817201139-bc19a97f63c8 to 0.6.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/commits/v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-24 11:04:37 +00:00
kayos@tcp.direct 20ef2e0607
Docs: Add documentation, update README.md 2023-02-05 20:03:24 -08:00
kayos@tcp.direct 9b000e97f2
Pool: Add benchmarks 2023-02-05 20:02:39 -08:00
kayos@tcp.direct 1a3c4cefc4
Pool: documenation 2023-02-05 19:32:59 -08:00
kayos@tcp.direct 02695f44bd
CI: add race detector 2023-01-18 16:16:05 -08:00
kayos@tcp.direct 8d84965386
Feat[pool]: add NewSizedBufferFactory 2023-01-18 16:13:44 -08:00
kayos@tcp.direct 25ae455041
Update README.md 2022-12-31 10:53:41 -08:00
kayos@tcp.direct 6bad25a5fd
Update .gitignore 2022-12-31 10:53:03 -08:00
kayos@tcp.direct 0082b2a442
Debloat: we're not v1 yet so I can do this (remove network) 2022-12-31 10:52:36 -08:00
kayos@tcp.direct 81dad19896
Fix[linux]: not returning error 2022-12-18 07:02:11 -08:00
kayos@tcp.direct e86d7171e0
Cleanup[linux] + lint 2022-12-18 06:46:16 -08:00
kayos@tcp.direct a39de9c865
Cleanup[linux]: de-dupe + doc++ 2022-12-18 04:11:45 -08:00
kayos@tcp.direct 2e58f576ff
Feat[linux]: sysinfo 2022-12-18 04:05:54 -08:00
kayos@tcp.direct c7eafdbc52
Fix[entropy]: fix `OneInA` function 2022-12-11 12:02:46 -08:00
kayos@tcp.direct 7c3c116baa
Debloat: Get rid of zerolog dependency 2022-11-22 13:33:08 -08:00
kayos@tcp.direct 3f6dbd7472
Feat[entropy]: Access shared rand.Rand 2022-10-11 01:06:20 -07:00
kayos@tcp.direct a9ce9387ac
Feat[pool]: safer bytes.Buffer+sync.Pool 2022-10-02 23:46:32 -07:00
kayos@tcp.direct 2e72a70838
Feat[pool/strings]: More Must functions 2022-10-02 23:45:48 -07:00
kayos@tcp.direct f8d9462a66
Feat: MustWriteString, MustPut 2022-10-02 22:06:42 -07:00
kayos@tcp.direct fb2b04efbc
Coverage: fix regression 2022-09-28 01:20:56 -07:00
kayos@tcp.direct 36795dee8e
Feat[pool]: add safer strings.Builder pool 2022-09-28 01:11:54 -07:00
kayos@tcp.direct 358021fb40
Revert defunct dev branch stuff 2022-09-28 00:04:16 -07:00
kayos@tcp.direct 68d7cd9ecc
Update test to match network changes 2022-09-27 23:47:16 -07:00
kayos@tcp.direct 781a3583d2
Add cmd: ipranger 2022-09-27 23:47:16 -07:00
kayos@tcp.direct 929cdf25bb
Feat[entropy]: Random slice value 2022-09-27 23:45:49 -07:00
kayos@tcp.direct 3139db4896
Feat: Fprintf 2022-09-23 13:45:54 -07:00
kayos@tcp.direct 98a80f4427
Retract defunct cached modules 2022-09-15 02:03:21 -07:00
kayos@tcp.direct 24fbc6514a
Paranoid checks 2022-08-25 04:11:02 -07:00
kayos@tcp.direct 9d344e77b5
Performance and testing improvements
Signed-off-by: kayos@tcp.direct <kayos@tcp.direct>
2022-08-22 06:14:08 -07:00
34 changed files with 3587 additions and 539 deletions

View File

@ -9,3 +9,7 @@ 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"

View File

@ -38,11 +38,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
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@v1
uses: github/codeql-action/autobuild@v3
# 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@v1
uses: github/codeql-action/analyze@v3

View File

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

1
.gitignore vendored
View File

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

View File

@ -1,9 +1,10 @@
# 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)
* [hash](https://pkg.go.dev/git.tcp.direct/kayos/common/hash)
* [linux](https://pkg.go.dev/git.tcp.direct/kayos/common/linux)
@ -12,38 +13,10 @@ Welcome to things. Here are some of the aforementioned:
* [entropy](https://pkg.go.dev/git.tcp.direct/kayos/common/entropy)
* [network](https://pkg.go.dev/git.tcp.direct/kayos/common/network)
* [pool](https://pkg.go.dev/git.tcp.direct/kayos/common/pool)
## base
---
`import "git.tcp.direct/kayos/common"`
### Dependencies
## 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.
[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++.

View File

@ -2,60 +2,12 @@ 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 {
@ -92,7 +44,6 @@ 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")
@ -108,6 +59,7 @@ 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")
@ -116,4 +68,15 @@ 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")
}
}

View File

@ -8,13 +8,53 @@ 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 {
@ -24,47 +64,59 @@ func RandomStrChoice(choice []string) string {
}
// GetCryptoSeed returns a random int64 derived from crypto/rand.
// This can be used as a seed for the math/rand package.
// This can be used as a seed for various PRNGs.
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 crypto/rand to seed a splitmix64 rng.
// GetOptimizedRand returns a pointer to a *new* rand.Rand which uses GetCryptoSeed to seed an rng.SplitMix64.
// 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)
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
}
// RNGUint32 returns a random uint32 using crypto/rand and splitmix64.
func RNGUint32() uint32 {
getSharedRand.Do(func() {
sharedRand = GetOptimizedRand()
})
return sharedRand.Uint32()
r := lolXD.Get()
ui := r.Uint32()
lolXD.Put(r)
return ui
}
/*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 {
getSharedRand.Do(func() {
sharedRand = GetOptimizedRand()
})
return sharedRand.Intn(n)
r := lolXD.Get()
i := r.Intn(n)
lolXD.Put(r)
return i
}
// 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
}
@ -77,22 +129,54 @@ 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 {
buf := make([]byte, size)
for i := 0; i != size; i++ {
buf[i] = charset[uint32(RNG(36))%uint32(len(charset))]
}
return string(buf)
return randStr(false, size)
}
// 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 {
buf := make([]byte, size)
for i := 0; i != size; i++ {
buf[i] = charsetWithUpper[uint32(RNG(62))%uint32(len(charsetWithUpper))]
}
return string(buf)
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()
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)])
}
}
lolXD.Put(r)
s := buf.String()
strBufs.MustPut(buf)
return s
}

View File

@ -1,19 +1,33 @@
package entropy
import (
"fmt"
"strings"
"sync"
"testing"
)
func check[T comparable](zero T, one T, t *testing.T) {
var dupCount = 0
func check[T comparable](t *testing.T, zero T, one T) {
t.Helper()
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,
@ -22,48 +36,94 @@ func Test_RNG(t *testing.T) {
}))
}
for n := 0; n != 500; n++ {
check(RNG(55555), RNG(55555), t)
check(RNGUint32(), RNGUint32(), 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!")
}
}
func randStrChecks(zero, one string, t *testing.T, intendedLength int) {
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()
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(zero, one, t)
check(t, zero, one)
}
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(zero, one, t, 55)
randStrChecks(t, zero, one, 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(zero, one, t, 15)
randStrChecks(t, zero, one, 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(zero, one, t, 55)
randStrChecks(t, zero, one, 55)
zeroSplit := strings.Split(zero, "")
oneSplit := strings.Split(one, "")
var similarity = 0
@ -72,10 +132,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)
@ -85,6 +145,7 @@ 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")
}
@ -92,72 +153,30 @@ func Test_RandomStrChoice(t *testing.T) {
for n := 0; n != 500; n++ {
slice = append(slice, RandStr(555))
}
check(RandomStrChoice(slice), RandomStrChoice(slice), t)
check(t, RandomStrChoice(slice), RandomStrChoice(slice))
}
func Test_RNGUint32(t *testing.T) {
t.Parallel()
// start globals fresh, just for coverage.
sharedRand = GetOptimizedRand()
setSharedRand()
hardLocc.Lock()
getSharedRand = &sync.Once{}
hardLocc.Unlock()
RNGUint32()
}
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)
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)
}
})
}
}
}

19
go.mod
View File

@ -1,19 +1,16 @@
module git.tcp.direct/kayos/common
go 1.18
go 1.19
require (
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.27.0
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
golang.org/x/crypto v0.22.0
nullprogram.com/x/rng v1.1.0
)
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-20211027215541-db492cf91b37 // indirect
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
require golang.org/x/sys v0.19.0 // indirect
retract (
v0.9.1 // premature (race condition)
v0.9.0 // premature
v0.0.0-20220210125455-40e3d2190a52
)

49
go.sum
View File

@ -1,47 +1,6 @@
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 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/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-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/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-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw=
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
nullprogram.com/x/rng v1.1.0 h1:SMU7DHaQSWtKJNTpNFIFt8Wd/KSmOuSDPXrMFp/UMro=
nullprogram.com/x/rng v1.1.0/go.mod h1:glGw6V87vyfawxCzqOABL3WfL95G65az9Z2JZCylCkg=

View File

@ -1,16 +1,19 @@
package hash
import (
"bytes"
"crypto/md5"
"crypto/sha1"
"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"
)
@ -23,37 +26,127 @@ 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{} {
return sha1.New() //nolint:gosec
},
}
sha256Pool = &sync.Pool{
New: func() interface{} {
return sha256.New()
},
}
sha512Pool = &sync.Pool{
New: func() interface{} {
return sha512.New()
},
}
md5Pool = &sync.Pool{
New: func() interface{} {
return md5.New() //nolint:gosec
},
}
blake2bPool = &sync.Pool{
New: func() interface{} {
h, _ := blake2b.New(blake2b.Size, nil)
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 {
var h hash.Hash
switch ht {
case TypeBlake2b:
h, _ := blake2b.New(blake2b.Size, nil)
h.Write(b)
return h.Sum(nil)
h = blake2bPool.Get().(hash.Hash)
defer blake2bPool.Put(h)
case TypeSHA1:
h = sha1.New()
h = sha1Pool.Get().(hash.Hash)
defer sha1Pool.Put(h)
case TypeSHA256:
h = sha256.New()
h = sha256Pool.Get().(hash.Hash)
defer sha256Pool.Put(h)
case TypeSHA512:
h = sha512.New()
h = sha512Pool.Get().(hash.Hash)
defer sha512Pool.Put(h)
case TypeMD5:
h = md5.New()
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
}
h.Write(b)
return h.Sum(nil)
sum := h.Sum(nil)
h.Reset()
return sum
}
// 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) {
// 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) {
var f *os.File
f, err = os.Open(path)
if err != nil {
@ -62,7 +155,7 @@ func BlakeFileChecksum(path string) (buf []byte, err error) {
defer func() {
if closeErr := f.Close(); err != nil {
err = errors.Wrapf(err, "failed to close file: %s", closeErr)
err = fmt.Errorf("failed to close file during BlakeFileChecksum: %w", closeErr)
}
}()
@ -71,10 +164,5 @@ func BlakeFileChecksum(path string) (buf []byte, err error) {
return nil, errors.New("file is empty")
}
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))
return Sum(ht, buf), nil
}

View File

@ -2,102 +2,138 @@ package hash
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=="
kayosBlake2b = "Kr+6ONDx+cq/WvhHpQE/4LVuJYi9QHz1TztHNTWwa9KJWqHxfTNLKF3YxrcLptA3wO0KHm83Lq7gpBWgCQzPag=="
kayosMD5 = "aoNjwileNitB208vOwpIow=="
kayosSHA1 = "M23ElC0sQYAK+MaMZVmza2L8mss="
kayosSHA256 = "BagY0TmoGR3O7t80BGm4K6UHPlqEg6HJirwQmhrPK4U="
kayosSHA512 = "xiuo2na76acrWXCTTR++O1pPZabOhyj8nbfb5Go3e1pEq9VJYIsOioTXalf2GCuERmFecWkmaL5QI8mIXXWpNA=="
kayosCRC32 = "xtig5w=="
kayosCRC64ISO = "YVx8IpQawAA="
kayosCRC64ECMA = "Nn5+vneo4j4="
)
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)
}
var kayosByteSlice = []byte{107, 97, 121, 111, 115, 10}
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())
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,
}
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")
}
var (
ogsha1 = squish.B64d(kayosSHA1)
ogsha256 = squish.B64d(kayosSHA256)
ogsha512 = squish.B64d(kayosSHA512)
ogmd5 = squish.B64d(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"))
)
if !bytes.Equal(newsha1, ogsha1) {
t.Fatalf("[sha1] wanted: %v, got %v", kayosSHA1, squish.B64e(newsha1))
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")
}
})
}
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)
}
})
}
})
}
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", kayosSHA256, squish.B64e(newsha256))
}
t.Logf("[sha256] success: %s", kayosSHA256)
if !bytes.Equal(newsha512, ogsha512) {
t.Fatalf("[sha512] wanted: %v, got %v", kayosSHA512, squish.B64e(newsha512))
}
t.Logf("[sha512] success: %s", kayosSHA512)
if !bytes.Equal(newmd5, ogmd5) {
t.Fatalf("[md5] wanted: %v, got %v", kayosMD5, squish.B64e(newmd5))
}
t.Logf("[md5] success: %s", kayosMD5)
}

8
ifaces.go Normal file
View File

@ -0,0 +1,8 @@
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)
}

16
ifaces_test.go Normal file
View File

@ -0,0 +1,16 @@
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{})
}

107
linux/sysinfo.go Normal file
View File

@ -0,0 +1,107 @@
//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
}

89
linux/sysinfo_test.go Normal file
View File

@ -0,0 +1,89 @@
//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)
}

View File

@ -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{}
_ = syscall.Uname(ub)
err = 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 []string
var uns = make([]string, len(targets))
for _, target := range targets {
var sub []string
for _, r := range target {

View File

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

310
list/list.go Normal file
View File

@ -0,0 +1,310 @@
// 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
}

562
list/list_test.go Normal file
View File

@ -0,0 +1,562 @@
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{})
}

View File

@ -1,45 +0,0 @@
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
}

View File

@ -1,109 +0,0 @@
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
}
}
}
}
}

98
pool/README.md Normal file
View File

@ -0,0 +1,98 @@
# 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
```

455
pool/bytes.go Normal file
View File

@ -0,0 +1,455 @@
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
}

173
pool/bytes_bench_test.go Normal file
View File

@ -0,0 +1,173 @@
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)
}
}

584
pool/bytes_test.go Normal file
View File

@ -0,0 +1,584 @@
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")
}
})
}

5
pool/errors.go Normal file
View File

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

19
pool/interface.go Normal file
View File

@ -0,0 +1,19 @@
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)
}

68
pool/interface_test.go Normal file
View File

@ -0,0 +1,68 @@
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)
}
})
}

160
pool/strings.go Normal file
View File

@ -0,0 +1,160 @@
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()
}

247
pool/strings_test.go Normal file
View File

@ -0,0 +1,247 @@
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)
})
}

View File

@ -4,46 +4,94 @@ import (
"bytes"
"compress/gzip"
"encoding/base64"
"errors"
"io"
"sync"
)
var (
bufPool = &sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
gzipPool = &sync.Pool{
New: func() interface{} {
return gzip.NewWriter(nil)
},
}
)
// Gzip compresses as slice of bytes using gzip compression.
func Gzip(data []byte) []byte {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
// In theory this should never fail, and I don't know how to make the gzip buffered reader fail in testing.
_, _ = gz.Write(data)
_ = gz.Close()
return b.Bytes()
buf := bufPool.Get().(*bytes.Buffer)
gz := gzipPool.Get().(*gzip.Writer)
buf.Reset()
r, w := io.Pipe()
gz.Reset(w)
go func() {
_, _ = gz.Write(data)
_ = gz.Close()
_ = w.Close()
}()
n, _ := buf.ReadFrom(r)
buf.Truncate(int(n))
_ = r.Close()
res, _ := io.ReadAll(buf)
bufPool.Put(buf)
gzipPool.Put(gz)
return res
}
// Gunzip decompresses a gzip compressed slice of bytes.
func Gunzip(data []byte) (out []byte, err error) {
var gz *gzip.Reader
gz, err = gzip.NewReader(bytes.NewReader(data))
gz, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return
return nil, err
}
return io.ReadAll(gz)
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
var n int64
n, _ = buf.ReadFrom(gz)
err = gz.Close()
buf.Truncate(int(n))
res, _ := io.ReadAll(buf)
bufPool.Put(buf)
return res, err
}
// B64e encodes the given slice of bytes into base64 standard encoding.
func B64e(cytes []byte) (data string) {
data = base64.StdEncoding.EncodeToString(cytes)
return
func B64e(in []byte) (out string) {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
buf.Grow(base64.StdEncoding.EncodedLen(len(in)))
b64 := base64.NewEncoder(base64.StdEncoding, buf)
_, _ = b64.Write(in)
_ = b64.Close()
res := buf.Bytes()
bufPool.Put(buf)
return string(res)
}
// B64d decodes the given string into the original slice of bytes.
// Do note that this is for non critical tasks, it has no error handling for purposes of clean code.
func B64d(str string) (data []byte) {
if len(str) == 0 {
return nil
}
data, _ = base64.StdEncoding.DecodeString(str)
return data
}
// UnpackStr UNsafely unpacks (usually banners) that have been base64'd and then gzip'd.
func UnpackStr(encoded string) (string, error) {
dcytes, err := Gunzip(B64d(encoded))
if err != nil {
one := B64d(encoded)
if len(one) == 0 {
return "", errors.New("0 length base64 decoding result")
}
dcytes, err := Gunzip(one)
if err != nil && !errors.Is(err, io.EOF) {
return "", err
}
return string(dcytes), nil

View File

@ -2,11 +2,13 @@ package squish
import (
"bytes"
"encoding/base64"
"testing"
"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.
@ -14,8 +16,9 @@ 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.
`
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=`
func TestGzip(t *testing.T) {
gsUp := Gzip([]byte(lip))
@ -32,7 +35,7 @@ func TestGzip(t *testing.T) {
t.Logf("[PASS] Gzip compress succeeded, squished %d bytes.", profit)
hosDown, err := Gunzip(gsUp)
if err != nil {
t.Fatalf("Gzip decompression failed: %e", err)
t.Fatalf("Gzip decompression failed: %s", err.Error())
}
if !bytes.Equal(hosDown, []byte(lip)) {
t.Fatalf("[FAIL] Gzip decompression failed, data does not appear to be the same after decompression")
@ -46,20 +49,138 @@ func TestGzip(t *testing.T) {
t.Fatalf("[FAIL] Gunzip didn't fail on nil input")
}
}
func TestUnpackStr(t *testing.T) {
packed := B64e(Gzip([]byte(lip)))
unpacked, err := UnpackStr(packed)
switch {
case err != nil:
t.Fatalf("[FAIL] %e", err)
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.")
default:
t.Logf("[PASS] TestUnpackStr")
func TestGunzipMustFails(t *testing.T) {
blank := ""
_, err := Gunzip([]byte(blank))
if err == nil {
t.Fatalf("[FAIL] Gunzip didn't fail on empty input")
}
_, nilerr := UnpackStr("")
if nilerr == nil {
t.Fatalf("[FAIL] unpackstr didn't fail on empty input")
_, err = UnpackStr(blank)
if err == nil {
t.Fatalf("[FAIL] UnpackStr didn't fail on empty input")
}
junk := "junk"
_, err = Gunzip([]byte(junk))
if err == nil {
t.Fatalf("[FAIL] Gunzip didn't fail on junk input")
}
_, err = UnpackStr(junk)
if err == nil {
t.Fatalf("[FAIL] UnpackStr didn't fail on junk input")
}
}
func TestGzipEntropic(t *testing.T) {
for i := 0; i < 50; i++ {
dat := []byte(entropy.RandStr(entropy.RNG(55) * 1024))
for len(dat) < 1024 {
dat = []byte(entropy.RandStr(entropy.RNG(55) * 1024))
}
gzTest(dat, t)
}
}
func gzTest(dat []byte, t *testing.T) {
t.Logf("Testing Gzip on %d bytes of data", len(dat))
gsUp := Gzip(dat)
if bytes.Equal(gsUp, dat) {
t.Fatalf("[FAIL] Gzip didn't change the data at all despite being error free...")
}
if len(gsUp) == len(dat) || len(gsUp) > len(dat) {
t.Fatalf("[FAIL] Gzip didn't change the sise of the data at all (or it grew)... before: %d after: %d",
len(dat), len(gsUp))
}
if len(gsUp) == 0 {
t.Fatalf("[FAIL] ended up with 0 bytes after compression...")
}
profit := len(dat) - len(gsUp)
t.Logf("[PASS] Gzip compress succeeded, squished %d bytes.", profit)
hosDown, err := Gunzip(gsUp)
if err != nil {
t.Fatalf("Gzip decompression failed: %s", err.Error())
}
if !bytes.Equal(hosDown, dat) {
t.Fatalf("[FAIL] Gzip decompression failed, data does not appear to be the same after decompression")
}
if len(hosDown) != len(dat) {
t.Fatalf("[FAIL] Gzip decompression failed, data [%d] does not appear to be the same [%d] length after decompression", hosDown, len(dat))
}
t.Logf("[PASS] Gzip decompress succeeded, restored %d bytes.", profit)
}
func TestGzipDeterministic(t *testing.T) {
packed := Gzip([]byte(lip))
for n := 0; n < 10; n++ {
again := Gzip([]byte(lip))
if !bytes.Equal(again, packed) {
t.Fatalf("[FAIL] Gzip is not deterministic")
}
}
}
func TestUnpackStr(t *testing.T) { //nolint:cyclop
gzd := Gzip([]byte(lip))
if len(gzd) == 0 {
t.Fatalf("[FAIL] Gzip failed to compress data")
}
gzdSanity, gzdErr := Gunzip(gzd)
if gzdErr != nil {
t.Fatalf("Gzip failed: %s", gzdErr.Error())
}
if !bytes.Equal(gzdSanity, []byte(lip)) {
t.Fatalf("Bytes not equal after Gzip: %v != %v", gzdSanity, []byte(lip))
}
packed := B64e(gzd)
if len(packed) == 0 {
t.Fatalf("[FAIL] B64e failed to encode data")
}
t.Logf("Packed: %s", packed)
sanity1, err1 := base64.StdEncoding.DecodeString(packed)
if err1 != nil {
t.Fatalf("b64 failed: %s", err1.Error())
}
if !bytes.Equal(sanity1, gzd) {
t.Fatalf("Bytes not equal after b64: %v != %v", sanity1, gzd)
}
sanity2, err2 := Gunzip(sanity1)
if err2 != nil {
t.Fatalf("Gzip failed: %s", err2.Error())
}
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())
}
}
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
View File

@ -5,15 +5,21 @@ 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 {
log.Error().Str("data", s).Err(err).Msg("Fprint failed!")
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())
}
}