tests: add locker and math tests
This commit is contained in:
parent
189cb5c968
commit
f436393965
7
Makefile
Normal file
7
Makefile
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export EV_DATA = mem:
|
||||||
|
# export EV_HTTP = :8080
|
||||||
|
|
||||||
|
run:
|
||||||
|
go run .
|
||||||
|
test:
|
||||||
|
go test -cover -race ./...
|
38
main.go
38
main.go
|
@ -17,7 +17,8 @@ import (
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
"github.com/sour-is/ev/pkg/es"
|
||||||
"github.com/sour-is/ev/pkg/es/driver"
|
"github.com/sour-is/ev/pkg/es/driver"
|
||||||
"github.com/sour-is/ev/pkg/es/driver/disk-store"
|
diskstore "github.com/sour-is/ev/pkg/es/driver/disk-store"
|
||||||
|
memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
"github.com/sour-is/ev/pkg/es/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,12 +33,15 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(ctx context.Context) error {
|
func run(ctx context.Context) error {
|
||||||
event.Register(&PostEvent{})
|
if err := event.Register(ctx, &PostEvent{}); err != nil {
|
||||||
diskstore.Init(ctx)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
es, err := es.Open(ctx, "file:data")
|
diskstore.Init(ctx)
|
||||||
|
memstore.Init(ctx)
|
||||||
|
|
||||||
|
es, err := es.Open(ctx, env("EV_DATA", "file:data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -47,7 +51,7 @@ func run(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s := http.Server{
|
s := http.Server{
|
||||||
Addr: ":8080",
|
Addr: env("EV_HTTP", ":8080"),
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/event/", svc.event)
|
http.HandleFunc("/event/", svc.event)
|
||||||
|
@ -67,6 +71,13 @@ func run(ctx context.Context) error {
|
||||||
|
|
||||||
return g.Wait()
|
return g.Wait()
|
||||||
}
|
}
|
||||||
|
func env(name, defaultValue string) string {
|
||||||
|
if v := os.Getenv(name); v != "" {
|
||||||
|
log.Println("# ", name, " = ", v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
es driver.EventStore
|
es driver.EventStore
|
||||||
|
@ -84,7 +95,8 @@ func (s *service) event(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Method == http.MethodGet {
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
if name == "" {
|
if name == "" {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
|
@ -93,14 +105,14 @@ func (s *service) event(w http.ResponseWriter, r *http.Request) {
|
||||||
var pos, count int64 = -1, -99
|
var pos, count int64 = -1, -99
|
||||||
qry := r.URL.Query()
|
qry := r.URL.Query()
|
||||||
|
|
||||||
if i, err := strconv.ParseInt(qry.Get("pos"), 10, 64); err == nil {
|
if i, err := strconv.ParseInt(qry.Get("idx"), 10, 64); err == nil {
|
||||||
pos = i
|
pos = i
|
||||||
}
|
}
|
||||||
if i, err := strconv.ParseInt(qry.Get("n"), 10, 64); err == nil {
|
if i, err := strconv.ParseInt(qry.Get("n"), 10, 64); err == nil {
|
||||||
count = i
|
count = i
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("name=", name, ", pos=", pos, ", n=", count)
|
log.Print("GET topic=", name, " idx=", pos, " n=", count)
|
||||||
events, err := s.es.Read(ctx, "post-"+name, pos, count)
|
events, err := s.es.Read(ctx, "post-"+name, pos, count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
@ -113,8 +125,7 @@ func (s *service) event(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
case http.MethodPost, http.MethodPut:
|
||||||
|
|
||||||
b, err := io.ReadAll(io.LimitReader(r.Body, 64*1024))
|
b, err := io.ReadAll(io.LimitReader(r.Body, 64*1024))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
@ -128,7 +139,6 @@ func (s *service) event(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print(name, tags)
|
|
||||||
events := event.NewEvents(&PostEvent{
|
events := event.NewEvents(&PostEvent{
|
||||||
Payload: b,
|
Payload: b,
|
||||||
Tags: strings.Split(tags, "/"),
|
Tags: strings.Split(tags, "/"),
|
||||||
|
@ -143,7 +153,11 @@ func (s *service) event(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
m := events.First().EventMeta()
|
m := events.First().EventMeta()
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
log.Print("POST topic=", name, " tags=", tags, " idx=", m.Position, " id=", m.EventID)
|
||||||
fmt.Fprintf(w, "OK %d %s", m.Position, m.EventID)
|
fmt.Fprintf(w, "OK %d %s", m.Position, m.EventID)
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostEvent struct {
|
type PostEvent struct {
|
||||||
|
|
|
@ -21,8 +21,9 @@ type diskStore struct {
|
||||||
|
|
||||||
var _ driver.Driver = (*diskStore)(nil)
|
var _ driver.Driver = (*diskStore)(nil)
|
||||||
|
|
||||||
func Init(ctx context.Context) {
|
func Init(ctx context.Context) error {
|
||||||
es.Register(ctx, "file", &diskStore{})
|
es.Register(ctx, "file", &diskStore{})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (diskStore) Open(dsn string) (driver.EventStore, error) {
|
func (diskStore) Open(dsn string) (driver.EventStore, error) {
|
||||||
|
@ -143,7 +144,7 @@ func (es *diskStore) Load(ctx context.Context, agg event.Aggregate) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
events[i], err = event.UnmarshalText(b, first+i)
|
events[i], err = event.UnmarshalText(ctx, b, first+i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -193,7 +194,7 @@ func (es *diskStore) Read(ctx context.Context, streamID string, pos, count int64
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return events, err
|
return events, err
|
||||||
}
|
}
|
||||||
events[i], err = event.UnmarshalText(b, start)
|
events[i], err = event.UnmarshalText(ctx, b, start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return events, err
|
return events, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ type config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Config = locker.New(&config{drivers: make(map[string]driver.Driver)})
|
drivers = locker.New(&config{drivers: make(map[string]driver.Driver)})
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(ctx context.Context, name string, d driver.Driver) {
|
func Register(ctx context.Context, name string, d driver.Driver) error {
|
||||||
Config.Modify(ctx, func(c *config) error {
|
return drivers.Modify(ctx, func(c *config) error {
|
||||||
if _, set := c.drivers[name]; set {
|
if _, set := c.drivers[name]; set {
|
||||||
return fmt.Errorf("driver %s already set", name)
|
return fmt.Errorf("driver %s already set", name)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func Open(ctx context.Context, dsn string) (driver.EventStore, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var d driver.Driver
|
var d driver.Driver
|
||||||
Config.Modify(ctx,func(c *config) error {
|
drivers.Modify(ctx,func(c *config) error {
|
||||||
var ok bool
|
var ok bool
|
||||||
d, ok = c.drivers[name]
|
d, ok = c.drivers[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -6,47 +6,42 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
"github.com/sour-is/ev/pkg/es"
|
||||||
memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
|
memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
"github.com/sour-is/ev/pkg/es/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestES(t *testing.T) {
|
func TestES(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
event.Register(&ValueSet{})
|
err := event.Register(ctx, &ValueSet{})
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
memstore.Init(ctx)
|
memstore.Init(ctx)
|
||||||
|
|
||||||
es, err := es.Open(ctx, "mem:")
|
es, err := es.Open(ctx, "mem:")
|
||||||
if err != nil {
|
is.NoErr(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
thing := &Thing{Name: "time"}
|
thing := &Thing{Name: "time"}
|
||||||
err = es.Load(ctx, thing)
|
err = es.Load(ctx, thing)
|
||||||
if err != nil {
|
is.NoErr(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(thing.StreamVersion(), thing.Name, thing.Value)
|
t.Log(thing.StreamVersion(), thing.Name, thing.Value)
|
||||||
|
|
||||||
err = thing.OnSetValue(time.Now().String())
|
err = thing.OnSetValue(time.Now().String())
|
||||||
if err != nil {
|
is.NoErr(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := es.Save(ctx, thing)
|
i, err := es.Save(ctx, thing)
|
||||||
if err != nil {
|
is.NoErr(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log(thing.StreamVersion(), thing.Name, thing.Value)
|
t.Log(thing.StreamVersion(), thing.Name, thing.Value)
|
||||||
t.Log("Wrote: ", i)
|
t.Log("Wrote: ", i)
|
||||||
|
|
||||||
events, err := es.Read(ctx, "thing-time", -1, -11)
|
events, err := es.Read(ctx, "thing-time", -1, -11)
|
||||||
if err != nil {
|
is.NoErr(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, e := range events {
|
for i, e := range events {
|
||||||
t.Logf("event %d %d - %v\n", i, e.EventMeta().Position, e)
|
t.Logf("event %d %d - %v\n", i, e.EventMeta().Position, e)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package event_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/matryer/is"
|
"github.com/matryer/is"
|
||||||
|
@ -30,8 +31,10 @@ func (e *DummyEvent) SetEventMeta(eventMeta event.Meta) {
|
||||||
|
|
||||||
func TestEventEncode(t *testing.T) {
|
func TestEventEncode(t *testing.T) {
|
||||||
is := is.New(t)
|
is := is.New(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
event.Register(&DummyEvent{})
|
err := event.Register(ctx, &DummyEvent{})
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
var lis event.Events = event.NewEvents(
|
var lis event.Events = event.NewEvents(
|
||||||
&DummyEvent{Value: "testA"},
|
&DummyEvent{Value: "testA"},
|
||||||
|
@ -50,7 +53,7 @@ func TestEventEncode(t *testing.T) {
|
||||||
is.Equal(string(sp[2]), "event_test.DummyEvent")
|
is.Equal(string(sp[2]), "event_test.DummyEvent")
|
||||||
}
|
}
|
||||||
|
|
||||||
chk, err := event.DecodeEvents(blis...)
|
chk, err := event.DecodeEvents(ctx, blis...)
|
||||||
is.NoErr(err)
|
is.NoErr(err)
|
||||||
|
|
||||||
for i := range chk {
|
for i := range chk {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package event
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding"
|
"encoding"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -9,11 +10,16 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
"github.com/sour-is/ev/pkg/locker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
eventTypes map[string]reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
eventTypes sync.Map
|
eventTypes = locker.New(&config{eventTypes: make(map[string]reflect.Type)})
|
||||||
)
|
)
|
||||||
|
|
||||||
type UnknownEvent struct {
|
type UnknownEvent struct {
|
||||||
|
@ -63,35 +69,56 @@ func (u *UnknownEvent) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register a type container for Unmarshalling values into. The type must implement Event and not be a nil value.
|
// Register a type container for Unmarshalling values into. The type must implement Event and not be a nil value.
|
||||||
func Register(lis ...Event) {
|
func Register(ctx context.Context, lis ...Event) error {
|
||||||
for _, e := range lis {
|
for _, e := range lis {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if e == nil {
|
if e == nil {
|
||||||
panic(fmt.Sprintf("can't register event.Event of type=%T with value=%v", e, e))
|
return fmt.Errorf("can't register event.Event of type=%T with value=%v", e, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := reflect.ValueOf(e)
|
value := reflect.ValueOf(e)
|
||||||
|
|
||||||
if value.IsNil() {
|
if value.IsNil() {
|
||||||
panic(fmt.Sprintf("can't register event.Event of type=%T with value=%v", e, e))
|
return fmt.Errorf("can't register event.Event of type=%T with value=%v", e, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
value = reflect.Indirect(value)
|
value = reflect.Indirect(value)
|
||||||
|
|
||||||
|
name := TypeOf(e)
|
||||||
typ := value.Type()
|
typ := value.Type()
|
||||||
|
|
||||||
eventTypes.LoadOrStore(TypeOf(e), typ)
|
if err := eventTypes.Modify(ctx, func(c *config) error {
|
||||||
|
c.eventTypes[name] = typ
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
func GetContainer(s string) Event {
|
func GetContainer(ctx context.Context, s string) Event {
|
||||||
if typ, ok := eventTypes.Load(s); ok {
|
var e Event
|
||||||
if typ, ok := typ.(reflect.Type); ok {
|
|
||||||
|
eventTypes.Modify(ctx, func(c *config) error {
|
||||||
|
typ, ok := c.eventTypes[s]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("not defined")
|
||||||
|
}
|
||||||
newType := reflect.New(typ)
|
newType := reflect.New(typ)
|
||||||
newInterface := newType.Interface()
|
newInterface := newType.Interface()
|
||||||
if typ, ok := newInterface.(Event); ok {
|
if iface, ok := newInterface.(Event); ok {
|
||||||
return typ
|
e = iface
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("failed")
|
||||||
|
})
|
||||||
|
if e == nil {
|
||||||
|
e = &UnknownEvent{eventType: s}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return &UnknownEvent{eventType: s}
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarshalText(e Event) (txt []byte, err error) {
|
func MarshalText(e Event) (txt []byte, err error) {
|
||||||
|
@ -123,7 +150,7 @@ func MarshalText(e Event) (txt []byte, err error) {
|
||||||
return b.Bytes(), err
|
return b.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalText(txt []byte, pos uint64) (e Event, err error) {
|
func UnmarshalText(ctx context.Context, txt []byte, pos uint64) (e Event, err error) {
|
||||||
sp := bytes.SplitN(txt, []byte{'\t'}, 4)
|
sp := bytes.SplitN(txt, []byte{'\t'}, 4)
|
||||||
if len(sp) != 4 {
|
if len(sp) != 4 {
|
||||||
return nil, fmt.Errorf("invalid format. expected=4, got=%d", len(sp))
|
return nil, fmt.Errorf("invalid format. expected=4, got=%d", len(sp))
|
||||||
|
@ -138,7 +165,7 @@ func UnmarshalText(txt []byte, pos uint64) (e Event, err error) {
|
||||||
m.Position = pos
|
m.Position = pos
|
||||||
|
|
||||||
eventType := string(sp[2])
|
eventType := string(sp[2])
|
||||||
e = GetContainer(eventType)
|
e = GetContainer(ctx, eventType)
|
||||||
|
|
||||||
if enc, ok := e.(encoding.TextUnmarshaler); ok {
|
if enc, ok := e.(encoding.TextUnmarshaler); ok {
|
||||||
if err = enc.UnmarshalText(sp[3]); err != nil {
|
if err = enc.UnmarshalText(sp[3]); err != nil {
|
||||||
|
@ -163,12 +190,12 @@ func writeMarshaler(out io.Writer, in encoding.TextMarshaler) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeEvents unmarshals the byte list into Events.
|
// DecodeEvents unmarshals the byte list into Events.
|
||||||
func DecodeEvents(lis ...[]byte) (Events, error) {
|
func DecodeEvents(ctx context.Context, lis ...[]byte) (Events, error) {
|
||||||
elis := make([]Event, len(lis))
|
elis := make([]Event, len(lis))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for i, txt := range lis {
|
for i, txt := range lis {
|
||||||
elis[i], err = UnmarshalText(txt, uint64(i))
|
elis[i], err = UnmarshalText(ctx, txt, uint64(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,3 +26,16 @@ func (s *Locked[T]) Modify(ctx context.Context, fn func(*T) error) error {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Locked[T]) Copy(ctx context.Context) (T, error) {
|
||||||
|
var t T
|
||||||
|
|
||||||
|
err := s.Modify(ctx, func(c *T) error {
|
||||||
|
if c != nil {
|
||||||
|
t = *c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
|
62
pkg/locker/locker_test.go
Normal file
62
pkg/locker/locker_test.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package locker_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
|
||||||
|
"github.com/sour-is/ev/pkg/locker"
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Value string
|
||||||
|
Counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocker(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
value := locker.New(&config{})
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := value.Modify(ctx, func(c *config) error {
|
||||||
|
c.Value = "one"
|
||||||
|
c.Counter++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
c, err := value.Copy(context.Background())
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(c.Value, "one")
|
||||||
|
is.Equal(c.Counter, 1)
|
||||||
|
|
||||||
|
wait := make(chan struct{})
|
||||||
|
|
||||||
|
go value.Modify(ctx, func(c *config) error {
|
||||||
|
c.Value = "two"
|
||||||
|
c.Counter++
|
||||||
|
close(wait)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
<-wait
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
err = value.Modify(ctx, func(c *config) error {
|
||||||
|
c.Value = "three"
|
||||||
|
c.Counter++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
is.True(err != nil)
|
||||||
|
|
||||||
|
c, err = value.Copy(context.Background())
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(c.Value, "two")
|
||||||
|
is.Equal(c.Counter, 2)
|
||||||
|
}
|
|
@ -22,15 +22,19 @@ func Abs[T signed](i T) T {
|
||||||
}
|
}
|
||||||
return -i
|
return -i
|
||||||
}
|
}
|
||||||
func Max[T ordered](i, j T) T {
|
func Max[T ordered](i T, candidates ...T) T {
|
||||||
if i > j {
|
for _, j := range candidates {
|
||||||
return i
|
|
||||||
}
|
|
||||||
return j
|
|
||||||
}
|
|
||||||
func Min[T ordered](i, j T) T {
|
|
||||||
if i < j {
|
if i < j {
|
||||||
return i
|
i = j
|
||||||
}
|
}
|
||||||
return j
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
func Min[T ordered](i T, candidates ...T) T {
|
||||||
|
for _, j := range candidates {
|
||||||
|
if i > j {
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
}
|
}
|
||||||
|
|
48
pkg/math/math_test.go
Normal file
48
pkg/math/math_test.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package math_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"github.com/sour-is/ev/pkg/math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMath(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
is.Equal(5, math.Abs(-5))
|
||||||
|
is.Equal(math.Abs(5), math.Abs(-5))
|
||||||
|
|
||||||
|
is.Equal(10, math.Max(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||||
|
is.Equal(1, math.Min(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||||
|
|
||||||
|
is.Equal(1, math.Min(89, 71, 54, 48, 49, 1, 72, 88, 25, 69))
|
||||||
|
is.Equal(89, math.Max(89, 71, 54, 48, 49, 1, 72, 88, 25, 69))
|
||||||
|
|
||||||
|
is.Equal(0.9348207729, math.Max(
|
||||||
|
0.3943310720,
|
||||||
|
0.1090868377,
|
||||||
|
0.9348207729,
|
||||||
|
0.3525527584,
|
||||||
|
0.4359833682,
|
||||||
|
0.7958538081,
|
||||||
|
0.1439352569,
|
||||||
|
0.1547311967,
|
||||||
|
0.6403818871,
|
||||||
|
0.8618832818,
|
||||||
|
))
|
||||||
|
|
||||||
|
is.Equal(0.1090868377, math.Min(
|
||||||
|
0.3943310720,
|
||||||
|
0.1090868377,
|
||||||
|
0.9348207729,
|
||||||
|
0.3525527584,
|
||||||
|
0.4359833682,
|
||||||
|
0.7958538081,
|
||||||
|
0.1439352569,
|
||||||
|
0.1547311967,
|
||||||
|
0.6403818871,
|
||||||
|
0.8618832818,
|
||||||
|
))
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user