test: server 包完善部分测试用例

This commit is contained in:
kercylan98 2024-01-15 17:27:29 +08:00
parent 22449ff5c3
commit bbf70fab02
17 changed files with 391 additions and 106 deletions

View File

@ -16,6 +16,22 @@ import (
"time"
)
// Network 服务器运行的网络模式
// - 根据不同的网络模式,服务器将会产生不同的行为,该类型将在服务器创建时候指定
//
// 服务器支持的网络模式如下:
// - NetworkNone 该模式下不监听任何网络端口,仅开启消息队列,适用于纯粹的跨服服务器等情况
// - NetworkTcp 该模式下将会监听 TCP 协议的所有地址,包括 IPv4 和 IPv6
// - NetworkTcp4 该模式下将会监听 TCP 协议的 IPv4 地址
// - NetworkTcp6 该模式下将会监听 TCP 协议的 IPv6 地址
// - NetworkUdp 该模式下将会监听 UDP 协议的所有地址,包括 IPv4 和 IPv6
// - NetworkUdp4 该模式下将会监听 UDP 协议的 IPv4 地址
// - NetworkUdp6 该模式下将会监听 UDP 协议的 IPv6 地址
// - NetworkUnix 该模式下将会监听 Unix 协议的地址
// - NetworkHttp 该模式下将会监听 HTTP 协议的地址
// - NetworkWebsocket 该模式下将会监听 Websocket 协议的地址
// - NetworkKcp 该模式下将会监听 KCP 协议的地址
// - NetworkGRPC 该模式下将会监听 GRPC 协议的地址
type Network string
const (
@ -321,7 +337,16 @@ func (n Network) websocketMode(state chan<- error, srv *Server) {
}((&listener{srv: srv, Listener: l, state: state}).init(), mux)
}
// IsSocket 返回当前服务器的网络模式是否为 Socket 模式
// IsSocket 返回当前服务器的网络模式是否为 Socket 模式,目前为止仅有如下几种模式为 Socket 模式:
// - NetworkTcp
// - NetworkTcp4
// - NetworkTcp6
// - NetworkUdp
// - NetworkUdp4
// - NetworkUdp6
// - NetworkUnix
// - NetworkKcp
// - NetworkWebsocket
func (n Network) IsSocket() bool {
return collection.KeyInMap(socketNetworks, n)
}

View File

@ -0,0 +1,51 @@
package server_test
import (
"fmt"
"github.com/kercylan98/minotaur/server"
"github.com/kercylan98/minotaur/utils/times"
"time"
)
// 服务器在启动时将阻塞 1s模拟了慢消息的过程这时候如果通过 RegMessageLowExecEvent 函数注册过慢消息事件,将会收到该事件的消息
// - 该示例中,将在收到慢消息时关闭服务器
func ExampleWithLowMessageDuration() {
srv := server.New(server.NetworkNone,
server.WithLowMessageDuration(time.Second),
)
srv.RegStartFinishEvent(func(srv *server.Server) {
time.Sleep(time.Second)
})
srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) {
srv.Shutdown()
fmt.Println(times.GetSecond(cost))
})
if err := srv.RunNone(); err != nil {
panic(err)
}
// Output:
// 1
}
// 服务器在启动时将发布一条阻塞 1s 的异步消息,模拟了慢消息的过程,这时候如果通过 RegMessageLowExecEvent 函数注册过慢消息事件,将会收到该事件的消息
// - 该示例中,将在收到慢消息时关闭服务器
func ExampleWithAsyncLowMessageDuration() {
srv := server.New(server.NetworkNone,
server.WithAsyncLowMessageDuration(time.Second),
)
srv.RegStartFinishEvent(func(srv *server.Server) {
srv.PushAsyncMessage(func() error {
time.Sleep(time.Second)
return nil
}, nil)
})
srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) {
srv.Shutdown()
fmt.Println(times.GetSecond(cost))
})
if err := srv.RunNone(); err != nil {
panic(err)
}
// Output:
// 1
}

109
server/options_test.go Normal file
View File

