Added basic testing

Started doing all comparisons in Runes
string cound emojis as 4 runes instead of just the 1 it is
This commit is contained in:
Matthew Stobbs 2024-02-02 01:04:48 -07:00
parent d00ac81a29
commit 79c235de31
3 changed files with 107 additions and 33 deletions

View File

@ -54,18 +54,18 @@ func NewTinDConfig() *Config {
//
// This does slow down initialization, but the final product still runs fast to
// generate ids
func (c *Config) WithRuneset(r string) *Config {
func (c *Config) WithRuneset(r []rune) *Config {
c.runes = ensureUniqueRunes(r)
c.runesize = len(c.runes)
c.mod = byte(c.runesize - 1)
return c
}
func ensureUniqueRunes(r string) []rune {
func ensureUniqueRunes(r []rune) []rune {
unq := make(map[rune]int)
var index int
// loop through the runes to find duplicates
for _, v := range []rune(r) {
for _, v := range r {
// if the current rune hasn't been seen yet, assign it
if _, ok := unq[v]; !ok {
unq[v] = index
@ -97,17 +97,21 @@ func (c *Config) Zero() TinD {
// Gen generates a TinD with the given configuration
func (c *Config) Gen() TinD {
t := TinD{
bytes: make([]byte, c.size),
config: c,
}
_, err := rand.Read(t.bytes)
bytes := make([]byte, c.size)
runes := make([]rune, c.size)
_, err := rand.Read(bytes)
if err != nil {
log.Fatalln(err)
}
// Make sure each byte fits the rune so it can be encoded and decoded
for i := 0; i < c.size; i++ {
t.bytes[i] = t.bytes[i] & c.mod
bytes[i] = bytes[i] & c.mod
runes[i] = c.runes[bytes[i]]
}
t := TinD{
bytes: bytes,
runes: runes,
config: c,
}
return t
}
@ -119,12 +123,15 @@ func (c *Config) FromString(in string) (TinD, error) {
if len(in) != c.size {
return c.Zero(), errors.New("given id isn't the same size as the configuration")
}
t := make([]byte, c.size)
t := TinD{
bytes: make([]byte, c.size),
config: c,
}
r := []rune(in)
for i := 0; i < l; i++ {
for i := 0; i < c.size; i++ {
for j := 0; j < c.runesize; j++ {
if r[i] == alphabet[j] {
t[i] = byte(j)
if r[i] == c.runes[j] {
t.bytes[i] = byte(j)
break
}
}

23
tind.go
View File

@ -10,11 +10,6 @@ package tind
// TinD is at it's core a byte slice. It's default alphabet can be replaced with
// whatever unicode characters you want, as the alphabet it uses is just a slice
// of runes
import (
"crypto/rand"
"errors"
"log"
)
// defaults are just a call to NewConfig()
var defaults = NewTinDConfig()
@ -26,7 +21,7 @@ var defaults = NewTinDConfig()
// used, the more randomness can be done.
type TinD struct {
bytes []byte
str string
runes []rune
config *Config
}
@ -37,11 +32,12 @@ func Gen() TinD {
// String returns the Human Readable form of the TinD
func (t TinD) String() string {
if len(t.str) == t.config.size {
return t.str
return string(t.runes)
}
t.str = makeString(t.bytes, t.config.size, t.config.runset)
return t.str
// Runes returns the Rune array of the TinD
func (t TinD) Runes() []rune {
return t.runes
}
// Bytes returns the raw bytes of the TinD
@ -49,10 +45,3 @@ func (t TinD) Bytes() []byte {
return t.bytes
}
func makeString(bytes []byte, size int, runeset []rune) string {
r := make([]rune, size)
for i := 0; i < size; i++ {
r[i] = runeset[bytes[i]]
}
return string(r)
}

78
tind_test.go Normal file
View File

@ -0,0 +1,78 @@
package tind
import (
"log"
"testing"
"time"
)
func Test_GenTinD_With_Defaults(t *testing.T) {
id := Gen()
if len(id.String()) != 4 {
t.Errorf("expecting 4 bytes, got %d", len(id.String()))
}
}
func Test_GenTinD_With_VaryingSizes(t *testing.T) {
tests := []struct {
size int
runes []rune
expectedRuneSize int
}{
{
size: 4,
runes: []rune("abcdefghijklmnopqrstuvwxyz"),
expectedRuneSize: 26,
}, {
size: 8,
runes: []rune("HGHHGHGJJHSHGABBJEBJBE"),
expectedRuneSize: 7,
}, {
size: 8,
runes: []rune("😀😡🤕🎃🤚🫲🙀😭"),
expectedRuneSize: 8,
},
}
for _, v := range tests {
tt := NewTinDConfig().WithRuneset(v.runes).WithSize(v.size).Gen()
if len(tt.Runes()) != v.size {
t.Errorf("config set size to %d, but generated an id of size %d. id: %s", v.size, len([]rune(tt.String())), tt.String())
}
if tt.config.runesize != v.expectedRuneSize {
t.Errorf("expected runsize of %d, got runesize of %d", len(v.runes), tt.config.runesize)
}
}
}
//func Test_Generate_With_Collisions(t *testing.T) {
// log.Println("Test TinD for collisions, base on number of iterations and time")
//
// maxSize := 16
// for size := 4; size < maxSize; size++ {
// CheckCollisions(size)
// }
//}
func CheckCollisions(tindSize int) {
ids := make(map[string]struct{})
collided := false
tc := NewTinDConfig().WithSize(tindSize)
var iters uint64
start := time.Now()
log.Printf("Starting check with size %d", tindSize)
for !collided {
iters++
nt := tc.Gen()
if _, ok := ids[nt.String()]; ok {
collided = true
} else {
ids[nt.String()] = struct{}{}
}
// Print a message every 2000000 iterations saying we are still working
if iters%2000000 == 0 {
log.Printf("Still no collions on size %d after %d iterations", tindSize, iters)
}
}
log.Printf("Collision found after %v and %d iterations with size of %d", time.Since(start), iters, tindSize)
}