go_dreamfactory/cmd/robot/robot.go
2022-09-08 15:49:17 +08:00

486 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package robot
import (
"bytes"
"encoding/json"
"fmt"
"go_dreamfactory/comm"
"io"
"os"
// zlog "go_dreamfactory/lego/sys/log"
"go_dreamfactory/modules/user"
"go_dreamfactory/pb"
"io/ioutil"
"net/http"
"time"
"github.com/Pallinder/go-randomdata"
"github.com/gorilla/websocket"
jsoniter "github.com/json-iterator/go"
uuid "github.com/satori/go.uuid"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
)
type Robot struct {
ws *websocket.Conn
opts *Options
user *pb.DBUser
builderMap map[string]*TestCase
// linkCase *LinkCase
enable bool //全局开关
reqCh chan string
printFormat bool //是否格式化结果
caseTotal int32 //测试数量
caseSuccess int32 //成功数量
caseError int32 //失败数量
start time.Time //启动时间
}
var zlog *logrus.Logger
func initlog() {
zlog = logrus.New()
zlog.SetLevel(logrus.DebugLevel)
zlog.SetFormatter(&RobotFormatter{
DisableTimestamp: true,
DisableQuote: true,
})
//设置output,默认为stderr,可以为任何io.Writer比如文件*os.File
file, err := os.OpenFile("robot.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
writers := []io.Writer{
file,
os.Stdout}
//同时写文件和屏幕
fileAndStdoutWriter := io.MultiWriter(writers...)
if err == nil {
zlog.SetOutput(fileAndStdoutWriter)
} else {
zlog.Info("failed to log to file.")
}
}
func NewRobot(opts *Options) *Robot {
initlog()
ws, _, err := websocket.DefaultDialer.Dial(opts.WsUrl, nil)
if err != nil {
zlog.Fatalf("websocket conn err:%v", err)
}
r := &Robot{
ws: ws,
opts: opts,
builderMap: make(map[string]*TestCase),
reqCh: make(chan string, 1),
printFormat: false,
}
return r
}
func (r *Robot) Run() {
zlog.Debug("Robot running...")
zlog.Infof("websocket %s ", r.opts.WsUrl)
if r.opts.Create { //创建新用户
r.enable = true
r.AccountRegister(r.opts.Account, r.opts.ServerId)
} else {
if r.opts.Account == "" {
zlog.Fatal("WARNNING: account is required !!!")
}
r.enable = r.opts.Role
r.AccountLogin()
}
// ticker := time.NewTicker(time.Second * 5)
// go func() {
// for {
// r.printBuilders()
// <-ticker.C
// if len(r.builderMap) == 0 {
// r.printReport(&TestReport{
// caseTotal: r.caseTotal,
// caseSuccess: r.caseSuccess,
// caseError: r.caseError,
// })
// os.Exit(0)
// }
// }
// }()
// select {}
}
type TestCase struct {
id string //用例ID 如果没有指定,会自动赋值uuid
desc string //用例描述
mainType string //协议类型 L1
subType string //协议类型 L2
req proto.Message //请求类型
rsp proto.Message //响应类型
enabled bool //是否启用
start time.Time //启用时间
hs time.Duration //耗时
print func(rsp proto.Message) //定义打印
next func(robot *Robot, rsp proto.Message) //处理下一层用例请求
}
//添加测试用用例
func (r *Robot) addBuilders(builders []*TestCase) {
for _, b := range builders {
if b.enabled {
if b.id == "" {
uuid := uuid.NewV4().String()
b.id = uuid
r.builderMap[uuid] = b
} else {
r.builderMap[b.id] = b
}
}
}
}
//处理用例,发送请求
func (r *Robot) handleReq() {
for len(r.builderMap) > 0 {
for _, b := range r.builderMap {
if b.enabled && b.req != nil {
// time.Sleep(time.Millisecond * 500)
b.start = time.Now()
r.start = time.Now()
head := &pb.UserMessage{MainType: b.mainType, SubType: b.subType}
// defer traceFunc(head.MainType, head.SubType, r.user.GetUid(), b.req)
err := r.SendToClient(head, b.req)
if err != nil {
delete(r.builderMap, b.id)
zlog.Errorf("send to client err:%v", err)
}
r.handleRsp(b.id)
}
}
}
}
//加入用例并执行请求
func (r *Robot) addTestCaseAndReq(tcs []*TestCase) {
r.addBuilders(tcs)
r.handleReq()
}
//错误通知处理
func (r *Robot) handleNotify(uuid string, msg *pb.UserMessage) {
if msg.MainType == "notify" && msg.SubType == "errornotify" {
r.caseError++
logrus.Debug("errornotify %v", r.caseError)
rsp := &pb.NotifyErrorNotifyPush{}
if !comm.ProtoUnmarshal(msg, rsp) {
return
}
tc := &TestCase{
id: uuid,
desc: "错误通知",
mainType: comm.MainTypeNotify,
subType: comm.SubTypeErrorNotify,
req: rsp.Arg,
rsp: rsp,
}
r.printReply(msg, tc)
delete(r.builderMap, uuid)
}
}
//处理响应
func (r *Robot) handleRsp(id string) {
defer func() {
r.caseTotal++
}()
var msg *pb.UserMessage = &pb.UserMessage{}
_, data, err := r.ws.ReadMessage()
if err != nil {
zlog.Fatalf("readMessage err:%v", err)
}
if err = proto.Unmarshal(data, msg); err != nil {
zlog.Fatalf("unmarshal err:%v", err)
}
uuid, _ := r.findTestCase(msg)
if uuid == "" {
uuid = id
}
r.handleNotify(uuid, msg)
if v, ok := r.builderMap[uuid]; ok {
if v.enabled &&
(msg.MainType == v.mainType &&
msg.SubType == v.subType) {
v.hs = time.Since(v.start)
if !comm.ProtoUnmarshal(msg, v.rsp) {
return
}
//处理下一层用例
if v.next != nil {
v.next(r, v.rsp)
}
//执行自定义打印
if v.print == nil {
r.printReply(msg, v)
} else {
zlog.Debug("==============================")
zlog.Debugf("%s [%s.%s]", v.desc, msg.MainType, msg.SubType)
v.print(v.rsp)
zlog.Debug("==============================")
}
if msg.MainType == "user" && msg.SubType == "login" {
r.loginCallback(v.rsp)
} else {
//清除已执行的用例
delete(r.builderMap, v.id)
}
r.caseSuccess++
}
}
}
func (r *Robot) findTestCase(msg *pb.UserMessage) (string, bool) {
for k, v := range r.builderMap {
if v.mainType == msg.MainType && v.subType == msg.SubType {
return k, true
}
}
return "", false
}
//登录回调
func (r *Robot) loginCallback(rsp proto.Message) {
//清除登录用例
delete(r.builderMap, "login")
lr := rsp.(*pb.UserLoginResp)
if lr.Data != nil {
r.user = lr.Data
r.onUserLoaded()
r.handleReq()
} else {
//请求Http接口模拟创建新账号
r.AccountRegister(r.opts.Account, r.opts.ServerId)
}
}
//发送消息
func (r *Robot) SendToClient(msg *pb.UserMessage, rsp proto.Message) error {
//模拟客户端 每次请求都会生成新的秘钥
msg.Sec = r.BuildSecStr()
if comm.ProtoMarshal(rsp, msg) {
data, _ := proto.Marshal(msg)
return r.ws.WriteMessage(websocket.BinaryMessage, data)
}
return nil
}
//注册账号
func (r *Robot) AccountRegister(account string, sid string) {
if account == "" {
zlog.Fatal("account value is empty")
}
//http
regReq := &pb.UserRegisterReq{Account: account, Sid: sid}
jsonByte, _ := json.Marshal(regReq)
req, err := http.NewRequest("POST", r.opts.RegUrl, bytes.NewReader(jsonByte))
if err != nil {
zlog.Fatalf("account register err %v", err)
}
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
httpClient := &http.Client{}
rsp, err := httpClient.Do(req)
if err != nil {
panic(err)
}
defer rsp.Body.Close()
body, _ := ioutil.ReadAll(rsp.Body)
regRsp := &pb.UserRegisterResp{}
err = jsoniter.Unmarshal(body, regRsp)
if regRsp.Code == pb.ErrorCode_Success { //注册成功
fmt.Printf("account:%s 注册成功", regRsp.Account)
//登录
var user_builders = []*TestCase{
{
id: "login",
desc: "登录",
mainType: "user",
subType: "login",
req: &pb.UserLoginReq{
Account: account,
Sid: sid,
},
rsp: &pb.UserLoginResp{},
enabled: true,
next: func(r *Robot, rsp proto.Message) {
tcs := []*TestCase{}
if _, ok := rsp.(*pb.UserLoginResp); ok {
nick := randomdata.SillyName()
tc := &TestCase{
desc: "创角",
mainType: string(comm.ModuleUser),
subType: user.UserSubTypeCreate,
req: &pb.UserCreateReq{ //设置请求参数
NickName: nick,
},
rsp: &pb.UserCreateResp{},
enabled: r.enable,
}
tcs = append(tcs, tc)
r.addBuilders(tcs)
}
},
},
}
r.addTestCaseAndReq(user_builders)
}
}
type TestReport struct {
caseTotal int32 //总用例数
caseSuccess int32 //成功数量
caseError int32 //失败数量
hsTotal time.Duration //总耗时
}
//打印响应
func (r *Robot) printReply(msg *pb.UserMessage, tc *TestCase) {
var (
code int32
mainType string
subType string
// arg proto.Message
// rsp proto.Message
// err error
)
if m, ok := tc.rsp.(*pb.NotifyErrorNotifyPush); ok {
code = int32(m.Code)
mainType = m.ReqMainType
subType = m.ReqSubType
// arg, _ = jsoniter.MarshalToString(m.Arg)
} else {
mainType = msg.MainType
subType = msg.SubType
// arg, _ = jsoniter.MarshalToString(tc.req)
}
// if r.printFormat {
// s, err := jsoniter.MarshalToString(tc.rsp)
// if err != nil {
// zlog.Errorf("MarshalToString err:%v", err)
// return
// }
// if rsp, err = formatJson(s); err != nil {
// zlog.Errorf("formatJson err:%v", err)
// return
// }
// } else {
// if rsp, err = jsoniter.MarshalToString(tc.rsp); err != nil {
// zlog.Errorf("MarshalToString err:%v", err)
// return
// }
// }
//表格显示
// data = append(data, []string{builder.desc, fmt.Sprintf("%s.%s", mainType, subType), "0", arg, cast.ToString(code)})
// table.SetHeader([]string{"描述", "协议", "耗时", "参数", "响应码"})
// for _, v := range data {
// table.Append(v)
// }
// table.Render()
//
zlog.Debug("-------------------------------------")
// zlog.Debugf("uuid%s", tc.id)
zlog.Debugf("描述:%s", tc.desc)
zlog.Debugf("协议:%s.%s", mainType, subType)
zlog.Debugf("耗时:%v", tc.hs)
zlog.Debugf("参数:%v", tc.req)
zlog.Debugf("响应码:%d", code)
zlog.Debugf("返回:%v", tc.rsp)
}
//打印测试报告
func (r *Robot) printReport(report *TestReport) {
zlog.Debug("====================================")
zlog.Infof("测试完毕,开始输出报告")
zlog.Infof("用例总数:%d", r.caseTotal)
zlog.Infof("成功:%d", r.caseSuccess)
zlog.Infof("失败:%d", r.caseError)
zlog.Infof("总耗时: %v", time.Since(r.start))
}
//格式化json
func formatJson(data string) (string, error) {
var out bytes.Buffer
err := json.Indent(&out, []byte(data), "", " ")
return out.String(), err
}
//方法参数跟踪
func traceFunc(module string, funcName string, uid string, funcArgs interface{}) {
zlog.Debugf("req [%s.%s] [%s] [%v]", module, funcName, uid, funcArgs)
}
//在这里添加玩家成功登录以后的测试方法
//次方法在用户登录成功后调用
func (r *Robot) onUserLoaded() {
//user
r.RunUser()
//hero
r.RunHero()
//friend
r.RunFriend()
//pack
// r.RunPack()
//task
r.RunTask()
// story
r.RunMainline()
}
type RobotFormatter struct {
DisableTimestamp bool
DisableQuote bool
}
func (r *RobotFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = &bytes.Buffer{}
}
var timestamp string
var newLog string
if !r.DisableTimestamp {
timestamp = entry.Time.Format("2006-01-02 15:04:05")
newLog = fmt.Sprintf("[%s] %s\n", timestamp, entry.Message)
} else {
newLog = fmt.Sprintf("%s\n", entry.Message)
}
b.WriteString(newLog)
return b.Bytes(), nil
}