@ -0,0 +1,109 @@
package server_test
import (
"fmt"
"github.com/kercylan98/minotaur/server"
"github.com/kercylan98/minotaur/utils/random"
"testing"
"time"
)
func TestWithLowMessageDuration(t *testing.T) {
var cases = []struct {
name string
duration time.Duration
}{
{name: "TestWithLowMessageDuration", duration: server.DefaultLowMessageDuration},
{name: "TestWithLowMessageDuration_Zero", duration: 0},
{name: "TestWithLowMessageDuration_Negative", duration: -server.DefaultAsyncLowMessageDuration},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
networks := server.GetNetworks()
for i := 0; i < len(networks); i++ {
low := false
network := networks[i]
srv := server.New(network,
server.WithLowMessageDuration(c.duration),
)
srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) {
low = true
srv.Shutdown()
})
srv.RegStartFinishEvent(func(srv *server.Server) {
if c.duration <= 0 {
srv.Shutdown()
return
}
time.Sleep(server.DefaultLowMessageDuration)
})
var lis string
switch network {
case server.NetworkNone, server.NetworkUnix:
lis = "addr"
default:
lis = fmt.Sprintf(":%d", random.UsablePort())
}
if err := srv.Run(lis); err != nil {
t.Fatalf("%s run error: %s", network, err)
}
if !low && c.duration > 0 {
t.Fatalf("%s low message not exec", network)
}
}
})
}
}
func TestWithAsyncLowMessageDuration(t *testing.T) {
var cases = []struct {
name string
duration time.Duration
}{
{name: "TestWithAsyncLowMessageDuration", duration: time.Millisecond * 100},
{name: "TestWithAsyncLowMessageDuration_Zero", duration: 0},
{name: "TestWithAsyncLowMessageDuration_Negative", duration: -server.DefaultAsyncLowMessageDuration},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
networks := server.GetNetworks()
for i := 0; i < len(networks); i++ {
low := false
network := networks[i]
srv := server.New(network,
server.WithAsyncLowMessageDuration(c.duration),
)
srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) {
low = true
srv.Shutdown()
})
srv.RegStartFinishEvent(func(srv *server.Server) {
if c.duration <= 0 {
srv.Shutdown()
return
}
srv.PushAsyncMessage(func() error {
time.Sleep(c.duration)
return nil
}, nil)
})
var lis string
switch network {
case server.NetworkNone, server.NetworkUnix:
lis = fmt.Sprintf("%s%d", "addr", random.Int(0, 9999))
default:
lis = fmt.Sprintf(":%d", random.UsablePort())
}
if err := srv.Run(lis); err != nil {
t.Fatalf("%s run error: %s", network, err)
}
if !low && c.duration > 0 {
t.Fatalf("%s low message not exec", network)
}
}
})
}
}

View File

@ -149,7 +149,7 @@ func (srv *Server) Run(addr string) (err error) {
return nil
}
// IsSocket 是否是 Socket 模式
// IsSocket 通过执行 Network.IsSocket 函数检查该服务器是否是 Socket 模式
func (srv *Server) IsSocket() bool {
return srv.network.IsSocket()
}

View File

@ -1,30 +1,53 @@
package server_test
import (
"fmt"
"github.com/kercylan98/minotaur/server"
"time"
)
// 该案例将创建一个简单的 WebSocket 服务器,如果需要更多的服务器类型可参考 [` Network `](#struct_Network) 部分
// - server.WithLimitLife(time.Millisecond) 通常不是在正常开发应该使用的,在这里只是为了让服务器在启动完成后的 1 毫秒后自动关闭
//
// 该案例的输出结果为 true
func ExampleNew() {
srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond))
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
conn.Write(packet)
})
if err := srv.Run(":9999"); err != nil {
panic(err)
}
fmt.Println(srv != nil)
// Output:
// true
}
// 该案例将创建两个不同类型的服务器,其中 WebSocket 是一个 Socket 服务器,而 Http 是一个非 Socket 服务器
//
// 可知案例输出结果为:
// - true
// - false
func ExampleServer_IsSocket() {
srv1 := server.New(server.NetworkWebsocket)
fmt.Println(srv1.IsSocket())
srv2 := server.New(server.NetworkHttp)
fmt.Println(srv2.IsSocket())
// Output:
// true
// false
}
// 该案例将创建一个简单的 WebSocket 服务器并启动监听 `:9999/` 作为 WebSocket 监听地址,如果需要更多的服务器类型可参考 [` Network `](#struct_Network) 部分
// - 当服务器启动失败后,将会返回错误信息并触发 panic
// - server.WithLimitLife(time.Millisecond) 通常不是在正常开发应该使用的,在这里只是为了让服务器在启动完成后的 1 毫秒后自动关闭
func ExampleServer_Run() {
srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond))
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
conn.Write(packet)
})
if err := srv.Run(":9999"); err != nil {
panic(err)
}
// Output:
}
// RunNone 函数并没有特殊的意义,该函数内部调用了 `srv.Run("")` 函数,仅是一个语法糖,用来表示服务器不需要监听任何地址
func ExampleServer_RunNone() {
srv := server.New(server.NetworkNone)
if err := srv.RunNone(); err != nil {
panic(err)
}
// Output:
}

