From ff6a0dba870aa4cf6d648894e261366129247528 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 29 Jun 2023 15:18:39 +0800 Subject: [PATCH] :white_check_mark: test: structs,cli - add more unit test cases --- basefn/extfunc_test.go | 1 + cliutil/cliutil_test.go | 2 ++ cliutil/read.go | 42 ++++++++++++++++------------------ cliutil/read_test.go | 3 +++ comdef/comdef.go | 50 +++++++++++++++++++++++++++++++++-------- comdef/types.go | 17 ++++++++++---- errorx/assert_test.go | 26 +++++++++++++++++++++ group_test.go | 21 ++++++++++++----- structs/data.go | 29 +++++++++++++----------- structs/data_test.go | 18 +++++++++++++++ structs/tags.go | 1 - structs/tags_test.go | 15 +++++++++++++ structs/wrapper.go | 40 +++++++++++++++++++++++++++------ structs/wrapper_test.go | 35 +++++++++++++++++++++++++++++ 14 files changed, 237 insertions(+), 63 deletions(-) create mode 100644 errorx/assert_test.go create mode 100644 structs/wrapper_test.go diff --git a/basefn/extfunc_test.go b/basefn/extfunc_test.go index ebbbfd2a6..b100899b5 100644 --- a/basefn/extfunc_test.go +++ b/basefn/extfunc_test.go @@ -31,6 +31,7 @@ func TestHowLongAgo(t *testing.T) { }{ {-36, "unknown"}, {36, "36 secs"}, + {60, "1 min"}, {346, "5 mins"}, {3467, "57 mins"}, {346778, "4 days"}, diff --git a/cliutil/cliutil_test.go b/cliutil/cliutil_test.go index 3605cf0ba..ec7bda55c 100644 --- a/cliutil/cliutil_test.go +++ b/cliutil/cliutil_test.go @@ -1,6 +1,7 @@ package cliutil_test import ( + "fmt" "strings" "testing" @@ -89,6 +90,7 @@ func TestWorkdir(t *testing.T) { assert.NotEmpty(t, cliutil.BinDir()) assert.NotEmpty(t, cliutil.BinFile()) assert.NotEmpty(t, cliutil.BinName()) + fmt.Println(cliutil.GetTermSize()) } func TestColorPrint(t *testing.T) { diff --git a/cliutil/read.go b/cliutil/read.go index c7f3356d6..ac65fe884 100644 --- a/cliutil/read.go +++ b/cliutil/read.go @@ -90,38 +90,17 @@ func ReadFirstRune(question string) (rune, error) { // ok := ReadAsBool("are you OK? [y/N]", false) func ReadAsBool(tip string, defVal bool) bool { fChar, err := ReadFirstByte(tip) - if err != nil { - panic(err) - } - - if fChar != 0 { + if err == nil && fChar != 0 { return ByteIsYes(fChar) } return defVal } -// ReadPassword from console terminal -func ReadPassword(question ...string) string { - if len(question) > 0 { - print(question[0]) - } else { - print("Enter Password: ") - } - - bs, err := term.ReadPassword(syscallStdinFd()) - if err != nil { - return "" - } - - println() // new line - return string(bs) -} - // Confirm with user input func Confirm(tip string, defVal ...bool) bool { + var defV bool mark := " [y/N]: " - var defV bool if len(defVal) > 0 && defVal[0] { defV = true mark = " [Y/n]: " @@ -139,3 +118,20 @@ func InputIsYes(ans string) bool { func ByteIsYes(ans byte) bool { return ans == 'y' || ans == 'Y' } + +// ReadPassword from console terminal +func ReadPassword(question ...string) string { + if len(question) > 0 { + print(question[0]) + } else { + print("Enter Password: ") + } + + bs, err := term.ReadPassword(syscallStdinFd()) + if err != nil { + return "" + } + + println() // new line + return string(bs) +} diff --git a/cliutil/read_test.go b/cliutil/read_test.go index c234b5f35..0a4709828 100644 --- a/cliutil/read_test.go +++ b/cliutil/read_test.go @@ -66,6 +66,9 @@ func TestRead_cases(t *testing.T) { ans := cliutil.Confirm("continue?", false) fmt.Println(ans) assert.True(t, ans) + ans = cliutil.Confirm("continue?", true) + fmt.Println(ans) + assert.True(t, ans) }) } diff --git a/comdef/comdef.go b/comdef/comdef.go index b544a0027..56e663062 100644 --- a/comdef/comdef.go +++ b/comdef/comdef.go @@ -20,6 +20,38 @@ type StringWriteStringer interface { fmt.Stringer } +type ( + // MarshalFunc define + MarshalFunc func(v any) ([]byte, error) + + // UnmarshalFunc define + UnmarshalFunc func(bts []byte, ptr any) error +) + +// Int64able interface +type Int64able interface { + Int64() (int64, error) +} + +// +// +// Matcher type +// +// + +// Matcher interface +type Matcher[T any] interface { + Match(s T) bool +} + +// MatchFunc definition. implements Matcher interface +type MatchFunc[T any] func(v T) bool + +// Match satisfies the Matcher interface +func (fn MatchFunc[T]) Match(v T) bool { + return fn(v) +} + // StringMatcher interface type StringMatcher interface { Match(s string) bool @@ -33,15 +65,15 @@ func (fn StringMatchFunc) Match(s string) bool { return fn(s) } -type ( - // MarshalFunc define - MarshalFunc func(v any) ([]byte, error) +// StringHandler interface +type StringHandler interface { + Handle(s string) string +} - // UnmarshalFunc define - UnmarshalFunc func(bts []byte, ptr any) error -) +// StringHandleFunc definition +type StringHandleFunc func(s string) string -// Int64able interface -type Int64able interface { - Int64() (int64, error) +// Handle satisfies the StringHandler interface +func (fn StringHandleFunc) Handle(s string) string { + return fn(s) } diff --git a/comdef/types.go b/comdef/types.go index 2fe167f2d..716cbab19 100644 --- a/comdef/types.go +++ b/comdef/types.go @@ -10,8 +10,11 @@ type Uint interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } -// Xint interface type. all int or uint types -type Xint interface { +// Xint interface type. alias of Integer +type Xint = Integer + +// Integer interface type. all int or uint types +type Integer interface { Int | Uint } @@ -30,16 +33,22 @@ type XintOrFloat interface { Int | Uint | Float } -// SortedType interface type. -// that supports the operators < <= >= >. +// SortedType interface type. same of constraints.Ordered +// +// it can be ordered, that supports the operators < <= >= >. // // contains: (x)int, float, ~string types type SortedType interface { Int | Uint | Float | ~string } +// Compared type. alias of constraints.ScalarType +type Compared = ScalarType + // ScalarType interface type. // +// it can be ordered, that supports the operators < <= >= >. +// // contains: (x)int, float, ~string, ~bool types type ScalarType interface { Int | Uint | Float | ~string | ~bool diff --git a/errorx/assert_test.go b/errorx/assert_test.go new file mode 100644 index 000000000..1bd423e1f --- /dev/null +++ b/errorx/assert_test.go @@ -0,0 +1,26 @@ +package errorx_test + +import ( + "testing" + + "github.com/gookit/goutil/errorx" + "github.com/gookit/goutil/testutil/assert" +) + +func TestAssert_methods(t *testing.T) { + // IsFalse + assert.NoErr(t, errorx.IsFalse(false)) + assert.Err(t, errorx.IsFalse(true)) + + // IsTrue + assert.NoErr(t, errorx.IsTrue(true)) + assert.Err(t, errorx.IsTrue(false)) + + // IsIn + assert.NoErr(t, errorx.IsIn(1, []int{1, 2, 3})) + assert.Err(t, errorx.IsIn(4, []int{1, 2, 3})) + + // NotIn + assert.NoErr(t, errorx.NotIn(4, []int{1, 2, 3})) + assert.Err(t, errorx.NotIn(1, []int{1, 2, 3})) +} diff --git a/group_test.go b/group_test.go index b0e5a8c2c..eb777171c 100644 --- a/group_test.go +++ b/group_test.go @@ -6,6 +6,7 @@ import ( "github.com/gookit/goutil" "github.com/gookit/goutil/netutil/httpreq" + "github.com/gookit/goutil/structs" "github.com/gookit/goutil/testutil" "github.com/gookit/goutil/testutil/assert" ) @@ -15,7 +16,7 @@ func TestNewErrGroup(t *testing.T) { eg := goutil.NewErrGroup() eg.Add(func() error { - resp, err := httpreq.Get(testSrvAddr+"/get", nil) + resp, err := httpreq.Get(testSrvAddr + "/get") if err != nil { return err } @@ -23,11 +24,7 @@ func TestNewErrGroup(t *testing.T) { fmt.Println(testutil.ParseBodyToReply(resp.Body)) return nil }, func() error { - resp, err := httpreq.Post(testSrvAddr+"/post", "hi") - if err != nil { - return err - } - + resp := httpreq.MustResp(httpreq.Post(testSrvAddr+"/post", "hi")) fmt.Println(testutil.ParseBodyToReply(resp.Body)) return nil }) @@ -35,3 +32,15 @@ func TestNewErrGroup(t *testing.T) { err := eg.Wait() assert.NoErr(t, err) } + +func TestQuickRun_methods(t *testing.T) { + qr := goutil.NewQuickRun() + qr.Add(func(ctx *structs.Data) error { + resp := httpreq.MustResp(httpreq.Get(testSrvAddr + "/get")) + rr := testutil.ParseBodyToReply(resp.Body) + assert.Eq(t, "GET", rr.Method) + return nil + }) + + assert.NoErr(t, qr.Run()) +} diff --git a/structs/data.go b/structs/data.go index 9dbe09845..6f574e9d2 100644 --- a/structs/data.go +++ b/structs/data.go @@ -12,15 +12,12 @@ import ( // LiteData simple map[string]any struct. no lock type LiteData = Data -// NewLiteData create, not lock +// NewLiteData create, not locked func NewLiteData(data map[string]any) *Data { if data == nil { data = make(map[string]any) } - - return &LiteData{ - data: data, - } + return &LiteData{data: data} } /************************************************************* @@ -82,7 +79,7 @@ func (d *Data) ResetData() { // Merge load new data func (d *Data) Merge(mp map[string]any) { - d.data = maputil.SimpleMerge(d.data, mp) + d.data = maputil.SimpleMerge(mp, d.data) } // Set value to data @@ -151,21 +148,27 @@ func (d *Data) String() string { return maputil.ToString(d.data) } -// OrderedMap data TODO -type OrderedMap struct { +// OrderedData data TODO +type OrderedData struct { maputil.Data - len int + cap int keys []string // vals []any } -// NewOrderedMap instance. -func NewOrderedMap(len int) *OrderedMap { - return &OrderedMap{len: len} +// NewOrderedData instance. +func NewOrderedData(cap int) *OrderedData { + return &OrderedData{cap: cap, Data: make(maputil.Data, cap)} +} + +// Load data +func (om *OrderedData) Load(data map[string]any) { + om.Data.Load(data) + om.keys = om.Data.Keys() } // Set key and value to map -func (om *OrderedMap) Set(key string, val any) { +func (om *OrderedData) Set(key string, val any) { om.keys = append(om.keys, key) om.Data.Set(key, val) } diff --git a/structs/data_test.go b/structs/data_test.go index 540ff7565..cd025b01d 100644 --- a/structs/data_test.go +++ b/structs/data_test.go @@ -20,6 +20,13 @@ func TestLiteData_Data(t *testing.T) { assert.True(t, ok) assert.Eq(t, 234, v) + d.Merge(map[string]any{ + "key1": "def", + "key4": "value4", + }) + assert.Eq(t, "def", d.StrVal("key1")) + assert.Eq(t, "value4", d.GetVal("key4")) + d.ResetData() assert.Empty(t, d.Data()) } @@ -69,3 +76,14 @@ func TestDataStore_EnableLock(t *testing.T) { assert.Eq(t, "def", md.Get("key1")) assert.NotEmpty(t, md.String()) } + +func TestNewOrderedData(t *testing.T) { + od := structs.NewOrderedData(10) + od.Set("key0", 234) + od.Load(map[string]any{ + "key1": "abc", + "key2": true, + }) + + assert.NotEmpty(t, od.Keys()) +} diff --git a/structs/tags.go b/structs/tags.go index 01ab9ff4d..98eabac7b 100644 --- a/structs/tags.go +++ b/structs/tags.go @@ -207,7 +207,6 @@ func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error) { // ParseTagValueQuick quick parse tag value string by sep(;) func ParseTagValueQuick(tagVal string, defines []string) maputil.SMap { parseFn := ParseTagValueDefine(";", defines) - mp, _ := parseFn("", tagVal) return mp } diff --git a/structs/tags_test.go b/structs/tags_test.go index 30880d45c..0010e8264 100644 --- a/structs/tags_test.go +++ b/structs/tags_test.go @@ -219,3 +219,18 @@ func TestParseTagValueNamed(t *testing.T) { _, err = structs.ParseTagValueNamed("name", "name=n;default=inhere", "name") assert.ErrSubMsg(t, err, "parse tag error on field 'name': invalid") } + +func TestParseTagValueQuick(t *testing.T) { + fields := []string{"name", "default"} + mp := structs.ParseTagValueQuick("", fields) + assert.Empty(t, mp) + + mp = structs.ParseTagValueQuick("inhere", fields) + assert.NotEmpty(t, mp) + assert.Eq(t, "inhere", mp.Str("name")) + + mp = structs.ParseTagValueQuick(";tom", fields) + assert.NotEmpty(t, mp) + assert.Eq(t, "", mp.Str("name")) + assert.Eq(t, "tom", mp.Str("default")) +} diff --git a/structs/wrapper.go b/structs/wrapper.go index 59bfe3b6d..1e7b073de 100644 --- a/structs/wrapper.go +++ b/structs/wrapper.go @@ -1,14 +1,23 @@ package structs -import "reflect" +import ( + "errors" + "reflect" +) -// Wrapper struct for read or set field value TODO +// Wrapper struct for read or set field value type Wrapper struct { - // src any // source data struct + // src any // source struct + + // reflect.Value of source struct rv reflect.Value // FieldTagName field name for read/write value. default tag: json FieldTagName string + + // caches for field rv and name and tag name TODO + fieldNames []string + fvCacheMap map[string]reflect.Value } // Wrap create a struct wrapper @@ -27,11 +36,10 @@ func WrapValue(rv reflect.Value) *Wrapper { if rv.Kind() != reflect.Struct { panic("must be provider an struct value") } - return &Wrapper{rv: rv} } -// Get field value by name +// Get field value by name, name allow use dot syntax. func (r *Wrapper) Get(name string) any { val, ok := r.Lookup(name) if !ok { @@ -40,12 +48,30 @@ func (r *Wrapper) Get(name string) any { return val } -// Lookup field value by name +// Lookup field value by name, name allow use dot syntax. func (r *Wrapper) Lookup(name string) (val any, ok bool) { fv := r.rv.FieldByName(name) if !fv.IsValid() { return } - return fv.Interface(), true + if fv.CanInterface() { + return fv.Interface(), true + } + return +} + +// Set field value by name, name allow use dot syntax. +func (r *Wrapper) Set(name string, val any) error { + fv := r.rv.FieldByName(name) + if !fv.IsValid() { + return errors.New("field not found") + } + + if !fv.CanSet() { + return errors.New("field can not set value") + } + + fv.Set(reflect.ValueOf(val)) + return nil } diff --git a/structs/wrapper_test.go b/structs/wrapper_test.go new file mode 100644 index 000000000..070ea8faf --- /dev/null +++ b/structs/wrapper_test.go @@ -0,0 +1,35 @@ +package structs_test + +import ( + "testing" + + "github.com/gookit/goutil/structs" + "github.com/gookit/goutil/testutil/assert" +) + +func TestNewWrapper(t *testing.T) { + type User struct { + Name string + Age int + City string + } + + u := &User{Age: 23, Name: "inhere"} + w := structs.Wrap(u) + + assert.Equal(t, "inhere", w.Get("Name")) + assert.Equal(t, 23, w.Get("Age")) + + assert.NoErr(t, w.Set("Age", 129)) + assert.Equal(t, 129, w.Get("Age")) + assert.Equal(t, 129, u.Age) + + assert.NoErr(t, w.Set("City", "CD")) + assert.Equal(t, "CD", w.Get("City")) + assert.Equal(t, "CD", u.City) + + assert.Nil(t, w.Get("NotExists")) + assert.Panics(t, func() { + structs.NewWrapper(nil) + }) +}