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

27
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 // 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 // whatever unicode characters you want, as the alphabet it uses is just a slice
// of runes // of runes
import (
"crypto/rand"
"errors"
"log"
)
// defaults are just a call to NewConfig() // defaults are just a call to NewConfig()
var defaults = NewTinDConfig() var defaults = NewTinDConfig()
@ -24,9 +19,9 @@ var defaults = NewTinDConfig()
// Each byte can be the value 0 -> len(alphabet) that is used, to ensure // Each byte can be the value 0 -> len(alphabet) that is used, to ensure
// values can be encoded and decoded reliably. The longer the alphabet // values can be encoded and decoded reliably. The longer the alphabet
// used, the more randomness can be done. // used, the more randomness can be done.
type TinD struct{ type TinD struct {
bytes []byte bytes []byte
str string runes []rune
config *Config config *Config
} }
@ -37,11 +32,12 @@ func Gen() TinD {
// String returns the Human Readable form of the TinD // String returns the Human Readable form of the TinD
func (t TinD) String() string { func (t TinD) String() string {
if len(t.str) == t.config.size { return string(t.runes)
return t.str }
}
t.str = makeString(t.bytes, t.config.size, t.config.runset) // Runes returns the Rune array of the TinD
return t.str func (t TinD) Runes() []rune {
return t.runes
} }
// Bytes returns the raw bytes of the TinD // Bytes returns the raw bytes of the TinD
@ -49,10 +45,3 @@ func (t TinD) Bytes() []byte {
return t.bytes 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)
}