View File

@ -1,81 +1,101 @@
package server_test
import (
"fmt"
"github.com/kercylan98/minotaur/server"
"github.com/kercylan98/minotaur/server/client"
"github.com/kercylan98/minotaur/utils/times"
"github.com/kercylan98/minotaur/utils/super"
"runtime/debug"
"testing"
"time"
)
// 该单元测试用于测试以不同的基本参数创建服务器是否存在异常
func TestNew(t *testing.T) {
srv := server.New(server.NetworkWebsocket, server.WithPProf())
srv.RegStartBeforeEvent(func(srv *server.Server) {
fmt.Println("启动前")
})
srv.RegStartFinishEvent(func(srv *server.Server) {
fmt.Println("启动完成")
})
srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) {
fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount())
})
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
conn.Write(packet)
})
if err := srv.Run(":9999"); err != nil {
panic(err)
var cases = []struct {
name string
network server.Network
addr string
shouldPanic bool
}{
{name: "TestNew_Unknown", addr: "", network: "Unknown", shouldPanic: true},
{name: "TestNew_None", addr: "", network: server.NetworkNone, shouldPanic: false},
{name: "TestNew_None_Addr", addr: "addr", network: server.NetworkNone, shouldPanic: false},
{name: "TestNew_Tcp_AddrEmpty", addr: "", network: server.NetworkTcp, shouldPanic: true},
{name: "TestNew_Tcp_AddrIllegal", addr: "addr", network: server.NetworkTcp, shouldPanic: true},
{name: "TestNew_Tcp_Addr", addr: ":9999", network: server.NetworkTcp, shouldPanic: false},
{name: "TestNew_Tcp4_AddrEmpty", addr: "", network: server.NetworkTcp4, shouldPanic: true},
{name: "TestNew_Tcp4_AddrIllegal", addr: "addr", network: server.NetworkTcp4, shouldPanic: true},
{name: "TestNew_Tcp4_Addr", addr: ":9999", network: server.NetworkTcp4, shouldPanic: false},
{name: "TestNew_Tcp6_AddrEmpty", addr: "", network: server.NetworkTcp6, shouldPanic: true},
{name: "TestNew_Tcp6_AddrIllegal", addr: "addr", network: server.NetworkTcp6, shouldPanic: true},
{name: "TestNew_Tcp6_Addr", addr: ":9999", network: server.NetworkTcp6, shouldPanic: false},
{name: "TestNew_Udp_AddrEmpty", addr: "", network: server.NetworkUdp, shouldPanic: true},
{name: "TestNew_Udp_AddrIllegal", addr: "addr", network: server.NetworkUdp, shouldPanic: true},
{name: "TestNew_Udp_Addr", addr: ":9999", network: server.NetworkUdp, shouldPanic: false},
{name: "TestNew_Udp4_AddrEmpty", addr: "", network: server.NetworkUdp4, shouldPanic: true},
{name: "TestNew_Udp4_AddrIllegal", addr: "addr", network: server.NetworkUdp4, shouldPanic: true},
{name: "TestNew_Udp4_Addr", addr: ":9999", network: server.NetworkUdp4, shouldPanic: false},
{name: "TestNew_Udp6_AddrEmpty", addr: "", network: server.NetworkUdp6, shouldPanic: true},
{name: "TestNew_Udp6_AddrIllegal", addr: "addr", network: server.NetworkUdp6, shouldPanic: true},
{name: "TestNew_Udp6_Addr", addr: ":9999", network: server.NetworkUdp6, shouldPanic: false},
{name: "TestNew_Unix_AddrEmpty", addr: "", network: server.NetworkUnix, shouldPanic: true},
{name: "TestNew_Unix_AddrIllegal", addr: "addr", network: server.NetworkUnix, shouldPanic: true},
{name: "TestNew_Unix_Addr", addr: "addr", network: server.NetworkUnix, shouldPanic: false},
{name: "TestNew_Websocket_AddrEmpty", addr: "", network: server.NetworkWebsocket, shouldPanic: true},
{name: "TestNew_Websocket_AddrIllegal", addr: "addr", network: server.NetworkWebsocket, shouldPanic: true},
{name: "TestNew_Websocket_Addr", addr: ":9999/ws", network: server.NetworkWebsocket, shouldPanic: false},
{name: "TestNew_Http_AddrEmpty", addr: "", network: server.NetworkHttp, shouldPanic: true},
{name: "TestNew_Http_AddrIllegal", addr: "addr", network: server.NetworkHttp, shouldPanic: true},
{name: "TestNew_Http_Addr", addr: ":9999", network: server.NetworkHttp, shouldPanic: false},
{name: "TestNew_Kcp_AddrEmpty", addr: "", network: server.NetworkKcp, shouldPanic: true},
{name: "TestNew_Kcp_AddrIllegal", addr: "addr", network: server.NetworkKcp, shouldPanic: true},
{name: "TestNew_Kcp_Addr", addr: ":9999", network: server.NetworkKcp, shouldPanic: false},
{name: "TestNew_GRPC_AddrEmpty", addr: "", network: server.NetworkGRPC, shouldPanic: true},
{name: "TestNew_GRPC_AddrIllegal", addr: "addr", network: server.NetworkGRPC, shouldPanic: true},
{name: "TestNew_GRPC_Addr", addr: ":9999", network: server.NetworkGRPC, shouldPanic: false},
}
}
func TestNew2(t *testing.T) {
srv := server.New(server.NetworkWebsocket, server.WithPProf())
srv.RegStartBeforeEvent(func(srv *server.Server) {
fmt.Println("启动前")
})
srv.RegStartFinishEvent(func(srv *server.Server) {
fmt.Println("启动完成")
})
srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) {
fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount())
})
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
conn.Write(packet)
})
if err := srv.Run(":9999"); err != nil {
panic(err)
}
}
func TestNewClient(t *testing.T) {
count := 500
for i := 0; i < count; i++ {
fmt.Println("启动", i+1)
cli := client.NewWebsocket("ws://172.29.5.138:9999")
cli.RegConnectionReceivePacketEvent(func(conn *client.Client, wst int, packet []byte) {
fmt.Println(time.Now().Unix(), "收到", string(packet))
})
cli.RegConnectionClosedEvent(func(conn *client.Client, err any) {
fmt.Println("关闭", err)
})
cli.RegConnectionOpenedEvent(func(conn *client.Client) {
go func() {
for i < count {
time.Sleep(time.Second)
}
for {
for i := 0; i < 10; i++ {
cli.WriteWS(2, []byte("hello"))
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer func() {
if err := super.RecoverTransform(recover()); err != nil && !c.shouldPanic {
debug.PrintStack()
t.Fatal("not should panic, err:", err)
}
}()
})
if err := cli.Run(); err != nil {
if err := server.New(c.network, server.WithLimitLife(time.Millisecond*10)).Run(""); err != nil {
panic(err)
}
})
}
}
// 这个测试检查了各个类型的服务器是否为 Socket 模式。如需查看为 Socket 模式的网络类型,请参考 [` Network.IsSocket` ](#struct_Network_IsSocket)
func TestServer_IsSocket(t *testing.T) {
var cases = []struct {
name string
network server.Network
expect bool
}{
{name: "TestServer_IsSocket_None", network: server.NetworkNone, expect: false},
{name: "TestServer_IsSocket_Tcp", network: server.NetworkTcp, expect: true},
{name: "TestServer_IsSocket_Tcp4", network: server.NetworkTcp4, expect: true},
{name: "TestServer_IsSocket_Tcp6", network: server.NetworkTcp6, expect: true},
{name: "TestServer_IsSocket_Udp", network: server.NetworkUdp, expect: true},
{name: "TestServer_IsSocket_Udp4", network: server.NetworkUdp4, expect: true},
{name: "TestServer_IsSocket_Udp6", network: server.NetworkUdp6, expect: true},
{name: "TestServer_IsSocket_Unix", network: server.NetworkUnix, expect: true},
{name: "TestServer_IsSocket_Http", network: server.NetworkHttp, expect: false},
{name: "TestServer_IsSocket_Websocket", network: server.NetworkWebsocket, expect: true},
{name: "TestServer_IsSocket_Kcp", network: server.NetworkKcp, expect: true},
{name: "TestServer_IsSocket_GRPC", network: server.NetworkGRPC, expect: false},
}
time.Sleep(times.Week)
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := server.New(c.network)
if s.IsSocket() != c.expect {
t.Fatalf("expect: %v, got: %v", c.expect, s.IsSocket())
}
})
}
}

