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 ./...
 | 
			
		||||
							
								
								
									
										92
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								main.go
									
									
									
									
									
								
							@ -17,7 +17,8 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/sour-is/ev/pkg/es"
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -32,12 +33,15 @@ func main() {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func run(ctx context.Context) error {
 | 
			
		||||
	event.Register(&PostEvent{})
 | 
			
		||||
	diskstore.Init(ctx)
 | 
			
		||||
	if err := event.Register(ctx, &PostEvent{}); err != nil {
 | 
			
		||||
		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 {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@ -47,7 +51,7 @@ func run(ctx context.Context) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s := http.Server{
 | 
			
		||||
		Addr: ":8080",
 | 
			
		||||
		Addr: env("EV_HTTP", ":8080"),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/event/", svc.event)
 | 
			
		||||
@ -67,6 +71,13 @@ func run(ctx context.Context) error {
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
	es driver.EventStore
 | 
			
		||||
@ -84,7 +95,8 @@ func (s *service) event(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r.Method == http.MethodGet {
 | 
			
		||||
	switch r.Method {
 | 
			
		||||
	case http.MethodGet:
 | 
			
		||||
		if name == "" {
 | 
			
		||||
			w.WriteHeader(http.StatusNotFound)
 | 
			
		||||
			return
 | 
			
		||||
@ -93,14 +105,14 @@ func (s *service) event(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var pos, count int64 = -1, -99
 | 
			
		||||
		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
 | 
			
		||||
		}
 | 
			
		||||
		if i, err := strconv.ParseInt(qry.Get("n"), 10, 64); err == nil {
 | 
			
		||||
			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)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Print(err)
 | 
			
		||||
@ -113,37 +125,39 @@ func (s *service) event(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return
 | 
			
		||||
	case http.MethodPost, http.MethodPut:
 | 
			
		||||
		b, err := io.ReadAll(io.LimitReader(r.Body, 64*1024))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Print(err)
 | 
			
		||||
			w.WriteHeader(http.StatusBadRequest)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		r.Body.Close()
 | 
			
		||||
 | 
			
		||||
		if name == "" {
 | 
			
		||||
			w.WriteHeader(http.StatusNotFound)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		events := event.NewEvents(&PostEvent{
 | 
			
		||||
			Payload: b,
 | 
			
		||||
			Tags:    strings.Split(tags, "/"),
 | 
			
		||||
		})
 | 
			
		||||
		_, err = s.es.Append(r.Context(), "post-"+name, events)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Print(err)
 | 
			
		||||
 | 
			
		||||
			w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		m := events.First().EventMeta()
 | 
			
		||||
		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)
 | 
			
		||||
	default:
 | 
			
		||||
		w.WriteHeader(http.StatusMethodNotAllowed)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b, err := io.ReadAll(io.LimitReader(r.Body, 64*1024))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Print(err)
 | 
			
		||||
		w.WriteHeader(http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	r.Body.Close()
 | 
			
		||||
 | 
			
		||||
	if name == "" {
 | 
			
		||||
		w.WriteHeader(http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Print(name, tags)
 | 
			
		||||
	events := event.NewEvents(&PostEvent{
 | 
			
		||||
		Payload: b,
 | 
			
		||||
		Tags:    strings.Split(tags, "/"),
 | 
			
		||||
	})
 | 
			
		||||
	_, err = s.es.Append(r.Context(), "post-"+name, events)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Print(err)
 | 
			
		||||
 | 
			
		||||
		w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := events.First().EventMeta()
 | 
			
		||||
	w.WriteHeader(http.StatusAccepted)
 | 
			
		||||
	fmt.Fprintf(w, "OK %d %s", m.Position, m.EventID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PostEvent struct {
 | 
			
		||||
 | 
			
		||||
@ -21,8 +21,9 @@ type diskStore struct {
 | 
			
		||||
 | 
			
		||||
var _ driver.Driver = (*diskStore)(nil)
 | 
			
		||||
 | 
			
		||||
func Init(ctx context.Context) {
 | 
			
		||||
func Init(ctx context.Context) error {
 | 
			
		||||
	es.Register(ctx, "file", &diskStore{})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		events[i], err = event.UnmarshalText(b, first+i)
 | 
			
		||||
		events[i], err = event.UnmarshalText(ctx, b, first+i)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@ -193,7 +194,7 @@ func (es *diskStore) Read(ctx context.Context, streamID string, pos, count int64
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return events, err
 | 
			
		||||
		}
 | 
			
		||||
		events[i], err = event.UnmarshalText(b, start)
 | 
			
		||||
		events[i], err = event.UnmarshalText(ctx, b, start)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return events, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -15,11 +15,11 @@ type config struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	Config.Modify(ctx, func(c *config) error {
 | 
			
		||||
func Register(ctx context.Context, name string, d driver.Driver) error {
 | 
			
		||||
	return drivers.Modify(ctx, func(c *config) error {
 | 
			
		||||
		if _, set := c.drivers[name]; set {
 | 
			
		||||
			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
 | 
			
		||||
	Config.Modify(ctx,func(c *config) error {
 | 
			
		||||
	drivers.Modify(ctx,func(c *config) error {
 | 
			
		||||
		var ok bool
 | 
			
		||||
		d, ok = c.drivers[name]
 | 
			
		||||
		if !ok {
 | 
			
		||||
 | 
			
		||||
@ -6,47 +6,42 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/matryer/is"
 | 
			
		||||
 | 
			
		||||
	"github.com/sour-is/ev/pkg/es"
 | 
			
		||||
	memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
 | 
			
		||||
	"github.com/sour-is/ev/pkg/es/event"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestES(t *testing.T) {
 | 
			
		||||
	is := is.New(t)
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	event.Register(&ValueSet{})
 | 
			
		||||
	err := event.Register(ctx, &ValueSet{})
 | 
			
		||||
	is.NoErr(err)
 | 
			
		||||
 | 
			
		||||
	memstore.Init(ctx)
 | 
			
		||||
 | 
			
		||||
	es, err := es.Open(ctx, "mem:")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	is.NoErr(err)
 | 
			
		||||
 | 
			
		||||
	thing := &Thing{Name: "time"}
 | 
			
		||||
	err = es.Load(ctx, thing)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	is.NoErr(err)
 | 
			
		||||
 | 
			
		||||
	t.Log(thing.StreamVersion(), thing.Name, thing.Value)
 | 
			
		||||
 | 
			
		||||
	err = thing.OnSetValue(time.Now().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	is.NoErr(err)
 | 
			
		||||
 | 
			
		||||
	i, err := es.Save(ctx, thing)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	is.NoErr(err)
 | 
			
		||||
 | 
			
		||||
	t.Log(thing.StreamVersion(), thing.Name, thing.Value)
 | 
			
		||||
	t.Log("Wrote: ", i)
 | 
			
		||||
 | 
			
		||||
	events, err := es.Read(ctx, "thing-time", -1, -11)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	is.NoErr(err)
 | 
			
		||||
 | 
			
		||||
	for i, e := range events {
 | 
			
		||||
		t.Logf("event %d %d - %v\n", i, e.EventMeta().Position, e)
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package event_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/matryer/is"
 | 
			
		||||
@ -30,8 +31,10 @@ func (e *DummyEvent) SetEventMeta(eventMeta event.Meta) {
 | 
			
		||||
 | 
			
		||||
func TestEventEncode(t *testing.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(
 | 
			
		||||
		&DummyEvent{Value: "testA"},
 | 
			
		||||
@ -50,7 +53,7 @@ func TestEventEncode(t *testing.T) {
 | 
			
		||||
		is.Equal(string(sp[2]), "event_test.DummyEvent")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chk, err := event.DecodeEvents(blis...)
 | 
			
		||||
	chk, err := event.DecodeEvents(ctx, blis...)
 | 
			
		||||
	is.NoErr(err)
 | 
			
		||||
 | 
			
		||||
	for i := range chk {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package event
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
@ -9,11 +10,16 @@ import (
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/sour-is/ev/pkg/locker"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type config struct {
 | 
			
		||||
	eventTypes map[string]reflect.Type
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	eventTypes sync.Map
 | 
			
		||||
	eventTypes = locker.New(&config{eventTypes: make(map[string]reflect.Type)})
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
func Register(lis ...Event) {
 | 
			
		||||
func Register(ctx context.Context, lis ...Event) error {
 | 
			
		||||
	for _, e := range lis {
 | 
			
		||||
		if err := ctx.Err(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		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)
 | 
			
		||||
 | 
			
		||||
		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)
 | 
			
		||||
 | 
			
		||||
		name := TypeOf(e)
 | 
			
		||||
		typ := value.Type()
 | 
			
		||||
 | 
			
		||||
		eventTypes.LoadOrStore(TypeOf(e), typ)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func GetContainer(s string) Event {
 | 
			
		||||
	if typ, ok := eventTypes.Load(s); ok {
 | 
			
		||||
		if typ, ok := typ.(reflect.Type); ok {
 | 
			
		||||
			newType := reflect.New(typ)
 | 
			
		||||
			newInterface := newType.Interface()
 | 
			
		||||
			if typ, ok := newInterface.(Event); ok {
 | 
			
		||||
				return typ
 | 
			
		||||
			}
 | 
			
		||||
		if err := eventTypes.Modify(ctx, func(c *config) error {
 | 
			
		||||
			c.eventTypes[name] = typ
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &UnknownEvent{eventType: s}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func GetContainer(ctx context.Context, s string) Event {
 | 
			
		||||
	var e Event
 | 
			
		||||
 | 
			
		||||
	eventTypes.Modify(ctx, func(c *config) error {
 | 
			
		||||
			typ, ok := c.eventTypes[s]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return fmt.Errorf("not defined")
 | 
			
		||||
			}
 | 
			
		||||
			newType := reflect.New(typ)
 | 
			
		||||
			newInterface := newType.Interface()
 | 
			
		||||
			if iface, ok := newInterface.(Event); ok {
 | 
			
		||||
				e = iface
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("failed")
 | 
			
		||||
	})
 | 
			
		||||
	if e == nil {
 | 
			
		||||
		e = &UnknownEvent{eventType: s}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func MarshalText(e Event) (txt []byte, err error) {
 | 
			
		||||
@ -123,7 +150,7 @@ func MarshalText(e Event) (txt []byte, err error) {
 | 
			
		||||
	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)
 | 
			
		||||
	if len(sp) != 4 {
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
	eventType := string(sp[2])
 | 
			
		||||
	e = GetContainer(eventType)
 | 
			
		||||
	e = GetContainer(ctx, eventType)
 | 
			
		||||
 | 
			
		||||
	if enc, ok := e.(encoding.TextUnmarshaler); ok {
 | 
			
		||||
		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.
 | 
			
		||||
func DecodeEvents(lis ...[]byte) (Events, error) {
 | 
			
		||||
func DecodeEvents(ctx context.Context, lis ...[]byte) (Events, error) {
 | 
			
		||||
	elis := make([]Event, len(lis))
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	for i, txt := range lis {
 | 
			
		||||
		elis[i], err = UnmarshalText(txt, uint64(i))
 | 
			
		||||
		elis[i], err = UnmarshalText(ctx, txt, uint64(i))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -25,4 +25,17 @@ func (s *Locked[T]) Modify(ctx context.Context, fn func(*T) error) error {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
func Max[T ordered](i, j T) T {
 | 
			
		||||
	if i > j {
 | 
			
		||||
		return i
 | 
			
		||||
func Max[T ordered](i T, candidates ...T) T {
 | 
			
		||||
	for _, j := range candidates {
 | 
			
		||||
		if i < j {
 | 
			
		||||
			i = j
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return j
 | 
			
		||||
	return i
 | 
			
		||||
}
 | 
			
		||||
func Min[T ordered](i, j T) T {
 | 
			
		||||
	if i < j {
 | 
			
		||||
		return i
 | 
			
		||||
func Min[T ordered](i T, candidates ...T) T {
 | 
			
		||||
	for _, j := range candidates {
 | 
			
		||||
		if i > j {
 | 
			
		||||
			i = j
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user