diff --git a/RELEASE.MD b/RELEASE.MD index 93ce84596..31a031b1a 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -1,3 +1,63 @@ +# `v1.7.0` +## 新功能/改进 +1. 重构改进`glog`模块: + - 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效 + - 增加日志内容的异步输出特性:https://goframe.org/os/glog/async + - 增加日志输出内容的`Json`格式支持:https://goframe.org/os/glog/json + - 增加`Flags`额外特性支持,包括文件行号打印、自定义时间格式、异步输出等特性控制:https://goframe.org/os/glog/flags + - 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成:https://goframe.org/os/glog/writer + - 修改`SetStdPrint`方法名为`SetStdoutPrint` + - 修改链式方法`StdPrint`方法名为`Stdout` + - 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出 + - 新增更多的链式方法支持:https://goframe.org/os/glog/chain +1. 重构改进`gmap`模块: + - 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap` + - 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap` + - 改进`Map/Keys/Values`方法以提高性能 + - 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes` + - 新增更多功能方法支持:https://goframe.org/container/gmap/index +1. 改进`gtime`时间模块: + - 增加并完善更多的类`PHP`时间格式支持 + - 新增更多功能方法,如`FormatTo`/`LayoutTo`等等 + - 详见开发文档:https://goframe.org/os/gtime/index +1. 改进`gdb`数据库模块: + - 增加对继承结构体的数据转换支持:https://goframe.org/database/gdb/senior + - 新增`GetLastSql`方法,用以在调试模式下获取最近一条执行的SQL语句 + - 其他的细节处理改进 +1. 改进`gtcp`通信模块: + - 完善处理细节,提高通信性能; + - 增加`TLS`服务端/客户端通信支持:https://goframe.org/net/gtcp/tls + - 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题:https://goframe.org/net/gtcp/conn/pkg + - TCP服务端增加`Close`方法 + - 更多细节查看开发文档:https://goframe.org/net/gtcp/index +1. 改进`gconv`类型转换模块 + - 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration` + - 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换 + - 详见开发文档:https://goframe.org/util/gconv/struct +1. 改进`ghttp`模块: + - 日志输出增加`http/https`字段:https://goframe.org/net/ghttp/logs + - 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性 + - 增加`ghttp.Request.GetUrl`方法,用以获取当前完整的URL请求地址 + - `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`:https://goframe.org/net/ghttp/client +1. 新增`gtree`树形数据结构容器支持:https://goframe.org/container/gtree/index +1. 改进`gudp`通信模块,具体请参考开发文档:https://goframe.org/net/gudp/index +1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持:https://goframe.org/os/gcfg/index +1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换:https://goframe.org/database/gredis/index +1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes` +1. 改进`gjson`/`gparser`模块,增加更多方法:https://goframe.org/encoding/gjson/index +1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境; +1. 改进`grpool`协程池模块,提高执行性能:https://goframe.org/os/grpool/index +1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理 +1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法 +1. 去除`gvar.VarRead`接口类型支持 + +## Bug Fix +1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突; +1. 修复`gcron.AddOnce`方法的细节逻辑问题; +1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误; +1. 修复`gview`模板引擎的并发安全问题; +1. 修复`ghttp.Server`的SESSION初始化过期时间问题; + # `v1.6.0` (2019-04-09) ## 新功能/改进 diff --git a/TODO.MD b/TODO.MD index f68a8f50e..072b2c7d4 100644 --- a/TODO.MD +++ b/TODO.MD @@ -47,7 +47,7 @@ 1. gtimer增加DelayAdd*方法返回Entry对象,以便DelayAdd*的定时任务也能进行状态控制;gcron同理需要改进; 1. 改进gdb对pgsql/mssql/oracle的支持,使用方法覆盖的方式改进操作,而不是完全依靠正则替换的方式; 1. gdb的Cache缓存功能增加可自定义缓存接口,以便支持外部缓存功能,缓存接口可以通过io.ReadWriter接口实现; - +1. grpool增加支持阻塞添加任务接口; diff --git a/g/container/gchan/gchan.go b/g/container/gchan/gchan.go index 6b6be6da3..9e762162f 100644 --- a/g/container/gchan/gchan.go +++ b/g/container/gchan/gchan.go @@ -4,7 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gchan provides graceful channel for safe operations. +// Package gchan provides graceful channel for no panic operations. // // It's safe to call Chan.Push/Close functions repeatedly. package gchan @@ -14,12 +14,13 @@ import ( "github.com/gogf/gf/g/container/gtype" ) +// Graceful channel. type Chan struct { channel chan interface{} closed *gtype.Bool } -// New creates a graceful channel with given limit. +// New creates a graceful channel with given . func New(limit int) *Chan { return &Chan { channel : make(chan interface{}, limit), @@ -31,7 +32,7 @@ func New(limit int) *Chan { // It is safe to be called repeatedly. func (c *Chan) Push(value interface{}) error { if c.closed.Val() { - return errors.New("closed") + return errors.New("channel is closed") } c.channel <- value return nil @@ -39,6 +40,7 @@ func (c *Chan) Push(value interface{}) error { // Pop pops value from channel. // If there's no value in channel, it would block to wait. +// If the channel is closed, it will return a nil value immediately. func (c *Chan) Pop() interface{} { return <- c.channel } diff --git a/g/container/gqueue/gqueue.go b/g/container/gqueue/gqueue.go index ea99c0c6f..9ec7b12c7 100644 --- a/g/container/gqueue/gqueue.go +++ b/g/container/gqueue/gqueue.go @@ -37,8 +37,8 @@ const ( ) // New returns an empty queue object. -// Optional parameter is used to limit the size of the queue, which is unlimited by default. -// When is given, the queue will be static and high performance which is comparable with stdlib chan. +// Optional parameter is used to limit the size of the queue, which is unlimited in default. +// When is given, the queue will be static and high performance which is comparable with stdlib channel. func New(limit...int) *Queue { q := &Queue { closed : make(chan struct{}, 0), @@ -103,7 +103,7 @@ func (q *Queue) Pop() interface{} { // Close closes the queue. // Notice: It would notify all goroutines return immediately, -// which are being blocked reading by Pop method. +// which are being blocked reading using Pop method. func (q *Queue) Close() { close(q.C) close(q.events) diff --git a/g/container/gset/gset_z_unit_test.go b/g/container/gset/gset_z_unit_test.go index a0901a070..9ee7c192e 100644 --- a/g/container/gset/gset_z_unit_test.go +++ b/g/container/gset/gset_z_unit_test.go @@ -9,152 +9,172 @@ package gset_test import ( - "github.com/gogf/gf/g/container/garray" - "github.com/gogf/gf/g/container/gset" - "github.com/gogf/gf/g/test/gtest" - "testing" + "github.com/gogf/gf/g/container/garray" + "github.com/gogf/gf/g/container/gset" + "github.com/gogf/gf/g/test/gtest" + "testing" ) +func TestSet_New(t *testing.T) { + gtest.Case(t, func() { + s := gset.New() + s.Add(1).Add(1).Add(2) + s.Add([]interface{}{3, 4}...) + gtest.Assert(s.Size(), 4) + gtest.AssertIN(1, s.Slice()) + gtest.AssertIN(2, s.Slice()) + gtest.AssertIN(3, s.Slice()) + gtest.AssertIN(4, s.Slice()) + gtest.AssertNI(0, s.Slice()) + gtest.Assert(s.Contains(4), true) + gtest.Assert(s.Contains(5), false) + s.Remove(1) + gtest.Assert(s.Size(), 3) + s.Clear() + gtest.Assert(s.Size(), 0) + }) +} + func TestSet_Basic(t *testing.T) { - gtest.Case(t, func() { - s := gset.NewSet() - s.Add(1).Add(1).Add(2) - s.Add([]interface{}{3,4}...) - gtest.Assert(s.Size(), 4) - gtest.AssertIN(1, s.Slice()) - gtest.AssertIN(2, s.Slice()) - gtest.AssertIN(3, s.Slice()) - gtest.AssertIN(4, s.Slice()) - gtest.AssertNI(0, s.Slice()) - gtest.Assert(s.Contains(4), true) - gtest.Assert(s.Contains(5), false) - s.Remove(1) - gtest.Assert(s.Size(), 3) - s.Clear() - gtest.Assert(s.Size(), 0) - }) + gtest.Case(t, func() { + s := gset.NewSet() + s.Add(1).Add(1).Add(2) + s.Add([]interface{}{3, 4}...) + gtest.Assert(s.Size(), 4) + gtest.AssertIN(1, s.Slice()) + gtest.AssertIN(2, s.Slice()) + gtest.AssertIN(3, s.Slice()) + gtest.AssertIN(4, s.Slice()) + gtest.AssertNI(0, s.Slice()) + gtest.Assert(s.Contains(4), true) + gtest.Assert(s.Contains(5), false) + s.Remove(1) + gtest.Assert(s.Size(), 3) + s.Clear() + gtest.Assert(s.Size(), 0) + }) } func TestSet_Iterator(t *testing.T) { - gtest.Case(t, func() { - s := gset.NewSet() - s.Add(1).Add(2).Add(3) - gtest.Assert(s.Size(), 3) + gtest.Case(t, func() { + s := gset.NewSet() + s.Add(1).Add(2).Add(3) + gtest.Assert(s.Size(), 3) - a1 := garray.New() - a2 := garray.New() - s.Iterator(func(v interface{}) bool { - a1.Append(1) - return false - }) - s.Iterator(func(v interface{}) bool { - a2.Append(1) - return true - }) - gtest.Assert(a1.Len(), 1) - gtest.Assert(a2.Len(), 3) - }) + a1 := garray.New() + a2 := garray.New() + s.Iterator(func(v interface{}) bool { + a1.Append(1) + return false + }) + s.Iterator(func(v interface{}) bool { + a2.Append(1) + return true + }) + gtest.Assert(a1.Len(), 1) + gtest.Assert(a2.Len(), 3) + }) } func TestSet_LockFunc(t *testing.T) { - gtest.Case(t, func() { - s := gset.NewSet() - s.Add(1).Add(2).Add(3) - gtest.Assert(s.Size(), 3) - s.LockFunc(func(m map[interface{}]struct{}) { - delete(m, 1) - }) - gtest.Assert(s.Size(), 2) - s.RLockFunc(func(m map[interface{}]struct{}) { - gtest.Assert(m, map[interface{}]struct{}{ - 3 : struct{}{}, - 2 : struct{}{}, - }) - }) - }) + gtest.Case(t, func() { + s := gset.NewSet() + s.Add(1).Add(2).Add(3) + gtest.Assert(s.Size(), 3) + s.LockFunc(func(m map[interface{}]struct{}) { + delete(m, 1) + }) + gtest.Assert(s.Size(), 2) + s.RLockFunc(func(m map[interface{}]struct{}) { + gtest.Assert(m, map[interface{}]struct{}{ + 3: struct{}{}, + 2: struct{}{}, + }) + }) + }) } func TestSet_Equal(t *testing.T) { - gtest.Case(t, func() { - s1 := gset.NewSet() - s2 := gset.NewSet() - s3 := gset.NewSet() - s1.Add(1).Add(2).Add(3) - s2.Add(1).Add(2).Add(3) - s3.Add(1).Add(2).Add(3).Add(4) - gtest.Assert(s1.Equal(s2), true) - gtest.Assert(s1.Equal(s3), false) - }) + gtest.Case(t, func() { + s1 := gset.NewSet() + s2 := gset.NewSet() + s3 := gset.NewSet() + s1.Add(1).Add(2).Add(3) + s2.Add(1).Add(2).Add(3) + s3.Add(1).Add(2).Add(3).Add(4) + gtest.Assert(s1.Equal(s2), true) + gtest.Assert(s1.Equal(s3), false) + }) } func TestSet_IsSubsetOf(t *testing.T) { - gtest.Case(t, func() { - s1 := gset.NewSet() - s2 := gset.NewSet() - s3 := gset.NewSet() - s1.Add(1).Add(2) - s2.Add(1).Add(2).Add(3) - s3.Add(1).Add(2).Add(3).Add(4) - gtest.Assert(s1.IsSubsetOf(s2), true) - gtest.Assert(s2.IsSubsetOf(s3), true) - gtest.Assert(s1.IsSubsetOf(s3), true) - gtest.Assert(s2.IsSubsetOf(s1), false) - gtest.Assert(s3.IsSubsetOf(s2), false) - }) + gtest.Case(t, func() { + s1 := gset.NewSet() + s2 := gset.NewSet() + s3 := gset.NewSet() + s1.Add(1).Add(2) + s2.Add(1).Add(2).Add(3) + s3.Add(1).Add(2).Add(3).Add(4) + gtest.Assert(s1.IsSubsetOf(s2), true) + gtest.Assert(s2.IsSubsetOf(s3), true) + gtest.Assert(s1.IsSubsetOf(s3), true) + gtest.Assert(s2.IsSubsetOf(s1), false) + gtest.Assert(s3.IsSubsetOf(s2), false) + }) } func TestSet_Union(t *testing.T) { - gtest.Case(t, func() { - s1 := gset.NewSet() - s2 := gset.NewSet() - s1.Add(1).Add(2) - s2.Add(3).Add(4) - s3 := s1.Union(s2) - gtest.Assert(s3.Contains(1), true) - gtest.Assert(s3.Contains(2), true) - gtest.Assert(s3.Contains(3), true) - gtest.Assert(s3.Contains(4), true) - }) + gtest.Case(t, func() { + s1 := gset.NewSet() + s2 := gset.NewSet() + s1.Add(1).Add(2) + s2.Add(3).Add(4) + s3 := s1.Union(s2) + gtest.Assert(s3.Contains(1), true) + gtest.Assert(s3.Contains(2), true) + gtest.Assert(s3.Contains(3), true) + gtest.Assert(s3.Contains(4), true) + }) } func TestSet_Diff(t *testing.T) { - gtest.Case(t, func() { - s1 := gset.NewSet() - s2 := gset.NewSet() - s1.Add(1).Add(2).Add(3) - s2.Add(3).Add(4).Add(5) - s3 := s1.Diff(s2) - gtest.Assert(s3.Contains(1), true) - gtest.Assert(s3.Contains(2), true) - gtest.Assert(s3.Contains(3), false) - gtest.Assert(s3.Contains(4), false) - }) + gtest.Case(t, func() { + s1 := gset.NewSet() + s2 := gset.NewSet() + s1.Add(1).Add(2).Add(3) + s2.Add(3).Add(4).Add(5) + s3 := s1.Diff(s2) + gtest.Assert(s3.Contains(1), true) + gtest.Assert(s3.Contains(2), true) + gtest.Assert(s3.Contains(3), false) + gtest.Assert(s3.Contains(4), false) + }) } func TestSet_Intersect(t *testing.T) { - gtest.Case(t, func() { - s1 := gset.NewSet() - s2 := gset.NewSet() - s1.Add(1).Add(2).Add(3) - s2.Add(3).Add(4).Add(5) - s3 := s1.Intersect(s2) - gtest.Assert(s3.Contains(1), false) - gtest.Assert(s3.Contains(2), false) - gtest.Assert(s3.Contains(3), true) - gtest.Assert(s3.Contains(4), false) - }) + gtest.Case(t, func() { + s1 := gset.NewSet() + s2 := gset.NewSet() + s1.Add(1).Add(2).Add(3) + s2.Add(3).Add(4).Add(5) + s3 := s1.Intersect(s2) + gtest.Assert(s3.Contains(1), false) + gtest.Assert(s3.Contains(2), false) + gtest.Assert(s3.Contains(3), true) + gtest.Assert(s3.Contains(4), false) + }) } func TestSet_Complement(t *testing.T) { - gtest.Case(t, func() { - s1 := gset.NewSet() - s2 := gset.NewSet() - s1.Add(1).Add(2).Add(3) - s2.Add(3).Add(4).Add(5) - s3 := s1.Complement(s2) - gtest.Assert(s3.Contains(1), false) - gtest.Assert(s3.Contains(2), false) - gtest.Assert(s3.Contains(4), true) - gtest.Assert(s3.Contains(5), true) - }) -} \ No newline at end of file + gtest.Case(t, func() { + s1 := gset.NewSet() + s2 := gset.NewSet() + s1.Add(1).Add(2).Add(3) + s2.Add(3).Add(4).Add(5) + s3 := s1.Complement(s2) + gtest.Assert(s3.Contains(1), false) + gtest.Assert(s3.Contains(2), false) + gtest.Assert(s3.Contains(4), true) + gtest.Assert(s3.Contains(5), true) + }) +} diff --git a/g/crypto/gaes/gaes_test.go b/g/crypto/gaes/gaes_test.go index fbae75d9c..57f307a6e 100644 --- a/g/crypto/gaes/gaes_test.go +++ b/g/crypto/gaes/gaes_test.go @@ -9,6 +9,7 @@ package gaes_test import ( + "github.com/gogf/gf/g/encoding/gbase64" "testing" "github.com/gogf/gf/g/crypto/gaes" @@ -16,52 +17,111 @@ import ( ) var ( - content = []byte("pibigstar") + content = []byte("pibigstar") + content_16, _ = gbase64.Decode("v1jqsGHId/H8onlVHR8Vaw==") + content_24, _ = gbase64.Decode("0TXOaj5KMoLhNWmJ3lxY1A==") + content_32, _ = gbase64.Decode("qM/Waw1kkWhrwzek24rCSA==") + content_16_iv, _ = gbase64.Decode("DqQUXiHgW/XFb6Qs98+hrA==") + content_32_iv, _ = gbase64.Decode("ZuLgAOii+lrD5KJoQ7yQ8Q==") // iv 长度必须等于blockSize,只能为16 - iv = []byte("Hello My GoFrame") - key_16 = []byte("1234567891234567") - key_24 = []byte("123456789123456789123456") - key_32 = []byte("12345678912345678912345678912345") - keys = []byte("12345678912345678912345678912346") + iv = []byte("Hello My GoFrame") + key_16 = []byte("1234567891234567") + key_17 = []byte("12345678912345670") + key_24 = []byte("123456789123456789123456") + key_32 = []byte("12345678912345678912345678912345") + keys = []byte("12345678912345678912345678912346") + key_err = []byte("1234") + key_32_err = []byte("1234567891234567891234567891234 ") ) func TestEncrypt(t *testing.T) { gtest.Case(t, func() { - _, err := gaes.Encrypt(content, key_16) + data, err := gaes.Encrypt(content, key_16) gtest.Assert(err, nil) - _, err = gaes.Encrypt(content, key_24) + gtest.Assert(data, []byte(content_16)) + data, err = gaes.Encrypt(content, key_24) gtest.Assert(err, nil) - _, err = gaes.Encrypt(content, key_32) + gtest.Assert(data, []byte(content_24)) + data, err = gaes.Encrypt(content, key_32) gtest.Assert(err, nil) - _, err = gaes.Encrypt(content, key_16, iv) + gtest.Assert(data, []byte(content_32)) + data, err = gaes.Encrypt(content, key_16, iv) gtest.Assert(err, nil) + gtest.Assert(data, []byte(content_16_iv)) + data, err = gaes.Encrypt(content, key_32, iv) + gtest.Assert(err, nil) + gtest.Assert(data, []byte(content_32_iv)) }) } func TestDecrypt(t *testing.T) { gtest.Case(t, func() { - encrypt, err := gaes.Encrypt(content, key_16) - decrypt, err := gaes.Decrypt(encrypt, key_16) + decrypt, err := gaes.Decrypt([]byte(content_16), key_16) gtest.Assert(err, nil) - gtest.Assert(string(decrypt), string(content)) + gtest.Assert(decrypt, content) - encrypt, err = gaes.Encrypt(content, key_24) - decrypt, err = gaes.Decrypt(encrypt, key_24) + decrypt, err = gaes.Decrypt([]byte(content_24), key_24) gtest.Assert(err, nil) - gtest.Assert(string(decrypt), string(content)) + gtest.Assert(decrypt, content) - encrypt, err = gaes.Encrypt(content, key_32) - decrypt, err = gaes.Decrypt(encrypt, key_32) + decrypt, err = gaes.Decrypt([]byte(content_32), key_32) gtest.Assert(err, nil) - gtest.Assert(string(decrypt), string(content)) + gtest.Assert(decrypt, content) - encrypt, err = gaes.Encrypt(content, key_32, iv) - decrypt, err = gaes.Decrypt(encrypt, key_32, iv) + decrypt, err = gaes.Decrypt([]byte(content_16_iv), key_16, iv) gtest.Assert(err, nil) - gtest.Assert(string(decrypt), string(content)) + gtest.Assert(decrypt, content) - encrypt, err = gaes.Encrypt(content, key_32, iv) - decrypt, err = gaes.Decrypt(encrypt, keys, iv) + decrypt, err = gaes.Decrypt([]byte(content_32_iv), key_32, iv) + gtest.Assert(err, nil) + gtest.Assert(decrypt, content) + + decrypt, err = gaes.Decrypt([]byte(content_32_iv), keys, iv) gtest.Assert(err, "invalid padding") }) } + +func TestEncryptErr(t *testing.T) { + gtest.Case(t, func() { + // encrypt key error + _, err := gaes.Encrypt(content, key_err) + gtest.AssertNE(err, nil) + }) +} + +func TestDecryptErr(t *testing.T) { + gtest.Case(t, func() { + // decrypt key error + encrypt, err := gaes.Encrypt(content, key_16) + _, err = gaes.Decrypt(encrypt, key_err) + gtest.AssertNE(err, nil) + + // decrypt content too short error + _, err = gaes.Decrypt([]byte("test"), key_16) + gtest.AssertNE(err, nil) + + // decrypt content size error + _, err = gaes.Decrypt(key_17, key_16) + gtest.AssertNE(err, nil) + }) +} + +func TestPKCS5UnPaddingErr(t *testing.T) { + gtest.Case(t, func() { + // PKCS5UnPadding blockSize zero + _, err := gaes.PKCS5UnPadding(content, 0) + gtest.AssertNE(err, nil) + + // PKCS5UnPadding src len zero + _, err = gaes.PKCS5UnPadding([]byte(""), 16) + gtest.AssertNE(err, nil) + + // PKCS5UnPadding src len > blockSize + _, err = gaes.PKCS5UnPadding(key_17, 16) + gtest.AssertNE(err, nil) + + // PKCS5UnPadding src len > blockSize + _, err = gaes.PKCS5UnPadding(key_32_err, 32) + gtest.AssertNE(err, nil) + }) +} diff --git a/g/crypto/gcrc32/gcrc32_test.go b/g/crypto/gcrc32/gcrc32_test.go index 32f9a3094..7a67d1c41 100644 --- a/g/crypto/gcrc32/gcrc32_test.go +++ b/g/crypto/gcrc32/gcrc32_test.go @@ -9,13 +9,14 @@ package gcrc32_test import ( + "github.com/gogf/gf/g/crypto/gmd5" "testing" "github.com/gogf/gf/g/crypto/gcrc32" "github.com/gogf/gf/g/test/gtest" ) -func TestEncrypt(t *testing.T) { +func TestEncryptString(t *testing.T) { gtest.Case(t, func() { s := "pibigstar" result := 693191136 @@ -25,3 +26,19 @@ func TestEncrypt(t *testing.T) { gtest.AssertEQ(int(encrypt2), result) }) } + +func TestEncrypt(t *testing.T) { + gtest.Case(t, func() { + s := "pibigstar" + result := 693191136 + encrypt1 := gcrc32.Encrypt(s) + encrypt2 := gcrc32.Encrypt([]byte(s)) + gtest.AssertEQ(int(encrypt1), result) + gtest.AssertEQ(int(encrypt2), result) + + strmd5 := gmd5.Encrypt(s) + test1 := gcrc32.Encrypt(strmd5) + test2 := gcrc32.Encrypt([]byte(strmd5)) + gtest.AssertEQ(test2, test1) + }) +} diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index 706b18e76..be2a6959f 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -58,7 +58,7 @@ func (s *Session) init() { // 否则执行初始化创建 s.id = s.request.Cookie.MakeSessionId() s.data = gmap.NewStrAnyMap() - s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()) + s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) } } diff --git a/g/net/ghttp/ghttp_unit_router_hook_test.go b/g/net/ghttp/ghttp_unit_router_hook_test.go index 3fb7bf3c2..581c10c8d 100644 --- a/g/net/ghttp/ghttp_unit_router_hook_test.go +++ b/g/net/ghttp/ghttp_unit_router_hook_test.go @@ -85,3 +85,36 @@ func Test_Router_Hook_Priority(t *testing.T) { }) } +func Test_Router_Hook_Multi(t *testing.T) { + p := ports.PopRand() + s := g.Server(p) + s.BindHandler("/multi-hook", func(r *ghttp.Request) { + r.Response.Write("show") + }) + + s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc { + "BeforeServe" : func(r *ghttp.Request) { + r.Response.Write("1") + }, + }) + s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc { + "BeforeServe" : func(r *ghttp.Request) { + r.Response.Write("2") + }, + }) + s.SetPort(p) + s.SetDumpRouteMap(false) + s.Start() + defer s.Shutdown() + + // 等待启动完成 + time.Sleep(time.Second) + gtest.Case(t, func() { + client := ghttp.NewClient() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + gtest.Assert(client.GetContent("/"), "Not Found") + gtest.Assert(client.GetContent("/multi-hook"), "12show") + }) +} + diff --git a/g/os/gcache/gcache.go b/g/os/gcache/gcache.go index f24ccf268..345adda82 100644 --- a/g/os/gcache/gcache.go +++ b/g/os/gcache/gcache.go @@ -7,81 +7,100 @@ // Package gcache provides high performance and concurrent-safe in-memory cache for process. package gcache -// 全局缓存管理对象 +// Default cache object. var cache = New() -// (使用全局KV缓存对象)设置kv缓存键值对,过期时间单位为**毫秒** + +// Set sets cache with - pair, which is expired after milliseconds. +// If <=0 means it does not expire. func Set(key interface{}, value interface{}, expire int) { cache.Set(key, value, expire) } -// 当键名不存在时写入,并返回true;否则返回false。 -// 常用来做对并发性要求不高的内存锁。 +// SetIfNotExist sets cache with - pair if does not exist in the cache, +// which is expired after milliseconds. +// If <=0 means it does not expire. func SetIfNotExist(key interface{}, value interface{}, expire int) bool { return cache.SetIfNotExist(key, value, expire) } -// (使用全局KV缓存对象)批量设置kv缓存键值对,过期时间单位为**毫秒** +// Sets batch sets cache with key-value pairs by , which is expired after milliseconds. +// If <=0 means it does not expire. func Sets(data map[interface{}]interface{}, expire int) { cache.Sets(data, expire) } -// (使用全局KV缓存对象)获取指定键名的值 +// Get returns the value of . +// It returns nil if it does not exist or its value is nil. func Get(key interface{}) interface{} { return cache.Get(key) } -// 当键名存在时返回其键值,否则写入指定的键值 +// GetOrSet returns the value of , +// or sets - pair and returns if does not exist in the cache. +// The key-value pair expires after milliseconds. +// If <=0 means it does not expire. func GetOrSet(key interface{}, value interface{}, expire int) interface{} { return cache.GetOrSet(key, value, expire) } -// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成 + +// GetOrSetFunc returns the value of , +// or sets with result of function and returns its result +// if does not exist in the cache. +// The key-value pair expires after milliseconds. +// If <=0 means it does not expire. func GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} { return cache.GetOrSetFunc(key, f, expire) } -// 与GetOrSetFunc不同的是,f是在写锁机制内执行 +// GetOrSetFuncLock returns the value of , +// or sets with result of function and returns its result +// if does not exist in the cache. +// The key-value pair expires after milliseconds. +// If <=0 means it does not expire. +// +// Note that the function is executed within writing mutex lock. func GetOrSetFuncLock(key interface{}, f func() interface{}, expire int) interface{} { return cache.GetOrSetFuncLock(key, f, expire) } -// 是否存在指定的键名,true表示存在,false表示不存在。 +// Contains returns true if exists in the cache, or else returns false. func Contains(key interface{}) bool { return cache.Contains(key) } -// (使用全局KV缓存对象)删除指定键值对 +// Remove deletes the in the cache, and returns its value. func Remove(key interface{}) interface{} { return cache.Remove(key) } -// (使用全局KV缓存对象)批量删除指定键值对 +// Removes deletes in the cache. func Removes(keys []interface{}) { cache.Removes(keys) } -// 返回缓存的所有数据键值对(不包含已过期数据) +// Data returns a copy of all key-value pairs in the cache as map type. func Data() map[interface{}]interface{} { return cache.Data() } -// 获得所有的键名,组成数组返回 +// Keys returns all keys in the cache as slice. func Keys() []interface{} { return cache.Keys() } -// 获得所有的键名,组成字符串数组返回 +// KeyStrings returns all keys in the cache as string slice. func KeyStrings() []string { return cache.KeyStrings() } -// 获得所有的值,组成数组返回 +// Values returns all values in the cache as slice. func Values() []interface{} { return cache.Values() } -// 获得缓存对象的键值对数量 +// Size returns the size of the cache. func Size() int { return cache.Size() } diff --git a/g/os/gcache/gcache_cache.go b/g/os/gcache/gcache_cache.go index 33ad10e74..e4456a9db 100644 --- a/g/os/gcache/gcache_cache.go +++ b/g/os/gcache/gcache_cache.go @@ -13,13 +13,12 @@ import ( "unsafe" ) -// 缓存对象。 -// 底层只有一个缓存对象,如果需要提高并发性能,可新增缓存对象无锁哈希表,用键名做固定分区。 +// Cache struct. type Cache struct { *memCache } -// Cache对象按照缓存键名首字母做了分组 +// New creates and returns a new cache object. func New(lruCap...int) *Cache { c := &Cache { memCache : newMemCache(lruCap...), @@ -28,10 +27,8 @@ func New(lruCap...int) *Cache { return c } -// 清空缓存中的所有数据 +// Clear clears all data of the cache. func (c *Cache) Clear() { - // 使用原子操作替换缓存对象 old := atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.memCache)), unsafe.Pointer(newMemCache())) - // 关闭旧的缓存对象 (*memCache)(old).Close() } \ No newline at end of file diff --git a/g/os/gcache/gcache_mem_cache.go b/g/os/gcache/gcache_mem_cache.go index 33836382e..0c4491dca 100644 --- a/g/os/gcache/gcache_mem_cache.go +++ b/g/os/gcache/gcache_mem_cache.go @@ -18,21 +18,25 @@ import ( ) -// 缓存对象 +// Internal cache object. type memCache struct { dataMu sync.RWMutex expireTimeMu sync.RWMutex expireSetMu sync.RWMutex - cap int // 控制缓存池大小,超过大小则按照LRU算法进行缓存过期处理(默认为0表示不进行限制) - data map[interface{}]memCacheItem // 缓存数据(所有的缓存数据存放哈希表) - expireTimes map[interface{}]int64 // 键名对应的分组过期时间(用于相同键名过期时间快速更新),键值为1秒级时间戳 - expireSets map[int64]*gset.Set // 分组过期时间对应的键名列表(用于自动过期快速删除),键值为1秒级时间戳 + // limits the size of the cache pool. + // If the size of the cache exceeds the , + // the cache expiration process is performed according to the LRU algorithm. + // It is 0 in default which means no limits. + cap int + data map[interface{}]memCacheItem // Underlying cache data which is stored in a hash table. + expireTimes map[interface{}]int64 // Expiring key mapping to its timestamp, which is used for quick indexing and deleting. + expireSets map[int64]*gset.Set // Expiring timestamp mapping to its key set, which is used for quick indexing and deleting. - lru *memCacheLru // LRU缓存限制(只有限定cap池大小时才启用) - lruGetList *glist.List // Get操作的LRU记录 - eventList *glist.List // 异步处理队列 - closed *gtype.Bool // 关闭事件通知 + lru *memCacheLru // LRU object, which is enabled when > 0. + lruGetList *glist.List // LRU history according with Get function. + eventList *glist.List // Asynchronous event list for internal data synchronization. + closed *gtype.Bool // Is this cache closed or not. } // 缓存数据项 diff --git a/g/os/gcache/gcache_z_unit_1_test.go b/g/os/gcache/gcache_z_unit_1_test.go index 4f0590a66..0cd6ea07c 100644 --- a/g/os/gcache/gcache_z_unit_1_test.go +++ b/g/os/gcache/gcache_z_unit_1_test.go @@ -9,57 +9,265 @@ package gcache_test import ( - "github.com/gogf/gf/g/os/gcache" - "github.com/gogf/gf/g/test/gtest" - "testing" - "time" + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/container/gset" + "github.com/gogf/gf/g/os/gcache" + "github.com/gogf/gf/g/os/grpool" + "github.com/gogf/gf/g/test/gtest" + "testing" + "time" ) +//clear 用于清除全局缓存,因gcache api 暂未暴露 Clear 方法 +//暂定所有测试用例key的集合为1,2,3,避免不同测试用例间因全局cache共享带来的问题,每个测试用例在测试gcache.XXX之前,先调用clear() +func clear() { + gcache.Removes(g.Slice{1, 2, 3}) +} + func TestCache_Set(t *testing.T) { - gtest.Case(t, func() { - cache := gcache.New() - cache.Set(1, 11, 0) - gtest.Assert(cache.Get(1), 11) - }) + gtest.Case(t, func() { + cache := gcache.New() + cache.Set(1, 11, 0) + gtest.Assert(cache.Get(1), 11) + gtest.Assert(cache.Contains(1), true) + + clear() + gcache.Set(1, 11, 0) + gtest.Assert(gcache.Get(1), 11) + gtest.Assert(gcache.Contains(1), true) + }) } func TestCache_Set_Expire(t *testing.T) { - gtest.Case(t, func() { - cache := gcache.New() - cache.Set(2, 22, 100) - gtest.Assert(cache.Get(2), 22) - time.Sleep(200*time.Millisecond) - gtest.Assert(cache.Get(2), nil) - time.Sleep(3*time.Second) - gtest.Assert(cache.Size(), 0) - }) + gtest.Case(t, func() { + cache := gcache.New() + cache.Set(2, 22, 100) + gtest.Assert(cache.Get(2), 22) + time.Sleep(200 * time.Millisecond) + gtest.Assert(cache.Get(2), nil) + time.Sleep(3 * time.Second) + gtest.Assert(cache.Size(), 0) + cache.Close() + }) } func TestCache_Keys_Values(t *testing.T) { - gtest.Case(t, func() { - cache := gcache.New() - for i := 0; i < 10; i++ { - cache.Set(i, i*10, 0) - } - gtest.Assert(len(cache.Keys()), 10) - gtest.Assert(len(cache.Values()), 10) - gtest.AssertIN(0, cache.Keys()) - gtest.AssertIN(90, cache.Values()) - }) + gtest.Case(t, func() { + cache := gcache.New() + for i := 0; i < 10; i++ { + cache.Set(i, i*10, 0) + } + gtest.Assert(len(cache.Keys()), 10) + gtest.Assert(len(cache.Values()), 10) + gtest.AssertIN(0, cache.Keys()) + gtest.AssertIN(90, cache.Values()) + }) } func TestCache_LRU(t *testing.T) { - gtest.Case(t, func() { - cache := gcache.New(2) - for i := 0; i < 10; i++ { - cache.Set(i, i, 0) - } + gtest.Case(t, func() { + cache := gcache.New(2) + for i := 0; i < 10; i++ { + cache.Set(i, i, 0) + } + gtest.Assert(cache.Size(), 10) + gtest.Assert(cache.Get(6), 6) + time.Sleep(4 * time.Second) + gtest.Assert(cache.Size(), 2) + gtest.Assert(cache.Get(6), 6) + gtest.Assert(cache.Get(1), nil) + cache.Close() + }) +} - gtest.Assert(cache.Size(), 10) - gtest.Assert(cache.Get(6), 6) - time.Sleep(4*time.Second) - gtest.Assert(cache.Size(), 2) - gtest.Assert(cache.Get(6), 6) - gtest.Assert(cache.Get(1), nil) - }) -} \ No newline at end of file +func TestCache_LRU_expire(t *testing.T) { + gtest.Case(t, func() { + cache := gcache.New(2) + cache.Set(1, nil, 1000) + gtest.Assert(cache.Size(), 1) + gtest.Assert(cache.Get(1), nil) + }) +} + +func TestCache_SetIfNotExist(t *testing.T) { + gtest.Case(t, func() { + cache := gcache.New() + cache.SetIfNotExist(1, 11, 0) + gtest.Assert(cache.Get(1), 11) + cache.SetIfNotExist(1, 22, 0) + gtest.Assert(cache.Get(1), 11) + cache.SetIfNotExist(2, 22, 0) + gtest.Assert(cache.Get(2), 22) + + clear() + gcache.SetIfNotExist(1, 11, 0) + gtest.Assert(gcache.Get(1), 11) + gcache.SetIfNotExist(1, 22, 0) + gtest.Assert(gcache.Get(1), 11) + }) +} + +func TestCache_Sets(t *testing.T) { + gtest.Case(t, func() { + cache := gcache.New() + cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0) + gtest.Assert(cache.Get(1), 11) + + clear() + gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0) + gtest.Assert(gcache.Get(1), 11) + }) +} + +func TestCache_GetOrSet(t *testing.T) { + gtest.Case(t, func() { + cache := gcache.New() + cache.GetOrSet(1, 11, 0) + gtest.Assert(cache.Get(1), 11) + cache.GetOrSet(1, 111, 0) + gtest.Assert(cache.Get(1), 11) + + clear() + gcache.GetOrSet(1, 11, 0) + gtest.Assert(gcache.Get(1), 11) + gcache.GetOrSet(1, 111, 0) + gtest.Assert(gcache.Get(1), 11) + }) +} + +func TestCache_GetOrSetFunc(t *testing.T) { + gtest.Case(t, func() { + cache := gcache.New() + cache.GetOrSetFunc(1, func() interface{} { + return 11 + }, 0) + gtest.Assert(cache.Get(1), 11) + cache.GetOrSetFunc(1, func() interface{} { + return 111 + }, 0) + gtest.Assert(cache.Get(1), 11) + + clear() + gcache.GetOrSetFunc(1, func() interface{} { + return 11 + }, 0) + gtest.Assert(gcache.Get(1), 11) + gcache.GetOrSetFunc(1, func() interface{} { + return 111 + }, 0) + gtest.Assert(gcache.Get(1), 11) + }) +} + +func TestCache_GetOrSetFuncLock(t *testing.T) { + gtest.Case(t, func() { + cache := gcache.New() + cache.GetOrSetFuncLock(1, func() interface{} { + return 11 + }, 0) + gtest.Assert(cache.Get(1), 11) + cache.GetOrSetFuncLock(1, func() interface{} { + return 111 + }, 0) + gtest.Assert(cache.Get(1), 11) + + clear() + gcache.GetOrSetFuncLock(1, func() interface{} { + return 11 + }, 0) + gtest.Assert(gcache.Get(1), 11) + gcache.GetOrSetFuncLock(1, func() interface{} { + return 111 + }, 0) + gtest.Assert(gcache.Get(1), 11) + }) +} + +func TestCache_Clear(t *testing.T) { + gtest.Case(t, func() { + cache := gcache.New() + cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0) + cache.Clear() + gtest.Assert(cache.Size(), 0) + }) +} + +func TestCache_SetConcurrency(t *testing.T) { + gtest.Case(t, func() { + cache := gcache.New() + pool := grpool.New(4) + go func() { + for { + pool.Add(func() { + cache.SetIfNotExist(1, 11, 10) + }) + } + }() + select { + case <-time.After(2 * time.Second): + t.Log("first part end") + } + + go func() { + for { + pool.Add(func() { + cache.SetIfNotExist(1, nil, 10) + }) + } + }() + select { + case <-time.After(2 * time.Second): + t.Log("second part end") + } + }) +} + +func TestCache_Basic(t *testing.T) { + gtest.Case(t, func() { + { + cache := gcache.New() + cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0) + gtest.Assert(cache.Contains(1), true) + gtest.Assert(cache.Get(1), 11) + data := cache.Data() + gtest.Assert(data[1], 11) + gtest.Assert(data[2], 22) + gtest.Assert(data[3], nil) + gtest.Assert(cache.Size(), 2) + keys := cache.Keys() + gtest.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true) + keyStrs := cache.KeyStrings() + gtest.Assert(gset.NewFrom(g.Slice{"1", "2"}).Equal(gset.NewFrom(keyStrs)), true) + values := cache.Values() + gtest.Assert(gset.NewFrom(g.Slice{11, 22}).Equal(gset.NewFrom(values)), true) + removeData1 := cache.Remove(1) + gtest.Assert(removeData1, 11) + gtest.Assert(cache.Size(), 1) + cache.Removes(g.Slice{2}) + gtest.Assert(cache.Size(), 0) + } + + clear() + { + gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0) + gtest.Assert(gcache.Contains(1), true) + gtest.Assert(gcache.Get(1), 11) + data := gcache.Data() + gtest.Assert(data[1], 11) + gtest.Assert(data[2], 22) + gtest.Assert(data[3], nil) + gtest.Assert(gcache.Size(), 2) + keys := gcache.Keys() + gtest.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true) + keyStrs := gcache.KeyStrings() + gtest.Assert(gset.NewFrom(g.Slice{"1", "2"}).Equal(gset.NewFrom(keyStrs)), true) + values := gcache.Values() + gtest.Assert(gset.NewFrom(g.Slice{11, 22}).Equal(gset.NewFrom(values)), true) + removeData1 := gcache.Remove(1) + gtest.Assert(removeData1, 11) + gtest.Assert(gcache.Size(), 1) + gcache.Removes(g.Slice{2}) + gtest.Assert(gcache.Size(), 0) + } + }) +} diff --git a/g/os/genv/genv_test.go b/g/os/genv/genv_test.go new file mode 100644 index 000000000..9f9a894e4 --- /dev/null +++ b/g/os/genv/genv_test.go @@ -0,0 +1,43 @@ +package genv_test + +import ( + "github.com/gogf/gf/g/os/genv" + "github.com/gogf/gf/g/test/gtest" + "os" + "testing" +) + +func Test_Genv_All(t *testing.T) { + gtest.Case(t, func() { + gtest.Assert(os.Environ(), genv.All()) + }) +} + +func Test_Genv_Get(t *testing.T) { + gtest.Case(t, func() { + key := "TEST_GET_ENV" + err := os.Setenv(key, "TEST") + gtest.Assert(err, nil) + gtest.AssertEQ(genv.Get(key), "TEST") + }) +} + +func Test_Genv_Set(t *testing.T) { + gtest.Case(t, func() { + key := "TEST_SET_ENV" + err := genv.Set(key, "TEST") + gtest.Assert(err, nil) + gtest.AssertEQ(os.Getenv(key), "TEST") + }) +} + +func Test_Genv_Remove(t *testing.T) { + gtest.Case(t, func() { + key := "TEST_REMOVE_ENV" + err := os.Setenv(key, "TEST") + gtest.Assert(err, nil) + err = genv.Remove(key) + gtest.Assert(err, nil) + gtest.AssertEQ(os.Getenv(key), "") + }) +} diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index e55e156f4..e658a869b 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -425,8 +425,7 @@ func MainPkgPath() string { continue } // separator of '/' will be converted to Separator. - path = Dir(file) - for path[len(path) - 1] != os.PathSeparator { + for path = Dir(file); len(path) > 1 && Exists(path) && path[len(path) - 1] != os.PathSeparator; { files, _ := ScanDir(path, "*.go") for _, v := range files { if gregex.IsMatchString(`package\s+main`, GetContents(v)) { diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index 7b9329c5f..5d6562d98 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -243,6 +243,13 @@ func (l *Logger) print(std io.Writer, lead string, value...interface{}) { if len(timeFormat) > 0 { buffer.WriteString(time.Now().Format(timeFormat)) } + // Lead string. + if len(lead) > 0 { + buffer.WriteString(lead) + if len(value) > 0 { + buffer.WriteByte(' ') + } + } // Caller path. callerPath := "" if l.flags & F_FILE_LONG > 0 { @@ -259,12 +266,6 @@ func (l *Logger) print(std io.Writer, lead string, value...interface{}) { buffer.WriteString(l.prefix + " ") } } - if len(lead) > 0 { - buffer.WriteString(lead) - if len(value) > 0 { - buffer.WriteByte(' ') - } - } for k, v := range value { if k > 0 { buffer.WriteByte(' ') diff --git a/g/os/grpool/grpool.go b/g/os/grpool/grpool.go index 8d2bf116c..9add6fe8b 100644 --- a/g/os/grpool/grpool.go +++ b/g/os/grpool/grpool.go @@ -14,10 +14,10 @@ import ( // Goroutine Pool type Pool struct { - limit int // Max goroutine count limit. - count *gtype.Int // Current running goroutine count. - list *glist.List // Job list. - closed *gtype.Bool // Is pool closed or not. + limit int // Max goroutine count limit. + count *gtype.Int // Current running goroutine count. + list *glist.List // Job list for asynchronous job adding purpose. + closed *gtype.Bool // Is pool closed or not. } // Default goroutine pool. @@ -33,7 +33,7 @@ func New(limit...int) *Pool { list : glist.New(), closed : gtype.NewBool(), } - if len(limit) > 0 { + if len(limit) > 0 && limit[0] > 0 { p.limit = limit[0] } return p @@ -72,6 +72,7 @@ func (p *Pool) Add(f func()) { p.fork() } + // Size returns current goroutine count of the pool. func (p *Pool) Size() int { return p.count.Val() diff --git a/g/os/gtime/gtime.go b/g/os/gtime/gtime.go index 5e45231a9..abd557567 100644 --- a/g/os/gtime/gtime.go +++ b/g/os/gtime/gtime.go @@ -5,8 +5,6 @@ // You can obtain one at https://github.com/gogf/gf. // Package gtime provides functionality for measuring and displaying time. -// -// 时间管理. package gtime import ( diff --git a/g/text/gregex/gregex_z_unit_test.go b/g/text/gregex/gregex_z_unit_test.go index 64ce85489..bd2556b3b 100644 --- a/g/text/gregex/gregex_z_unit_test.go +++ b/g/text/gregex/gregex_z_unit_test.go @@ -15,6 +15,10 @@ import ( "testing" ) +var ( + PatternErr = `([\d+` +) + func Test_Quote(t *testing.T) { gtest.Case(t, func() { s1 := `[foo]` //`\[foo\]` @@ -40,6 +44,8 @@ func Test_IsMatch(t *testing.T) { gtest.Assert(gregex.IsMatch(pattern, s1), false) s1 = []byte(`sfs:`) gtest.Assert(gregex.IsMatch(pattern, s1), false) + // error pattern + gtest.Assert(gregex.IsMatch(PatternErr, s1), false) }) } @@ -52,6 +58,8 @@ func Test_IsMatchString(t *testing.T) { gtest.Assert(gregex.IsMatchString(pattern, s1), false) s1 = `sfs:` gtest.Assert(gregex.IsMatchString(pattern, s1), false) + // error pattern + gtest.Assert(gregex.IsMatchString(PatternErr, s1), false) }) } @@ -68,6 +76,9 @@ func Test_Match(t *testing.T) { if string(subs[1]) != "aab" { t.Fatalf("Match(%q)[1] = %q; want %q", s, subs[1], "aab") } + // error pattern + _, err = gregex.Match(PatternErr, []byte(s)) + gtest.AssertNE(err, nil) }) } @@ -84,6 +95,9 @@ func Test_MatchString(t *testing.T) { if string(subs[1]) != "aab" { t.Fatalf("Match(%q)[1] = %q; want %q", s, subs[1], "aab") } + // error pattern + _, err = gregex.MatchString(PatternErr, s) + gtest.AssertNE(err, nil) }) } @@ -108,6 +122,9 @@ func Test_MatchAll(t *testing.T) { if string(subs[1][1]) != "aab" { t.Fatalf("Match(%q)[1] = %q; want %q", s, subs[1][1], "aab") } + // error pattern + _, err = gregex.MatchAll(PatternErr, []byte(s)) + gtest.AssertNE(err, nil) }) } @@ -131,6 +148,9 @@ func Test_MatchAllString(t *testing.T) { if string(subs[1][1]) != "aab" { t.Fatalf("Match(%q)[1] = %q; want %q", s, subs[1][1], "aab") } + // error pattern + _, err = gregex.MatchAllString(PatternErr, s) + gtest.AssertNE(err, nil) }) } @@ -146,6 +166,9 @@ func Test_Replace(t *testing.T) { if string(replacedStr) != wanted { t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted) } + // error pattern + _, err = gregex.Replace(PatternErr, []byte(replace), []byte(s)) + gtest.AssertNE(err, nil) }) } @@ -161,6 +184,9 @@ func Test_ReplaceString(t *testing.T) { if replacedStr != wanted { t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted) } + // error pattern + _, err = gregex.ReplaceString(PatternErr, replace, s) + gtest.AssertNE(err, nil) }) } @@ -182,6 +208,11 @@ func Test_ReplaceFun(t *testing.T) { if string(replacedStr) != wanted { t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted) } + // error pattern + _, err = gregex.ReplaceFunc(PatternErr, []byte(s), func(s []byte) []byte { + return []byte("") + }) + gtest.AssertNE(err, nil) }) } @@ -209,6 +240,11 @@ func Test_ReplaceFuncMatch(t *testing.T) { }) gtest.Assert(e3, nil) gtest.Assert(s3, []byte("7890")) + // error pattern + _, err := gregex.ReplaceFuncMatch(PatternErr, s, func(match [][]byte) []byte { + return match[3] + }) + gtest.AssertNE(err, nil) }) } @@ -230,6 +266,11 @@ func Test_ReplaceStringFunc(t *testing.T) { if replacedStr != wanted { t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted) } + // error pattern + _, err = gregex.ReplaceStringFunc(PatternErr, s, func(s string) string { + return "" + }) + gtest.AssertNE(err, nil) }) } @@ -257,6 +298,11 @@ func Test_ReplaceStringFuncMatch(t *testing.T) { }) gtest.Assert(e3, nil) gtest.Assert(s3, "7890") + // error pattern + _, err := gregex.ReplaceStringFuncMatch(PatternErr, s, func(match []string) string { + return "" + }) + gtest.AssertNE(err, nil) }) } @@ -288,6 +334,9 @@ func Test_Split(t *testing.T) { if items[0] != s { t.Fatalf("regex:%s,Split(%q) want %q", re, s, item0) } + // error pattern + items = gregex.Split(PatternErr, s) + gtest.AssertEQ(items, nil) }) } diff --git a/g/util/gconv/gconv_struct.go b/g/util/gconv/gconv_struct.go index 8ffe29a09..d5cc119c4 100644 --- a/g/util/gconv/gconv_struct.go +++ b/g/util/gconv/gconv_struct.go @@ -157,7 +157,9 @@ func StructDeep(params interface{}, pointer interface{}, mapping...map[string]st trv := rv.Field(i) switch trv.Kind() { case reflect.Struct: - StructDeep(params, trv, mapping...) + if err := StructDeep(params, trv, mapping...); err != nil { + return err + } } } } @@ -239,7 +241,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er switch structFieldValue.Kind() { // 属性为结构体 case reflect.Struct: - Struct(value, structFieldValue) + if err := Struct(value, structFieldValue); err != nil { + structFieldValue.Set(reflect.ValueOf(value)) + } // 属性为数组类型 case reflect.Slice: fallthrough @@ -253,11 +257,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er for i := 0; i < v.Len(); i++ { if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - Struct(v.Index(i).Interface(), e) + if err := Struct(v.Index(i).Interface(), e); err != nil { + e.Set(reflect.ValueOf(v.Index(i).Interface())) + } a.Index(i).Set(e.Addr()) } else { e := reflect.New(t).Elem() - Struct(v.Index(i).Interface(), e) + if err := Struct(v.Index(i).Interface(), e); err != nil { + e.Set(reflect.ValueOf(v.Index(i).Interface())) + } a.Index(i).Set(e) } } @@ -267,11 +275,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er t := a.Index(0).Type() if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } a.Index(0).Set(e.Addr()) } else { e := reflect.New(t).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } a.Index(0).Set(e) } } @@ -280,7 +292,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er // 属性为指针类型 case reflect.Ptr: e := reflect.New(structFieldValue.Type().Elem()).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } structFieldValue.Set(e.Addr()) default: diff --git a/g/util/gconv/gconv_z_unit_struct_test.go b/g/util/gconv/gconv_z_unit_struct_test.go index 35d4274f8..0efe7987f 100644 --- a/g/util/gconv/gconv_z_unit_struct_test.go +++ b/g/util/gconv/gconv_z_unit_struct_test.go @@ -7,12 +7,12 @@ package gconv_test import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/os/gtime" - "github.com/gogf/gf/g/test/gtest" - "github.com/gogf/gf/g/util/gconv" - "testing" - "time" + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/os/gtime" + "github.com/gogf/gf/g/test/gtest" + "github.com/gogf/gf/g/util/gconv" + "testing" + "time" ) func Test_Struct_Basic1(t *testing.T) { @@ -104,6 +104,26 @@ func Test_Struct_Basic2(t *testing.T) { }) } +// 带有指针的基础类型属性 +func Test_Struct_Basic3(t *testing.T) { + gtest.Case(t, func() { + type User struct { + Uid int + Name *string + } + user := new(User) + params := g.Map { + "uid" : 1, + "Name" : "john", + } + if err := gconv.Struct(params, user); err != nil { + gtest.Error(err) + } + gtest.Assert(user.Uid, 1) + gtest.Assert(*user.Name, "john") + }) +} + // slice类型属性的赋值 func Test_Struct_Attr_Slice(t *testing.T) { gtest.Case(t, func() { diff --git a/geg/os/glog/glog_line.go b/geg/os/glog/glog_line.go index 41d2e0779..ab28cb622 100644 --- a/geg/os/glog/glog_line.go +++ b/geg/os/glog/glog_line.go @@ -5,6 +5,6 @@ import ( ) func main() { - glog.Line().Println("this is the short file name with its line number") - glog.Line(true).Println("lone file name with line number") + glog.Line().Debug("this is the short file name with its line number") + glog.Line(true).Debug("lone file name with line number") } diff --git a/geg/other/test.go b/geg/other/test.go index 729ac6946..9f3000841 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,19 +1,11 @@ package main import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/net/ghttp" + "fmt" + "github.com/gogf/gf/g/os/gtime" ) func main() { - s := g.Server() - s.BindHandler("/", func(r *ghttp.Request) { - rs :="GF(Go Frame)是一款模块化、松耦合、生产级的Go应用开发框架。提供了常用的核心开发组件,如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、 并发安全容器等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、路由注册、配置管理、模板引擎等等,支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。" - //此行压测会提示map并发错误 webbench -c 8000 -t 60 http://IP 局域网两台机器测试 - r.Response.WriteTplContent(rs, g.Map{ - "Contentb": 1, - }) - }) - s.SetPort(8199) - s.Run() + fmt.Println(gtime.Now().Format("U")) + fmt.Println(gtime.Second()) } \ No newline at end of file diff --git a/go.mod b/go.mod index fd3232e72..28729e4f6 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,2 @@ -module github.com/gogf/gf \ No newline at end of file +module github.com/gogf/gf + diff --git a/version.go b/version.go index 9dae0d748..ad997480a 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.6.17" +const VERSION = "v1.7.0" const AUTHORS = "john"