View File

@ -5,7 +5,8 @@ import (
"reflect"
)
// Service 兼容传统 service 设计模式的接口
// Service 兼容传统 service 设计模式的接口,通过该接口可以实现更简洁、更具有可读性的服务绑定
// - 在这之前,我们在实现功能上会将 Server 进行全局存储,之后通过 init 函数进行初始化,这样的顺序是不可控的。
type Service interface {
// OnInit 初始化服务,该方法将会在 Server 初始化时执行
// - 通常来说,该阶段发生任何错误都应该 panic 以阻止 Server 启动

View File

@ -0,0 +1,44 @@
package server_test
import (
"github.com/kercylan98/minotaur/server"
"time"
)
// 这个案例中我们将 `TestService` 绑定到了 `srv` 服务器中,当服务器启动时,将会对 `TestService` 进行初始化
//
// 其中 `TestService` 的定义如下:
// ```go
//
// type TestService struct{}
//
// func (ts *TestService) OnInit(srv *server.Server) {
// srv.RegStartFinishEvent(onStartFinish)
//
// srv.RegStopEvent(func(srv *server.Server) {
// fmt.Println("server stop")
// })
// }
//
// func (ts *TestService) onStartFinish(srv *server.Server) {
// fmt.Println("server start finish")
// }
//
// ```
//
// 可以看出,在服务初始化时,该服务向服务器注册了启动完成事件及停止事件。这是我们推荐的编码方式,这样编码有以下好处:
// - 具备可控制的初始化顺序,避免 init 产生的各种顺序导致的问题,如配置还未加载完成,即开始进行数据库连接等操作
// - 可以方便的将不同的服务拆分到不同的包中进行管理
// - 当不需要某个服务时,可以直接删除该服务的绑定,而不需要修改其他代码
// - ...
func ExampleBindService() {
srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second))
server.BindService(srv, new(TestService))
if err := srv.RunNone(); err != nil {
panic(err)
}
// Output:
// server start finish
// server stop
}

