138 lines
2.6 KiB
Go
138 lines
2.6 KiB
Go
|
|
package threadsafeset
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"context"
|
||
|
|
"math/rand/v2"
|
||
|
|
"os"
|
||
|
|
"runtime"
|
||
|
|
"slices"
|
||
|
|
"strings"
|
||
|
|
"sync"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
var Words = func() []string {
|
||
|
|
f, err := os.Open("../words")
|
||
|
|
if err != nil {
|
||
|
|
panic("error opening ../words: " + err.Error())
|
||
|
|
}
|
||
|
|
defer f.Close()
|
||
|
|
scanner := bufio.NewScanner(f)
|
||
|
|
results := make([]string, 0)
|
||
|
|
for scanner.Scan() {
|
||
|
|
if line := strings.TrimSpace(scanner.Text()); len(line) > 0 {
|
||
|
|
results = append(results, line)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if err := scanner.Err(); err != nil {
|
||
|
|
panic("error from scanner.Err(): " + err.Error())
|
||
|
|
}
|
||
|
|
return results
|
||
|
|
}()
|
||
|
|
|
||
|
|
func CloneWords() []string {
|
||
|
|
results := make([]string, 0, len(Words))
|
||
|
|
for _, word := range Words {
|
||
|
|
results = append(results, strings.Clone(word))
|
||
|
|
}
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
func RandomWord() string {
|
||
|
|
return Words[rand.IntN(len(Words))]
|
||
|
|
}
|
||
|
|
|
||
|
|
func ContextNotDone(ctx context.Context) bool {
|
||
|
|
select {
|
||
|
|
case <-ctx.Done():
|
||
|
|
return false
|
||
|
|
default:
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAdd(t *testing.T) {
|
||
|
|
set := New[string]()
|
||
|
|
var wg sync.WaitGroup
|
||
|
|
ctx, cancel := context.WithTimeout(t.Context(), time.Second*2)
|
||
|
|
defer cancel()
|
||
|
|
for range runtime.NumCPU() * 10 {
|
||
|
|
wg.Go(func() {
|
||
|
|
for i := 0; i < len(Words) && ContextNotDone(ctx); i++ {
|
||
|
|
set.Add(Words[i])
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
wg.Wait()
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestContains(t *testing.T) {
|
||
|
|
set := New(Words...)
|
||
|
|
var wg sync.WaitGroup
|
||
|
|
ctx, cancel := context.WithTimeout(t.Context(), time.Second*2)
|
||
|
|
defer cancel()
|
||
|
|
for range runtime.NumCPU() * 10 {
|
||
|
|
wg.Go(func() {
|
||
|
|
for i := 0; i < len(Words) && ContextNotDone(ctx); i++ {
|
||
|
|
if !set.Contains(Words[i]) {
|
||
|
|
panic("error: " + Words[i] + " not found")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
wg.Wait()
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRemove(t *testing.T) {
|
||
|
|
set := New(Words...)
|
||
|
|
var wg sync.WaitGroup
|
||
|
|
for range runtime.NumCPU() * 10 {
|
||
|
|
wg.Go(func() {
|
||
|
|
for _, word := range Words {
|
||
|
|
set.Remove(word)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
wg.Wait()
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSize(t *testing.T) {
|
||
|
|
set := New[string]()
|
||
|
|
for index, word := range Words {
|
||
|
|
if want, got := index, set.Size(); want != got {
|
||
|
|
t.Fatalf("error: wanted size %d; got %d\n", want, got)
|
||
|
|
}
|
||
|
|
set.Add(word)
|
||
|
|
if want, got := index+1, set.Size(); want != got {
|
||
|
|
t.Fatalf("error: wanted size %d; got %d\n", want, got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestToSlice(t *testing.T) {
|
||
|
|
want := CloneWords()
|
||
|
|
slices.Sort(want)
|
||
|
|
set := New(Words...)
|
||
|
|
got := set.ToSlice()
|
||
|
|
slices.Sort(got)
|
||
|
|
if !slices.Equal(want, got) {
|
||
|
|
t.Fatalf("error: mismatched ToSlice() output\n")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkAdd(b *testing.B) {
|
||
|
|
set := New[string]()
|
||
|
|
for b.Loop() {
|
||
|
|
set.Add(RandomWord())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkContains(b *testing.B) {
|
||
|
|
set := New(Words...)
|
||
|
|
for b.Loop() {
|
||
|
|
set.Contains(RandomWord())
|
||
|
|
}
|
||
|
|
}
|