486 lines
11 KiB
Go
486 lines
11 KiB
Go
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
|
||
}
|