View File

@ -20,24 +20,19 @@ func (ts *TestService) OnInit(srv *server.Server) {
}
func TestBindService(t *testing.T) {
srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second))
var cases = []struct {
name string
}{
{name: "TestBindService"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
srv := server.New(server.NetworkNone, server.WithLimitLife(time.Millisecond))
server.BindService(srv, new(TestService))
if err := srv.RunNone(); err != nil {
t.Fatal(err)
}
}
func ExampleBindService() {
srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second))
server.BindService(srv, new(TestService))
if err := srv.RunNone(); err != nil {
panic(err)
})
}
// Output:
// server start finish
// server stop
}

View File

@ -5,7 +5,7 @@ import (
"strings"
)
func newComment(cg *ast.CommentGroup) *Comment {
func newComment(name string, cg *ast.CommentGroup) *Comment {
c := &Comment{}
if cg == nil {
return c
@ -14,9 +14,10 @@ func newComment(cg *ast.CommentGroup) *Comment {
c.Comments = append(c.Comments, comment.Text)
cc := strings.TrimPrefix(strings.Replace(comment.Text, "// ", "//", 1), "//")
if i == 0 {
s := strings.SplitN(cc, " ", 2)
if len(s) == 2 {
cc = s[1]
tsc := strings.TrimSpace(cc)
if strings.HasPrefix(tsc, name) {
s := strings.TrimSpace(strings.TrimPrefix(tsc, name))
cc = s
}
}
c.Clear = append(c.Clear, cc)

View File

@ -10,7 +10,7 @@ func newField(field *ast.Field) []*Field {
return []*Field{{
Anonymous: true,
Type: newType(field.Type),
Comments: newComment(field.Comment),
Comments: newComment("", field.Comment),
}}
} else {
var fs []*Field
@ -22,7 +22,7 @@ func newField(field *ast.Field) []*Field {
Anonymous: false,
Name: name.String(),
Type: newType(field.Type),
Comments: newComment(field.Comment),
Comments: newComment(name.String(), field.Comment),
})
}
return fs

View File

@ -16,7 +16,7 @@ func newFile(owner *Package, filePath string) (*File, error) {
af: af,
owner: owner,
FilePath: filePath,
Comment: newComment(af.Doc),
Comment: newComment("Package", af.Doc),
}
for _, decl := range af.Decls {
switch typ := decl.(type) {

View File

@ -12,7 +12,7 @@ func newFunction(astFunc *ast.FuncDecl) *Function {
f := &Function{
decl: astFunc,
Name: astFunc.Name.String(),
Comments: newComment(astFunc.Doc),
Comments: newComment(astFunc.Name.String(), astFunc.Doc),
}
f.IsTest = strings.HasPrefix(f.Name, "Test")
f.IsBenchmark = strings.HasPrefix(f.Name, "Benchmark")

View File

@ -91,7 +91,7 @@ func (p *Package) Structs() []*Struct {
}
func (p *Package) FileComments() *Comment {
var comment = newComment(nil)
var comment = newComment("", nil)
for _, file := range p.Files {
for _, c := range file.Comment.Comments {
comment.Comments = append(comment.Comments, c)

View File

@ -8,7 +8,7 @@ func newStruct(astGen *ast.GenDecl) *Struct {
astTypeSpec := astGen.Specs[0].(*ast.TypeSpec)
s := &Struct{
Name: astTypeSpec.Name.String(),
Comments: newComment(astGen.Doc),
Comments: newComment(astTypeSpec.Name.String(), astGen.Doc),
}
s.Internal = s.Name[0] >= 97 && s.Name[0] <= 122
if astTypeSpec.TypeParams != nil {

View File

@ -288,6 +288,7 @@ func (b *Builder) genStructs() {
if function.Internal || function.Test {
continue
}
b.newLine(fmt.Sprintf(`<span id="struct_%s_%s"></span>`, structInfo.Name, function.Name)).newLine()
b.title(4, strings.TrimSpace(fmt.Sprintf("func (%s%s) %s%s %s",
super.If(function.Struct.Type.IsPointer, "*", ""),
structInfo.Name,
@ -311,6 +312,7 @@ func (b *Builder) genStructs() {
for _, comment := range function.Comments.Clear {
b.quote(comment)
}
b.newLine()
if example := b.p.GetExampleTest(function); example != nil {
b.newLine("**示例代码:**").newLine()
if len(example.Comments.Clear) > 0 {

View File

@ -15,6 +15,20 @@ func Port() int {
return Int(1, 65535)
}
// UsablePort 随机返回一个可用的端口号,如果没有可用端口号则返回 -1
func UsablePort() int {
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
if err != nil {
return -1
}
cli, err := net.ListenTCP("tcp", addr)
if err != nil {
return -1
}
defer func() { _ = cli.Close() }()
return cli.Addr().(*net.TCPAddr).Port
}
// IPv4 返回一个随机产生的IPv4地址。
func IPv4() string {
return fmt.Sprintf("%d.%d.%d.%d", Int(1, 255), Int(0, 255), Int(0, 255), Int(0, 255))