This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fakeReadCloser struct {
|
||||
r *bytes.Reader
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newFakeReadCloser(data []byte) *fakeReadCloser {
|
||||
return &fakeReadCloser{r: bytes.NewReader(data)}
|
||||
}
|
||||
|
||||
func (f *fakeReadCloser) Read(p []byte) (int, error) {
|
||||
return f.r.Read(p)
|
||||
}
|
||||
|
||||
func (f *fakeReadCloser) Close() error {
|
||||
f.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBufferedReadCloser_ReadAll(t *testing.T) {
|
||||
data := []byte("hello world")
|
||||
brc := NewBufferedReadCloser(newFakeReadCloser(data))
|
||||
|
||||
buf := make([]byte, 64)
|
||||
total := 0
|
||||
for {
|
||||
n, err := brc.Read(buf[total:])
|
||||
total += n
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf[:total], data) {
|
||||
t.Fatalf("got %q want %q", buf[:total], data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedReadCloser_BufferedAllThenRead(t *testing.T) {
|
||||
data := []byte("foobar baz")
|
||||
brc := NewBufferedReadCloser(newFakeReadCloser(data))
|
||||
|
||||
all, err := brc.BufferedAll()
|
||||
if err != nil {
|
||||
t.Fatalf("BufferedAll err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(all, data) {
|
||||
t.Fatalf("BufferedAll got %q want %q", all, data)
|
||||
}
|
||||
|
||||
// after BufferedAll, Reset put us in BufferReading mode - we can read again
|
||||
out, err := io.ReadAll(brc)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadAll err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(out, data) {
|
||||
t.Fatalf("ReadAll got %q want %q", out, data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedReadCloser_FullyReadResetReread(t *testing.T) {
|
||||
data := []byte("abcdefghij")
|
||||
brc := NewBufferedReadCloser(newFakeReadCloser(data))
|
||||
|
||||
out, err := io.ReadAll(brc)
|
||||
if err != nil {
|
||||
t.Fatalf("first ReadAll err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(out, data) {
|
||||
t.Fatalf("first read got %q want %q", out, data)
|
||||
}
|
||||
|
||||
if err := brc.Reset(); err != nil {
|
||||
t.Fatalf("reset err: %v", err)
|
||||
}
|
||||
|
||||
out2, err := io.ReadAll(brc)
|
||||
if err != nil {
|
||||
t.Fatalf("second ReadAll err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(out2, data) {
|
||||
t.Fatalf("after reset got %q want %q", out2, data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedReadCloser_Close(t *testing.T) {
|
||||
data := []byte("xyz")
|
||||
inner := newFakeReadCloser(data)
|
||||
brc := NewBufferedReadCloser(inner)
|
||||
|
||||
if err := brc.Close(); err != nil {
|
||||
t.Fatalf("close err: %v", err)
|
||||
}
|
||||
if !inner.closed {
|
||||
t.Fatal("inner not closed")
|
||||
}
|
||||
|
||||
// double close should be no-op
|
||||
if err := brc.Close(); err != nil {
|
||||
t.Fatalf("second close err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedReadCloser_ResetWithoutRead(t *testing.T) {
|
||||
data := []byte("abc")
|
||||
brc := NewBufferedReadCloser(newFakeReadCloser(data))
|
||||
|
||||
if err := brc.Reset(); err != nil {
|
||||
t.Fatalf("reset err: %v", err)
|
||||
}
|
||||
|
||||
out, err := io.ReadAll(brc)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadAll err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(out, data) {
|
||||
t.Fatalf("got %q want %q", out, data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCASMutex_LockUnlock(t *testing.T) {
|
||||
m := NewCASMutex()
|
||||
m.Lock()
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func TestCASMutex_TryLock(t *testing.T) {
|
||||
m := NewCASMutex()
|
||||
if !m.TryLock() {
|
||||
t.Fatal("TryLock should succeed on fresh mutex")
|
||||
}
|
||||
if m.TryLock() {
|
||||
t.Fatal("TryLock should fail when already locked")
|
||||
}
|
||||
m.Unlock()
|
||||
if !m.TryLock() {
|
||||
t.Fatal("TryLock should succeed after Unlock")
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func TestCASMutex_TryLockWithTimeout(t *testing.T) {
|
||||
m := NewCASMutex()
|
||||
m.Lock()
|
||||
start := time.Now()
|
||||
if m.TryLockWithTimeout(20 * time.Millisecond) {
|
||||
t.Fatal("TryLockWithTimeout should fail when locked")
|
||||
}
|
||||
if time.Since(start) < 15*time.Millisecond {
|
||||
t.Fatal("TryLockWithTimeout returned too quickly")
|
||||
}
|
||||
m.Unlock()
|
||||
|
||||
if !m.TryLockWithTimeout(50 * time.Millisecond) {
|
||||
t.Fatal("TryLockWithTimeout should succeed when unlocked")
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func TestCASMutex_TryLockWithContext_Cancel(t *testing.T) {
|
||||
m := NewCASMutex()
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
if m.TryLockWithContext(ctx) {
|
||||
t.Fatal("expected lock to fail after cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCASMutex_RLockMultiple(t *testing.T) {
|
||||
m := NewCASMutex()
|
||||
if !m.RTryLock() {
|
||||
t.Fatal("RTryLock should succeed")
|
||||
}
|
||||
if !m.RTryLock() {
|
||||
t.Fatal("Second RTryLock should succeed")
|
||||
}
|
||||
if m.TryLock() {
|
||||
t.Fatal("Write TryLock should fail with read locks held")
|
||||
}
|
||||
m.RUnlock()
|
||||
m.RUnlock()
|
||||
if !m.TryLock() {
|
||||
t.Fatal("Write TryLock should succeed after read unlocks")
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func TestCASMutex_RLocker(t *testing.T) {
|
||||
m := NewCASMutex()
|
||||
rl := m.RLocker()
|
||||
rl.Lock()
|
||||
rl.Unlock()
|
||||
}
|
||||
|
||||
func TestCASMutex_Concurrent(t *testing.T) {
|
||||
m := NewCASMutex()
|
||||
var counter int64
|
||||
const n = 50
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
m.Lock()
|
||||
atomic.AddInt64(&counter, 1)
|
||||
m.Unlock()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if atomic.LoadInt64(&counter) != n {
|
||||
t.Fatalf("counter=%d want %d", counter, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCASMutex_RTryLockWithTimeout(t *testing.T) {
|
||||
m := NewCASMutex()
|
||||
m.Lock()
|
||||
if m.RTryLockWithTimeout(20 * time.Millisecond) {
|
||||
t.Fatal("RTryLockWithTimeout should fail when write-locked")
|
||||
}
|
||||
m.Unlock()
|
||||
if !m.RTryLockWithTimeout(20 * time.Millisecond) {
|
||||
t.Fatal("RTryLockWithTimeout should succeed when free")
|
||||
}
|
||||
m.RUnlock()
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func waitForCalls(t *testing.T, calls *int64, want int64, max time.Duration) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(max)
|
||||
for time.Now().Before(deadline) {
|
||||
if atomic.LoadInt64(calls) >= want {
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedCombiningInvoker_SingleRequest(t *testing.T) {
|
||||
var calls int64
|
||||
d := NewDelayedCombiningInvoker(func() {
|
||||
atomic.AddInt64(&calls, 1)
|
||||
}, 20*time.Millisecond, 200*time.Millisecond)
|
||||
|
||||
d.Request()
|
||||
|
||||
waitForCalls(t, &calls, 1, 2*time.Second)
|
||||
if c := atomic.LoadInt64(&calls); c != 1 {
|
||||
t.Fatalf("calls=%d want 1", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedCombiningInvoker_TwoRequestsCombine(t *testing.T) {
|
||||
var calls int64
|
||||
d := NewDelayedCombiningInvoker(func() {
|
||||
atomic.AddInt64(&calls, 1)
|
||||
}, 50*time.Millisecond, 1*time.Second)
|
||||
|
||||
d.Request()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.Request()
|
||||
|
||||
waitForCalls(t, &calls, 1, 2*time.Second)
|
||||
if c := atomic.LoadInt64(&calls); c != 1 {
|
||||
t.Fatalf("calls=%d want 1 (should be combined)", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedCombiningInvoker_SequentialRuns(t *testing.T) {
|
||||
var calls int64
|
||||
d := NewDelayedCombiningInvoker(func() {
|
||||
atomic.AddInt64(&calls, 1)
|
||||
}, 20*time.Millisecond, 200*time.Millisecond)
|
||||
|
||||
d.Request()
|
||||
waitForCalls(t, &calls, 1, 2*time.Second)
|
||||
if c := atomic.LoadInt64(&calls); c != 1 {
|
||||
t.Fatalf("after first wait calls=%d want 1", c)
|
||||
}
|
||||
|
||||
// allow executorRunning to clear
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
d.Request()
|
||||
waitForCalls(t, &calls, 2, 2*time.Second)
|
||||
if c := atomic.LoadInt64(&calls); c != 2 {
|
||||
t.Fatalf("calls=%d want 2", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedCombiningInvoker_ExecuteNow(t *testing.T) {
|
||||
var calls int64
|
||||
d := NewDelayedCombiningInvoker(func() {
|
||||
atomic.AddInt64(&calls, 1)
|
||||
}, 5*time.Second, 30*time.Second)
|
||||
|
||||
d.Request()
|
||||
if !d.HasPendingRequests() {
|
||||
t.Fatal("should have pending requests")
|
||||
}
|
||||
|
||||
if !d.ExecuteNow() {
|
||||
t.Fatal("ExecuteNow should return true when running")
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if atomic.LoadInt64(&calls) >= 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if c := atomic.LoadInt64(&calls); c != 1 {
|
||||
t.Fatalf("calls=%d want 1 (ExecuteNow should fire well before delay)", c)
|
||||
}
|
||||
|
||||
// allow internal state cleanup
|
||||
for i := 0; i < 100; i++ {
|
||||
if !d.HasPendingRequests() {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if d.ExecuteNow() {
|
||||
t.Fatal("ExecuteNow should return false when no pending")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedCombiningInvoker_Cancel(t *testing.T) {
|
||||
var calls int64
|
||||
d := NewDelayedCombiningInvoker(func() {
|
||||
atomic.AddInt64(&calls, 1)
|
||||
}, 500*time.Millisecond, 5*time.Second)
|
||||
|
||||
d.Request()
|
||||
d.CancelPendingRequests()
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
if c := atomic.LoadInt64(&calls); c != 0 {
|
||||
t.Fatalf("calls=%d want 0 after cancel", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedCombiningInvoker_HasAndCountPending(t *testing.T) {
|
||||
d := NewDelayedCombiningInvoker(func() {
|
||||
// no-op
|
||||
}, 500*time.Millisecond, 5*time.Second)
|
||||
|
||||
if d.HasPendingRequests() {
|
||||
t.Fatal("should not have pending before any Request")
|
||||
}
|
||||
if d.CountPendingRequests() != 0 {
|
||||
t.Fatalf("count=%d want 0", d.CountPendingRequests())
|
||||
}
|
||||
|
||||
d.Request()
|
||||
if !d.HasPendingRequests() {
|
||||
t.Fatal("should have pending")
|
||||
}
|
||||
if d.CountPendingRequests() < 1 {
|
||||
t.Fatalf("count=%d want >=1", d.CountPendingRequests())
|
||||
}
|
||||
d.CancelPendingRequests()
|
||||
}
|
||||
|
||||
func TestDelayedCombiningInvoker_Listeners(t *testing.T) {
|
||||
var (
|
||||
startCount int64
|
||||
doneCount int64
|
||||
requestCount int64
|
||||
)
|
||||
|
||||
d := NewDelayedCombiningInvoker(func() {
|
||||
// no-op
|
||||
}, 20*time.Millisecond, 200*time.Millisecond)
|
||||
|
||||
d.RegisterOnExecutionStart(func(immediately bool) {
|
||||
atomic.AddInt64(&startCount, 1)
|
||||
})
|
||||
d.RegisterOnExecutionDone(func() {
|
||||
atomic.AddInt64(&doneCount, 1)
|
||||
})
|
||||
d.RegisterOnRequest(func(pending int, initial bool) {
|
||||
atomic.AddInt64(&requestCount, 1)
|
||||
})
|
||||
|
||||
d.Request()
|
||||
|
||||
waitForCalls(t, &doneCount, 1, 2*time.Second)
|
||||
|
||||
if atomic.LoadInt64(&startCount) != 1 {
|
||||
t.Fatalf("startCount=%d want 1", startCount)
|
||||
}
|
||||
if atomic.LoadInt64(&doneCount) != 1 {
|
||||
t.Fatalf("doneCount=%d want 1", doneCount)
|
||||
}
|
||||
if atomic.LoadInt64(&requestCount) != 1 {
|
||||
t.Fatalf("requestCount=%d want 1", requestCount)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMultiMutex_LockDifferentKeys(t *testing.T) {
|
||||
mm := NewMultiMutex[string]()
|
||||
mm.Lock("a")
|
||||
mm.Lock("b")
|
||||
mm.Unlock("a")
|
||||
mm.Unlock("b")
|
||||
}
|
||||
|
||||
func TestMultiMutex_TryLockSameKey(t *testing.T) {
|
||||
mm := NewMultiMutex[string]()
|
||||
if !mm.TryLock("k") {
|
||||
t.Fatal("TryLock should succeed first time")
|
||||
}
|
||||
if mm.TryLock("k") {
|
||||
t.Fatal("TryLock should fail second time")
|
||||
}
|
||||
mm.Unlock("k")
|
||||
if !mm.TryLock("k") {
|
||||
t.Fatal("TryLock should succeed after unlock")
|
||||
}
|
||||
mm.Unlock("k")
|
||||
}
|
||||
|
||||
func TestMultiMutex_TryLockDifferentKeys(t *testing.T) {
|
||||
mm := NewMultiMutex[int]()
|
||||
if !mm.TryLock(1) {
|
||||
t.Fatal("TryLock(1) failed")
|
||||
}
|
||||
if !mm.TryLock(2) {
|
||||
t.Fatal("TryLock(2) failed - different keys should be independent")
|
||||
}
|
||||
mm.Unlock(1)
|
||||
mm.Unlock(2)
|
||||
}
|
||||
|
||||
func TestMultiMutex_RLockMultiple(t *testing.T) {
|
||||
mm := NewMultiMutex[string]()
|
||||
if !mm.RTryLock("k") {
|
||||
t.Fatal("first RTryLock failed")
|
||||
}
|
||||
if !mm.RTryLock("k") {
|
||||
t.Fatal("second RTryLock failed")
|
||||
}
|
||||
mm.RUnlock("k")
|
||||
mm.RUnlock("k")
|
||||
}
|
||||
|
||||
func TestMultiMutex_TryLockWithTimeout(t *testing.T) {
|
||||
mm := NewMultiMutex[string]()
|
||||
mm.Lock("k")
|
||||
if mm.TryLockWithTimeout("k", 10*time.Millisecond) {
|
||||
t.Fatal("expected timeout failure")
|
||||
}
|
||||
mm.Unlock("k")
|
||||
}
|
||||
|
||||
func TestMultiMutex_TryLockWithContext(t *testing.T) {
|
||||
mm := NewMultiMutex[string]()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||
defer cancel()
|
||||
if !mm.TryLockWithContext(ctx, "k") {
|
||||
t.Fatal("TryLockWithContext should succeed on free key")
|
||||
}
|
||||
mm.Unlock("k")
|
||||
}
|
||||
|
||||
func TestMultiMutex_GetAndGetCAS(t *testing.T) {
|
||||
mm := NewMultiMutex[string]()
|
||||
l := mm.Get("a")
|
||||
if l == nil {
|
||||
t.Fatal("Get returned nil")
|
||||
}
|
||||
cas := mm.GetCAS("a")
|
||||
if cas == nil {
|
||||
t.Fatal("GetCAS returned nil")
|
||||
}
|
||||
rl := mm.RLocker("a")
|
||||
if rl == nil {
|
||||
t.Fatal("RLocker returned nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMutexSet_BasicLockUnlock(t *testing.T) {
|
||||
ms := NewMutexSet[string]()
|
||||
ms.Lock("a")
|
||||
ms.Unlock("a")
|
||||
ms.RLock("b")
|
||||
ms.RUnlock("b")
|
||||
}
|
||||
|
||||
func TestMutexSet_DifferentKeysIndependent(t *testing.T) {
|
||||
ms := NewMutexSet[int]()
|
||||
ms.Lock(1)
|
||||
ms.Lock(2)
|
||||
ms.Unlock(1)
|
||||
ms.Unlock(2)
|
||||
}
|
||||
|
||||
func TestMutexSet_SameKeyMutuallyExclusive(t *testing.T) {
|
||||
ms := NewMutexSet[string]()
|
||||
var counter int64
|
||||
const n = 50
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ms.Lock("shared")
|
||||
atomic.AddInt64(&counter, 1)
|
||||
ms.Unlock("shared")
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if atomic.LoadInt64(&counter) != n {
|
||||
t.Fatalf("got %d want %d", counter, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMutexSet_RLockMultiple(t *testing.T) {
|
||||
ms := NewMutexSet[string]()
|
||||
ms.RLock("k")
|
||||
ms.RLock("k")
|
||||
ms.RUnlock("k")
|
||||
ms.RUnlock("k")
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonOpt_NewAndEmpty(t *testing.T) {
|
||||
o := NewJsonOpt[int](42)
|
||||
if !o.IsSet() {
|
||||
t.Fatal("expected IsSet=true")
|
||||
}
|
||||
if o.IsUnset() {
|
||||
t.Fatal("expected IsUnset=false")
|
||||
}
|
||||
|
||||
e := EmptyJsonOpt[int]()
|
||||
if e.IsSet() {
|
||||
t.Fatal("expected IsSet=false")
|
||||
}
|
||||
if !e.IsUnset() {
|
||||
t.Fatal("expected IsUnset=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonOpt_Value(t *testing.T) {
|
||||
o := NewJsonOpt[string]("hello")
|
||||
v, ok := o.Value()
|
||||
if !ok || v != "hello" {
|
||||
t.Fatalf("got (%q,%v)", v, ok)
|
||||
}
|
||||
|
||||
e := EmptyJsonOpt[string]()
|
||||
v, ok = e.Value()
|
||||
if ok || v != "" {
|
||||
t.Fatalf("empty got (%q,%v)", v, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonOpt_ValueOrNil(t *testing.T) {
|
||||
o := NewJsonOpt[int](7)
|
||||
p := o.ValueOrNil()
|
||||
if p == nil || *p != 7 {
|
||||
t.Fatalf("expected ptr to 7")
|
||||
}
|
||||
e := EmptyJsonOpt[int]()
|
||||
if e.ValueOrNil() != nil {
|
||||
t.Fatal("expected nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonOpt_ValueDblPtrOrNil(t *testing.T) {
|
||||
o := NewJsonOpt[int](7)
|
||||
p := o.ValueDblPtrOrNil()
|
||||
if p == nil || *p == nil || **p != 7 {
|
||||
t.Fatalf("expected double ptr to 7")
|
||||
}
|
||||
e := EmptyJsonOpt[int]()
|
||||
if e.ValueDblPtrOrNil() != nil {
|
||||
t.Fatal("expected nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonOpt_MustValue(t *testing.T) {
|
||||
o := NewJsonOpt[int](9)
|
||||
if o.MustValue() != 9 {
|
||||
t.Fatal("MustValue wrong")
|
||||
}
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Fatal("expected panic")
|
||||
}
|
||||
}()
|
||||
EmptyJsonOpt[int]().MustValue()
|
||||
}
|
||||
|
||||
func TestJsonOpt_IfSet(t *testing.T) {
|
||||
called := false
|
||||
NewJsonOpt[int](1).IfSet(func(v int) {
|
||||
called = true
|
||||
if v != 1 {
|
||||
t.Fatalf("v=%d", v)
|
||||
}
|
||||
})
|
||||
if !called {
|
||||
t.Fatal("IfSet did not invoke fn")
|
||||
}
|
||||
|
||||
called = false
|
||||
EmptyJsonOpt[int]().IfSet(func(v int) { called = true })
|
||||
if called {
|
||||
t.Fatal("IfSet invoked fn on empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonOpt_MarshalJSON(t *testing.T) {
|
||||
o := NewJsonOpt[int](5)
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != "5" {
|
||||
t.Fatalf("got %s", b)
|
||||
}
|
||||
|
||||
e := EmptyJsonOpt[int]()
|
||||
b, err = json.Marshal(e)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != "null" {
|
||||
t.Fatalf("got %s", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonOpt_UnmarshalJSON(t *testing.T) {
|
||||
var o JsonOpt[int]
|
||||
if err := json.Unmarshal([]byte("42"), &o); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !o.IsSet() {
|
||||
t.Fatal("should be set")
|
||||
}
|
||||
if v, _ := o.Value(); v != 42 {
|
||||
t.Fatalf("got %d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonOpt_StructWithJsonOpt(t *testing.T) {
|
||||
type S struct {
|
||||
A JsonOpt[int] `json:"a"`
|
||||
B JsonOpt[string] `json:"b"`
|
||||
}
|
||||
s := S{A: NewJsonOpt[int](1), B: EmptyJsonOpt[string]()}
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != `{"a":1,"b":null}` {
|
||||
t.Fatalf("got %s", b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStack_PushPop(t *testing.T) {
|
||||
s := NewStack[int](false, 4)
|
||||
s.Push(1)
|
||||
s.Push(2)
|
||||
s.Push(3)
|
||||
|
||||
if s.Length() != 3 {
|
||||
t.Fatalf("Length=%d", s.Length())
|
||||
}
|
||||
if s.Empty() {
|
||||
t.Fatal("should not be empty")
|
||||
}
|
||||
|
||||
v, err := s.Pop()
|
||||
if err != nil || v != 3 {
|
||||
t.Fatalf("Pop got (%d,%v)", v, err)
|
||||
}
|
||||
v, err = s.Pop()
|
||||
if err != nil || v != 2 {
|
||||
t.Fatalf("Pop got (%d,%v)", v, err)
|
||||
}
|
||||
v, err = s.Pop()
|
||||
if err != nil || v != 1 {
|
||||
t.Fatalf("Pop got (%d,%v)", v, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStack_PopEmpty(t *testing.T) {
|
||||
s := NewStack[int](false, 0)
|
||||
_, err := s.Pop()
|
||||
if !errors.Is(err, ErrEmptyStack) {
|
||||
t.Fatalf("expected ErrEmptyStack, got %v", err)
|
||||
}
|
||||
if !s.Empty() {
|
||||
t.Fatal("should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStack_Peek(t *testing.T) {
|
||||
s := NewStack[string](false, 0)
|
||||
if _, err := s.Peek(); !errors.Is(err, ErrEmptyStack) {
|
||||
t.Fatalf("expected ErrEmptyStack got %v", err)
|
||||
}
|
||||
s.Push("a")
|
||||
s.Push("b")
|
||||
v, err := s.Peek()
|
||||
if err != nil || v != "b" {
|
||||
t.Fatalf("Peek got (%q,%v)", v, err)
|
||||
}
|
||||
if s.Length() != 2 {
|
||||
t.Fatal("Peek must not pop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStack_OptPopOptPeek(t *testing.T) {
|
||||
s := NewStack[int](false, 0)
|
||||
if s.OptPop() != nil {
|
||||
t.Fatal("OptPop on empty should return nil")
|
||||
}
|
||||
if s.OptPeek() != nil {
|
||||
t.Fatal("OptPeek on empty should return nil")
|
||||
}
|
||||
s.Push(7)
|
||||
if p := s.OptPeek(); p == nil || *p != 7 {
|
||||
t.Fatalf("OptPeek bad")
|
||||
}
|
||||
if p := s.OptPop(); p == nil || *p != 7 {
|
||||
t.Fatalf("OptPop bad")
|
||||
}
|
||||
if !s.Empty() {
|
||||
t.Fatal("should be empty after OptPop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStack_ThreadSafe(t *testing.T) {
|
||||
s := NewStack[int](true, 0)
|
||||
var wg sync.WaitGroup
|
||||
const n = 200
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
go func(v int) {
|
||||
defer wg.Done()
|
||||
s.Push(v)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if s.Length() != n {
|
||||
t.Fatalf("Length=%d want %d", s.Length(), n)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSyncMap_SetGet(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
m.Set("a", 1)
|
||||
v, ok := m.Get("a")
|
||||
if !ok || v != 1 {
|
||||
t.Fatalf("got (%d,%v)", v, ok)
|
||||
}
|
||||
if _, ok := m.Get("missing"); ok {
|
||||
t.Fatal("expected missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_SetIfNotContains(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
if !m.SetIfNotContains("a", 1) {
|
||||
t.Fatal("first set should succeed")
|
||||
}
|
||||
if m.SetIfNotContains("a", 2) {
|
||||
t.Fatal("second set should fail")
|
||||
}
|
||||
v, _ := m.Get("a")
|
||||
if v != 1 {
|
||||
t.Fatalf("expected unchanged got %d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_SetIfNotContainsFunc(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
calls := 0
|
||||
if !m.SetIfNotContainsFunc("a", func() int { calls++; return 5 }) {
|
||||
t.Fatal("first should succeed")
|
||||
}
|
||||
if m.SetIfNotContainsFunc("a", func() int { calls++; return 6 }) {
|
||||
t.Fatal("second should fail")
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Fatalf("calls=%d want 1", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_GetAndSetIfNotContains(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
if v := m.GetAndSetIfNotContains("a", 10); v != 10 {
|
||||
t.Fatalf("got %d", v)
|
||||
}
|
||||
if v := m.GetAndSetIfNotContains("a", 99); v != 10 {
|
||||
t.Fatalf("got %d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_GetAndSetIfNotContainsFunc(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
calls := 0
|
||||
if v := m.GetAndSetIfNotContainsFunc("a", func() int { calls++; return 1 }); v != 1 {
|
||||
t.Fatalf("got %d", v)
|
||||
}
|
||||
if v := m.GetAndSetIfNotContainsFunc("a", func() int { calls++; return 2 }); v != 1 {
|
||||
t.Fatalf("got %d", v)
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Fatalf("calls=%d", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_Delete(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
m.Set("a", 1)
|
||||
if !m.Delete("a") {
|
||||
t.Fatal("delete existing returned false")
|
||||
}
|
||||
if m.Delete("a") {
|
||||
t.Fatal("delete missing returned true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_DeleteIf(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
m.Set("a", 1)
|
||||
m.Set("b", 2)
|
||||
m.Set("c", 3)
|
||||
rm := m.DeleteIf(func(k string, v int) bool { return v%2 == 1 })
|
||||
if rm != 2 {
|
||||
t.Fatalf("removed=%d", rm)
|
||||
}
|
||||
if m.Count() != 1 {
|
||||
t.Fatalf("count=%d", m.Count())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_UpdateIfExists(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
if m.UpdateIfExists("a", func(v int) int { return v + 1 }) {
|
||||
t.Fatal("should be false on missing key")
|
||||
}
|
||||
m.Set("a", 5)
|
||||
if !m.UpdateIfExists("a", func(v int) int { return v + 1 }) {
|
||||
t.Fatal("should be true on existing")
|
||||
}
|
||||
v, _ := m.Get("a")
|
||||
if v != 6 {
|
||||
t.Fatalf("v=%d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_UpdateOrInsert(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
if m.UpdateOrInsert("a", func(v int) int { return v + 1 }, 100) {
|
||||
t.Fatal("should return false on insert")
|
||||
}
|
||||
if v, _ := m.Get("a"); v != 100 {
|
||||
t.Fatalf("v=%d", v)
|
||||
}
|
||||
if !m.UpdateOrInsert("a", func(v int) int { return v + 1 }, 100) {
|
||||
t.Fatal("should return true on update")
|
||||
}
|
||||
if v, _ := m.Get("a"); v != 101 {
|
||||
t.Fatalf("v=%d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_ClearContains(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
m.Set("a", 1)
|
||||
if !m.Contains("a") {
|
||||
t.Fatal("Contains should be true")
|
||||
}
|
||||
m.Clear()
|
||||
if m.Contains("a") {
|
||||
t.Fatal("after Clear should be false")
|
||||
}
|
||||
if m.Count() != 0 {
|
||||
t.Fatalf("count=%d", m.Count())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_GetAllKeysValues(t *testing.T) {
|
||||
m := NewSyncMap[string, int]()
|
||||
m.Set("a", 1)
|
||||
m.Set("b", 2)
|
||||
m.Set("c", 3)
|
||||
keys := m.GetAllKeys()
|
||||
sort.Strings(keys)
|
||||
if len(keys) != 3 || keys[0] != "a" || keys[2] != "c" {
|
||||
t.Fatalf("keys=%v", keys)
|
||||
}
|
||||
vals := m.GetAllValues()
|
||||
sort.Ints(vals)
|
||||
if len(vals) != 3 || vals[0] != 1 || vals[2] != 3 {
|
||||
t.Fatalf("vals=%v", vals)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMap_Concurrent(t *testing.T) {
|
||||
m := NewSyncMap[int, int]()
|
||||
var wg sync.WaitGroup
|
||||
const n = 200
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
go func(k int) {
|
||||
defer wg.Done()
|
||||
m.Set(k, k*2)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if m.Count() != n {
|
||||
t.Fatalf("count=%d want %d", m.Count(), n)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSyncRingSet_AddAndContains(t *testing.T) {
|
||||
s := NewSyncRingSet[int](3)
|
||||
if !s.Add(1) {
|
||||
t.Fatal("first Add(1) should be true")
|
||||
}
|
||||
if s.Add(1) {
|
||||
t.Fatal("duplicate Add(1) should be false")
|
||||
}
|
||||
if !s.Contains(1) {
|
||||
t.Fatal("expected Contains(1)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncRingSet_CapacityEvicts(t *testing.T) {
|
||||
s := NewSyncRingSet[int](3)
|
||||
s.Add(1)
|
||||
s.Add(2)
|
||||
s.Add(3)
|
||||
s.Add(4) // should evict the oldest (1)
|
||||
if s.Contains(1) {
|
||||
t.Fatal("1 should have been evicted")
|
||||
}
|
||||
for _, v := range []int{2, 3, 4} {
|
||||
if !s.Contains(v) {
|
||||
t.Fatalf("expected %d", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncRingSet_Remove(t *testing.T) {
|
||||
s := NewSyncRingSet[string](3)
|
||||
s.Add("a")
|
||||
s.Add("b")
|
||||
if !s.Remove("a") {
|
||||
t.Fatal("remove existing failed")
|
||||
}
|
||||
if s.Remove("a") {
|
||||
t.Fatal("remove missing returned true")
|
||||
}
|
||||
if s.Contains("a") {
|
||||
t.Fatal("a should be gone")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncRingSet_AddAllRemoveAll(t *testing.T) {
|
||||
s := NewSyncRingSet[int](10)
|
||||
s.AddAll([]int{1, 2, 3, 2})
|
||||
out := s.Get()
|
||||
sort.Ints(out)
|
||||
if len(out) != 3 {
|
||||
t.Fatalf("got %v", out)
|
||||
}
|
||||
|
||||
s.RemoveAll([]int{1, 99})
|
||||
if s.Contains(1) {
|
||||
t.Fatal("1 should be removed")
|
||||
}
|
||||
if !s.Contains(2) || !s.Contains(3) {
|
||||
t.Fatal("2/3 should remain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncRingSet_AddIfNotContainsRemoveIfContains(t *testing.T) {
|
||||
s := NewSyncRingSet[string](5)
|
||||
if !s.AddIfNotContains("x") {
|
||||
t.Fatal("first should succeed")
|
||||
}
|
||||
if s.AddIfNotContains("x") {
|
||||
t.Fatal("second should fail")
|
||||
}
|
||||
if !s.RemoveIfContains("x") {
|
||||
t.Fatal("remove existing failed")
|
||||
}
|
||||
if s.RemoveIfContains("x") {
|
||||
t.Fatal("remove missing returned true")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSyncSet_Add(t *testing.T) {
|
||||
s := NewSyncSet[string]()
|
||||
if !s.Add("a") {
|
||||
t.Fatal("first add should be true")
|
||||
}
|
||||
if s.Add("a") {
|
||||
t.Fatal("duplicate add should be false")
|
||||
}
|
||||
if !s.Contains("a") {
|
||||
t.Fatal("Contains a should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncSet_AddAll(t *testing.T) {
|
||||
s := NewSyncSet[int]()
|
||||
s.AddAll([]int{1, 2, 3, 2})
|
||||
if !s.Contains(1) || !s.Contains(2) || !s.Contains(3) {
|
||||
t.Fatal("missing items")
|
||||
}
|
||||
if len(s.Get()) != 3 {
|
||||
t.Fatalf("got len %d", len(s.Get()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncSet_Remove(t *testing.T) {
|
||||
s := NewSyncSet[string]()
|
||||
s.Add("a")
|
||||
if !s.Remove("a") {
|
||||
t.Fatal("remove existing failed")
|
||||
}
|
||||
if s.Remove("a") {
|
||||
t.Fatal("remove missing returned true")
|
||||
}
|
||||
if s.Contains("a") {
|
||||
t.Fatal("still contains after remove")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncSet_RemoveAll(t *testing.T) {
|
||||
s := NewSyncSet[int]()
|
||||
s.AddAll([]int{1, 2, 3})
|
||||
s.RemoveAll([]int{1, 2, 99})
|
||||
if s.Contains(1) || s.Contains(2) {
|
||||
t.Fatal("should be removed")
|
||||
}
|
||||
if !s.Contains(3) {
|
||||
t.Fatal("3 should remain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncSet_Get(t *testing.T) {
|
||||
s := NewSyncSet[int]()
|
||||
s.AddAll([]int{3, 1, 2})
|
||||
out := s.Get()
|
||||
sort.Ints(out)
|
||||
if len(out) != 3 || out[0] != 1 || out[2] != 3 {
|
||||
t.Fatalf("out=%v", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncSet_AddIfNotContainsRemoveIfContains(t *testing.T) {
|
||||
s := NewSyncSet[string]()
|
||||
if !s.AddIfNotContains("x") {
|
||||
t.Fatal("first AddIfNotContains failed")
|
||||
}
|
||||
if s.AddIfNotContains("x") {
|
||||
t.Fatal("second AddIfNotContains succeeded")
|
||||
}
|
||||
if !s.RemoveIfContains("x") {
|
||||
t.Fatal("RemoveIfContains failed")
|
||||
}
|
||||
if s.RemoveIfContains("x") {
|
||||
t.Fatal("RemoveIfContains on missing succeeded")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package dataext
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSingle(t *testing.T) {
|
||||
s := NewSingle[int](7)
|
||||
if s.V1 != 7 {
|
||||
t.Fatalf("V1=%d", s.V1)
|
||||
}
|
||||
if s.TupleLength() != 1 {
|
||||
t.Fatalf("len=%d", s.TupleLength())
|
||||
}
|
||||
if !reflect.DeepEqual(s.TupleValues(), []any{7}) {
|
||||
t.Fatalf("values=%v", s.TupleValues())
|
||||
}
|
||||
if NewTuple1[int](7).V1 != 7 {
|
||||
t.Fatal("NewTuple1 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTuple(t *testing.T) {
|
||||
tp := NewTuple[int, string](1, "two")
|
||||
if tp.V1 != 1 || tp.V2 != "two" {
|
||||
t.Fatal("values wrong")
|
||||
}
|
||||
if tp.TupleLength() != 2 {
|
||||
t.Fatal("len wrong")
|
||||
}
|
||||
if !reflect.DeepEqual(tp.TupleValues(), []any{1, "two"}) {
|
||||
t.Fatalf("values=%v", tp.TupleValues())
|
||||
}
|
||||
if NewTuple2[int, string](1, "two") != tp {
|
||||
t.Fatal("NewTuple2 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTriple(t *testing.T) {
|
||||
tr := NewTriple[int, string, bool](1, "x", true)
|
||||
if tr.TupleLength() != 3 {
|
||||
t.Fatal("len wrong")
|
||||
}
|
||||
if !reflect.DeepEqual(tr.TupleValues(), []any{1, "x", true}) {
|
||||
t.Fatalf("values=%v", tr.TupleValues())
|
||||
}
|
||||
if NewTuple3[int, string, bool](1, "x", true) != tr {
|
||||
t.Fatal("NewTuple3 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuadruple(t *testing.T) {
|
||||
q := NewQuadruple[int, int, int, int](1, 2, 3, 4)
|
||||
if q.TupleLength() != 4 {
|
||||
t.Fatal("len wrong")
|
||||
}
|
||||
if !reflect.DeepEqual(q.TupleValues(), []any{1, 2, 3, 4}) {
|
||||
t.Fatalf("values=%v", q.TupleValues())
|
||||
}
|
||||
if NewTuple4[int, int, int, int](1, 2, 3, 4) != q {
|
||||
t.Fatal("NewTuple4 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuintuple(t *testing.T) {
|
||||
q := NewQuintuple[int, int, int, int, int](1, 2, 3, 4, 5)
|
||||
if q.TupleLength() != 5 {
|
||||
t.Fatal("len wrong")
|
||||
}
|
||||
if !reflect.DeepEqual(q.TupleValues(), []any{1, 2, 3, 4, 5}) {
|
||||
t.Fatalf("values=%v", q.TupleValues())
|
||||
}
|
||||
if NewTuple5[int, int, int, int, int](1, 2, 3, 4, 5) != q {
|
||||
t.Fatal("NewTuple5 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSextuple(t *testing.T) {
|
||||
s := NewSextuple[int, int, int, int, int, int](1, 2, 3, 4, 5, 6)
|
||||
if s.TupleLength() != 6 {
|
||||
t.Fatal("len wrong")
|
||||
}
|
||||
if !reflect.DeepEqual(s.TupleValues(), []any{1, 2, 3, 4, 5, 6}) {
|
||||
t.Fatalf("values=%v", s.TupleValues())
|
||||
}
|
||||
if NewTuple6[int, int, int, int, int, int](1, 2, 3, 4, 5, 6) != s {
|
||||
t.Fatal("NewTuple6 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeptuple(t *testing.T) {
|
||||
s := NewSeptuple[int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7)
|
||||
if s.TupleLength() != 7 {
|
||||
t.Fatal("len wrong")
|
||||
}
|
||||
if !reflect.DeepEqual(s.TupleValues(), []any{1, 2, 3, 4, 5, 6, 7}) {
|
||||
t.Fatalf("values=%v", s.TupleValues())
|
||||
}
|
||||
if NewTuple7[int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7) != s {
|
||||
t.Fatal("NewTuple7 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOctuple(t *testing.T) {
|
||||
o := NewOctuple[int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8)
|
||||
if o.TupleLength() != 8 {
|
||||
t.Fatal("len wrong")
|
||||
}
|
||||
if !reflect.DeepEqual(o.TupleValues(), []any{1, 2, 3, 4, 5, 6, 7, 8}) {
|
||||
t.Fatalf("values=%v", o.TupleValues())
|
||||
}
|
||||
if NewTuple8[int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8) != o {
|
||||
t.Fatal("NewTuple8 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonuple(t *testing.T) {
|
||||
n := NewNonuple[int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9)
|
||||
if n.TupleLength() != 9 {
|
||||
t.Fatal("len wrong")
|
||||
}
|
||||
if !reflect.DeepEqual(n.TupleValues(), []any{1, 2, 3, 4, 5, 6, 7, 8, 9}) {
|
||||
t.Fatalf("values=%v", n.TupleValues())
|
||||
}
|
||||
if NewTuple9[int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9) != n {
|
||||
t.Fatal("NewTuple9 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueGroupInterface(t *testing.T) {
|
||||
var vg ValueGroup = NewTuple[int, string](1, "a")
|
||||
if vg.TupleLength() != 2 {
|
||||
t.Fatal("interface length wrong")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user