commit
a0a87b8520
@ -1,48 +0,0 @@ |
||||
load( |
||||
"@io_bazel_rules_go//go:def.bzl", |
||||
"go_library", |
||||
) |
||||
load( |
||||
"@io_bazel_rules_go//proto:def.bzl", |
||||
"go_proto_library", |
||||
) |
||||
|
||||
go_library( |
||||
name = "go_default_library", |
||||
srcs = [], |
||||
embed = [":proto_go_proto"], |
||||
importpath = "go-common/library/cache/memcache/test", |
||||
tags = ["automanaged"], |
||||
visibility = ["//visibility:public"], |
||||
deps = ["@com_github_golang_protobuf//proto:go_default_library"], |
||||
) |
||||
|
||||
filegroup( |
||||
name = "package-srcs", |
||||
srcs = glob(["**"]), |
||||
tags = ["automanaged"], |
||||
visibility = ["//visibility:private"], |
||||
) |
||||
|
||||
filegroup( |
||||
name = "all-srcs", |
||||
srcs = [":package-srcs"], |
||||
tags = ["automanaged"], |
||||
visibility = ["//visibility:public"], |
||||
) |
||||
|
||||
proto_library( |
||||
name = "test_proto", |
||||
srcs = ["test.proto"], |
||||
import_prefix = "go-common/library/cache/memcache/test", |
||||
strip_import_prefix = "", |
||||
tags = ["automanaged"], |
||||
) |
||||
|
||||
go_proto_library( |
||||
name = "proto_go_proto", |
||||
compilers = ["@io_bazel_rules_go//proto:go_proto"], |
||||
importpath = "go-common/library/cache/memcache/test", |
||||
proto = ":test_proto", |
||||
tags = ["automanaged"], |
||||
) |
@ -0,0 +1,9 @@ |
||||
version: "3.7" |
||||
|
||||
services: |
||||
mc: |
||||
image: memcached:1 |
||||
ports: |
||||
- 11211:11211 |
||||
|
||||
|
@ -0,0 +1,4 @@ |
||||
## testing/lich 运行环境构建 |
||||
基于 docker-compose 实现跨平台跨语言环境的容器依赖管理方案,以解决运行ut场景下的 (mysql, redis, mc)容器依赖问题。 |
||||
|
||||
使用说明参见:https://github.com/bilibili/kratos/tree/master/tool/testcli/README.md |
@ -0,0 +1,125 @@ |
||||
package lich |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/md5" |
||||
"encoding/json" |
||||
"flag" |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"time" |
||||
|
||||
"github.com/bilibili/kratos/pkg/log" |
||||
) |
||||
|
||||
var ( |
||||
retry int |
||||
noDown bool |
||||
yamlPath string |
||||
pathHash string |
||||
services map[string]*Container |
||||
) |
||||
|
||||
func init() { |
||||
flag.StringVar(&yamlPath, "f", "docker-compose.yaml", "composer yaml path.") |
||||
flag.BoolVar(&noDown, "nodown", false, "containers are not recycled.") |
||||
} |
||||
|
||||
func runCompose(args ...string) (output []byte, err error) { |
||||
if _, err = os.Stat(yamlPath); os.IsNotExist(err) { |
||||
log.Error("os.Stat(%s) composer yaml is not exist!", yamlPath) |
||||
return |
||||
} |
||||
if yamlPath, err = filepath.Abs(yamlPath); err != nil { |
||||
log.Error("filepath.Abs(%s) error(%v)", yamlPath, err) |
||||
return |
||||
} |
||||
pathHash = fmt.Sprintf("%x", md5.Sum([]byte(yamlPath)))[:9] |
||||
args = append([]string{"-f", yamlPath, "-p", pathHash}, args...) |
||||
if output, err = exec.Command("docker-compose", args...).CombinedOutput(); err != nil { |
||||
log.Error("exec.Command(docker-compose) args(%v) stdout(%s) error(%v)", args, string(output), err) |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Setup setup UT related environment dependence for everything.
|
||||
func Setup() (err error) { |
||||
if _, err = runCompose("up", "-d"); err != nil { |
||||
return |
||||
} |
||||
defer func() { |
||||
if err != err { |
||||
go Teardown() |
||||
} |
||||
}() |
||||
if _, err = getServices(); err != nil { |
||||
return |
||||
} |
||||
_, err = checkServices() |
||||
return |
||||
} |
||||
|
||||
// Teardown unsetup all environment dependence.
|
||||
func Teardown() (err error) { |
||||
if !noDown { |
||||
_, err = runCompose("down") |
||||
} |
||||
return |
||||
} |
||||
|
||||
func getServices() (output []byte, err error) { |
||||
if output, err = runCompose("config", "--services"); err != nil { |
||||
return |
||||
} |
||||
services = make(map[string]*Container) |
||||
output = bytes.TrimSpace(output) |
||||
for _, svr := range bytes.Split(output, []byte("\n")) { |
||||
if output, err = runCompose("ps", "-a", "-q", string(svr)); err != nil { |
||||
return |
||||
} |
||||
var ( |
||||
id = string(bytes.TrimSpace(output)) |
||||
args = []string{"inspect", id, "--format", "'{{json .}}'"} |
||||
) |
||||
if output, err = exec.Command("docker", args...).CombinedOutput(); err != nil { |
||||
log.Error("exec.Command(docker) args(%v) stdout(%s) error(%v)", args, string(output), err) |
||||
return |
||||
} |
||||
if output = bytes.TrimSpace(output); bytes.Equal(output, []byte("")) { |
||||
err = fmt.Errorf("service: %s | container: %s fails to launch", svr, id) |
||||
log.Error("exec.Command(docker) args(%v) error(%v)", args, err) |
||||
return |
||||
} |
||||
var c = &Container{} |
||||
if err = json.Unmarshal(bytes.Trim(output, "'"), c); err != nil { |
||||
log.Error("json.Unmarshal(%s) error(%v)", string(output), err) |
||||
return |
||||
} |
||||
services[string(svr)] = c |
||||
} |
||||
return |
||||
} |
||||
|
||||
func checkServices() (output []byte, err error) { |
||||
defer func() { |
||||
if err != nil && retry < 4 { |
||||
retry++ |
||||
getServices() |
||||
time.Sleep(time.Second * 5) |
||||
output, err = checkServices() |
||||
return |
||||
} |
||||
retry = 0 |
||||
}() |
||||
for svr, c := range services { |
||||
if err = c.Healthcheck(); err != nil { |
||||
log.Error("healthcheck(%s) error(%v) retrying %d times...", svr, err, 5-retry) |
||||
return |
||||
} |
||||
// TODO About container check and more...
|
||||
} |
||||
return |
||||
} |
@ -0,0 +1,85 @@ |
||||
package lich |
||||
|
||||
import ( |
||||
"database/sql" |
||||
"fmt" |
||||
"net" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"github.com/bilibili/kratos/pkg/log" |
||||
// Register go-sql-driver stuff
|
||||
_ "github.com/go-sql-driver/mysql" |
||||
) |
||||
|
||||
var healthchecks = map[string]func(*Container) error{"mysql": checkMysql, "mariadb": checkMysql} |
||||
|
||||
// Healthcheck check container health.
|
||||
func (c *Container) Healthcheck() (err error) { |
||||
if status, health := c.State.Status, c.State.Health.Status; !c.State.Running || (health != "" && health != "healthy") { |
||||
err = fmt.Errorf("service: %s | container: %s not running", c.GetImage(), c.GetID()) |
||||
log.Error("docker status(%s) health(%s) error(%v)", status, health, err) |
||||
return |
||||
} |
||||
if check, ok := healthchecks[c.GetImage()]; ok { |
||||
err = check(c) |
||||
return |
||||
} |
||||
for proto, ports := range c.NetworkSettings.Ports { |
||||
if id := c.GetID(); !strings.Contains(proto, "tcp") { |
||||
log.Error("container: %s proto(%s) unsupported.", id, proto) |
||||
continue |
||||
} |
||||
for _, publish := range ports { |
||||
var ( |
||||
ip = net.ParseIP(publish.HostIP) |
||||
port, _ = strconv.Atoi(publish.HostPort) |
||||
tcpAddr = &net.TCPAddr{IP: ip, Port: port} |
||||
tcpConn *net.TCPConn |
||||
) |
||||
if tcpConn, err = net.DialTCP("tcp", nil, tcpAddr); err != nil { |
||||
log.Error("net.DialTCP(%s:%s) error(%v)", publish.HostIP, publish.HostPort, err) |
||||
return |
||||
} |
||||
tcpConn.Close() |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func checkMysql(c *Container) (err error) { |
||||
var ip, port, user, passwd string |
||||
for _, env := range c.Config.Env { |
||||
splits := strings.Split(env, "=") |
||||
if strings.Contains(splits[0], "MYSQL_ROOT_PASSWORD") { |
||||
user, passwd = "root", splits[1] |
||||
continue |
||||
} |
||||
if strings.Contains(splits[0], "MYSQL_ALLOW_EMPTY_PASSWORD") { |
||||
user, passwd = "root", "" |
||||
continue |
||||
} |
||||
if strings.Contains(splits[0], "MYSQL_USER") { |
||||
user = splits[1] |
||||
continue |
||||
} |
||||
if strings.Contains(splits[0], "MYSQL_PASSWORD") { |
||||
passwd = splits[1] |
||||
continue |
||||
} |
||||
} |
||||
var db *sql.DB |
||||
if ports, ok := c.NetworkSettings.Ports["3306/tcp"]; ok { |
||||
ip, port = ports[0].HostIP, ports[0].HostPort |
||||
} |
||||
var dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/", user, passwd, ip, port) |
||||
if db, err = sql.Open("mysql", dsn); err != nil { |
||||
log.Error("sql.Open(mysql) dsn(%s) error(%v)", dsn, err) |
||||
return |
||||
} |
||||
if err = db.Ping(); err != nil { |
||||
log.Error("ping(db) dsn(%s) error(%v)", dsn, err) |
||||
} |
||||
defer db.Close() |
||||
return |
||||
} |
@ -0,0 +1,88 @@ |
||||
package lich |
||||
|
||||
import ( |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// Container docker inspect resp.
|
||||
type Container struct { |
||||
ID string `json:"Id"` |
||||
Created time.Time `json:"Created"` |
||||
Path string `json:"Path"` |
||||
Args []string `json:"Args"` |
||||
State struct { |
||||
Status string `json:"Status"` |
||||
Running bool `json:"Running"` |
||||
Paused bool `json:"Paused"` |
||||
Restarting bool `json:"Restarting"` |
||||
OOMKilled bool `json:"OOMKilled"` |
||||
Dead bool `json:"Dead"` |
||||
Pid int `json:"Pid"` |
||||
ExitCode int `json:"ExitCode"` |
||||
Error string `json:"Error"` |
||||
StartedAt time.Time `json:"StartedAt"` |
||||
FinishedAt time.Time `json:"FinishedAt"` |
||||
Health struct { |
||||
Status string `json:"Status"` |
||||
FailingStreak int `json:"FailingStreak"` |
||||
Log []struct { |
||||
Start time.Time `json:"Start"` |
||||
End time.Time `json:"End"` |
||||
ExitCode int `json:"ExitCode"` |
||||
Output string `json:"Output"` |
||||
} `json:"Log"` |
||||
} `json:"Health"` |
||||
} `json:"State"` |
||||
Config struct { |
||||
Hostname string `json:"Hostname"` |
||||
Domainname string `json:"Domainname"` |
||||
User string `json:"User"` |
||||
Tty bool `json:"Tty"` |
||||
OpenStdin bool `json:"OpenStdin"` |
||||
StdinOnce bool `json:"StdinOnce"` |
||||
Env []string `json:"Env"` |
||||
Cmd []string `json:"Cmd"` |
||||
Image string `json:"Image"` |
||||
WorkingDir string `json:"WorkingDir"` |
||||
Entrypoint []string `json:"Entrypoint"` |
||||
} `json:"Config"` |
||||
Image string `json:"Image"` |
||||
ResolvConfPath string `json:"ResolvConfPath"` |
||||
HostnamePath string `json:"HostnamePath"` |
||||
HostsPath string `json:"HostsPath"` |
||||
LogPath string `json:"LogPath"` |
||||
Name string `json:"Name"` |
||||
RestartCount int `json:"RestartCount"` |
||||
Driver string `json:"Driver"` |
||||
Platform string `json:"Platform"` |
||||
MountLabel string `json:"MountLabel"` |
||||
ProcessLabel string `json:"ProcessLabel"` |
||||
AppArmorProfile string `json:"AppArmorProfile"` |
||||
NetworkSettings struct { |
||||
Bridge string `json:"Bridge"` |
||||
SandboxID string `json:"SandboxID"` |
||||
HairpinMode bool `json:"HairpinMode"` |
||||
Ports map[string][]struct { |
||||
HostIP string `json:"HostIp"` |
||||
HostPort string `json:"HostPort"` |
||||
} `json:"Ports"` |
||||
} `json:"NetworkSettings"` |
||||
} |
||||
|
||||
// GetImage get image name at container
|
||||
func (c *Container) GetImage() (image string) { |
||||
image = c.Config.Image |
||||
if images := strings.Split(image, ":"); len(images) > 0 { |
||||
image = images[0] |
||||
} |
||||
return |
||||
} |
||||
|
||||
// GetID get id at container
|
||||
func (c *Container) GetID() (id string) { |
||||
if id = c.ID; len(id) > 9 { |
||||
id = id[0:9] |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,154 @@ |
||||
## testcli UT运行环境构建工具 |
||||
基于 docker-compose 实现跨平台跨语言环境的容器依赖管理方案,以解决运行ut场景下的 (mysql, redis, mc)容器依赖问题。 |
||||
|
||||
*这个是testing/lich的二进制工具版本(Go请直接使用库版本:github.com/bilibili/kratos/pkg/testing/lich)* |
||||
|
||||
### 功能和特性 |
||||
- 自动读取 test 目录下的 yaml 并启动依赖 |
||||
- 自动导入 test 目录下的 DB 初始化 SQL |
||||
- 提供特定容器内的 healthcheck (mysql, mc, redis) |
||||
- 提供一站式解决 UT 服务依赖的工具版本 (testcli) |
||||
|
||||
### 编译安装 |
||||
*使用本工具/库需要前置安装好 docker & docker-compose@v1.24.1^* |
||||
|
||||
#### Method 1. With go get |
||||
```shell |
||||
go get -u github.com/bilibili/kratos/tool/testcli |
||||
$GOPATH/bin/testcli -h |
||||
``` |
||||
#### Method 2. Build with Go |
||||
```shell |
||||
cd github.com/bilibili/kratos/tool/testcli |
||||
go build -o $GOPATH/bin/testcli |
||||
$GOPATH/bin/testcli -h |
||||
``` |
||||
#### Method 3. Import with Kratos pkg |
||||
```Go |
||||
import "github.com/bilibili/kratos/pkg/testing/lich" |
||||
``` |
||||
|
||||
### 构建数据 |
||||
#### Step 1. create docker-compose.yml |
||||
创建依赖服务的 docker-compose.yml,并把它放在项目路径下的 test 文件夹下面。例如: |
||||
```shell |
||||
mkdir -p $YOUR_PROJECT/test |
||||
``` |
||||
```yaml |
||||
version: "3.7" |
||||
|
||||
services: |
||||
db: |
||||
image: mysql:5.6 |
||||
ports: |
||||
- 3306:3306 |
||||
environment: |
||||
- MYSQL_ROOT_PASSWORD=root |
||||
volumes: |
||||
- .:/docker-entrypoint-initdb.d |
||||
command: [ |
||||
'--character-set-server=utf8', |
||||
'--collation-server=utf8_unicode_ci' |
||||
] |
||||
|
||||
redis: |
||||
image: redis |
||||
ports: |
||||
- 6379:6379 |
||||
``` |
||||
一般来讲,我们推荐在项目根目录创建 test 目录,里面存放描述服务的yml,以及需要初始化的数据(database.sql等)。 |
||||
|
||||
同时也需要注意,正确的对容器内服务进行健康检测,testcli会在容器的health状态执行UT,其实我们也内置了针对几个较为通用镜像(mysql mariadb mc redis)的健康检测,也就是不写也没事(^^;; |
||||
|
||||
#### Step 2. export database.sql |
||||
构造初始化的数据(database.sql等),当然也把它也在 test 文件夹里。 |
||||
```sql |
||||
CREATE DATABASE IF NOT EXISTS `YOUR_DATABASE_NAME`; |
||||
|
||||
SET NAMES 'utf8'; |
||||
USE `YOUR_DATABASE_NAME`; |
||||
|
||||
CREATE TABLE IF NOT EXISTS `YOUR_TABLE_NAME` ( |
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', |
||||
PRIMARY KEY (`id`), |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='YOUR_TABLE_NAME'; |
||||
``` |
||||
这里需要注意,在创建库/表的时候尽量加上 IF NOT EXISTS,以给予一定程度的容错,以及 SET NAMES 'utf8'; 用于解决客户端连接乱码问题。 |
||||
|
||||
#### Step 3. change your project mysql config |
||||
```toml |
||||
[mysql] |
||||
addr = "127.0.0.1:3306" |
||||
dsn = "root:root@tcp(127.0.0.1:3306)/YOUR_DATABASE?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8mb4,utf8" |
||||
active = 20 |
||||
idle = 10 |
||||
idleTimeout ="1s" |
||||
queryTimeout = "1s" |
||||
execTimeout = "1s" |
||||
tranTimeout = "1s" |
||||
``` |
||||
在 *Step 1* 我们已经指定了服务对外暴露的端口为3306(这当然也可以是你指定的任何值),那理所应当的我们也要修改项目连接数据库的配置~ |
||||
|
||||
Great! 至此你已经完成了运行所需要用到的数据配置,接下来就来运行它。 |
||||
|
||||
### 运行 |
||||
开头也说过本工具支持两种运行方式:testcli 二进制工具版本和 go package 源码包,业务方可以根据需求场景进行选择。 |
||||
#### Method 1. With testcli tool |
||||
*已支持的 flag: -f,--nodown,down,run* |
||||
- -f,指定 docker-compose.yaml 文件路径,默认为当前目录下。 |
||||
- --nodown,指定是否在UT执行完成后保留容器,以供下次复用。 |
||||
- down,teardown 销毁当前项目下这个 compose 文件产生的容器。 |
||||
- run,运行你当前语言的单测执行命令(如:golang为 go test -v ./) |
||||
|
||||
example: |
||||
```shell |
||||
testcli -f ../../test/docker-compose.yaml run go test -v ./ |
||||
``` |
||||
#### Method 2. Import with Kratos pkg |
||||
- Step1. 在 Dao|Service 层中的 TestMain 单测主入口中,import "go-common/library/testing/lich" 引入testcli工具的go库版本。 |
||||
- Step2. 使用 flag.Set("f", "../../test/docker-compose.yaml") 指定 docker-compose.yaml 文件的路径。 |
||||
- Step3. 在 flag.Parse() 后即可使用 lich.Setup() 安装依赖&初始化数据(注意测试用例执行结束后 lich.Teardown() 回收下~) |
||||
- Step4. 运行 `go test -v ./ `看看效果吧~ |
||||
|
||||
example: |
||||
```Go |
||||
package dao |
||||
|
||||
|
||||
import ( |
||||
"flag" |
||||
"os" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/bilibili/kratos/pkg/conf/paladin" |
||||
"github.com/bilibili/kratos/pkg/testing/lich" |
||||
) |
||||
|
||||
var ( |
||||
d *Dao |
||||
) |
||||
|
||||
func TestMain(m *testing.M) { |
||||
flag.Set("conf", "../../configs") |
||||
flag.Set("f", "../../test/docker-compose.yaml") |
||||
flag.Parse() |
||||
if err := paladin.Init(); err != nil { |
||||
panic(err) |
||||
} |
||||
if err := lich.Setup(); err != nil { |
||||
panic(err) |
||||
} |
||||
defer lich.Teardown() |
||||
d = New() |
||||
if code := m.Run(); code != 0 { |
||||
panic(code) |
||||
} |
||||
} |
||||
``` |
||||
## 注意 |
||||
因为启动mysql容器较为缓慢,健康检测的机制会重试3次,每次暂留5秒钟,基本在10s内mysql就能从creating到服务正常启动! |
||||
|
||||
当然你也可以在使用 testcli 时加上 --nodown,使其不用每次跑都新建容器,只在第一次跑的时候会初始化容器,后面都进行复用,这样速度会快很多。 |
||||
|
||||
成功启动后就欢乐奔放的玩耍吧~ Good Lucky! |
@ -0,0 +1,26 @@ |
||||
version: "3.7" |
||||
|
||||
services: |
||||
db: |
||||
image: mysql:5.6 |
||||
ports: |
||||
- 3306:3306 |
||||
environment: |
||||
- MYSQL_ROOT_PASSWORD=root |
||||
- TZ=Asia/Shanghai |
||||
volumes: |
||||
- .:/docker-entrypoint-initdb.d |
||||
command: [ |
||||
'--character-set-server=utf8', |
||||
'--collation-server=utf8_unicode_ci' |
||||
] |
||||
|
||||
redis: |
||||
image: redis |
||||
ports: |
||||
- 6379:6379 |
||||
|
||||
memcached: |
||||
image: memcached |
||||
ports: |
||||
- 11211:11211 |
@ -0,0 +1,50 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"flag" |
||||
"os" |
||||
"os/exec" |
||||
"strings" |
||||
|
||||
"github.com/bilibili/kratos/pkg/testing/lich" |
||||
) |
||||
|
||||
func parseArgs() (flags map[string]string) { |
||||
flags = make(map[string]string) |
||||
for idx, arg := range os.Args { |
||||
if idx == 0 { |
||||
continue |
||||
} |
||||
if arg == "down" { |
||||
flags["down"] = "" |
||||
return |
||||
} |
||||
if cmds := os.Args[idx+1:]; arg == "run" { |
||||
flags["run"] = strings.Join(cmds, " ") |
||||
return |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
flags := parseArgs() |
||||
if _, ok := flags["down"]; ok { |
||||
lich.Teardown() |
||||
return |
||||
} |
||||
if cmd, ok := flags["run"]; !ok || cmd == "" { |
||||
panic("Your need 'run' flag assign to be run commands.") |
||||
} |
||||
if err := lich.Setup(); err != nil { |
||||
panic(err) |
||||
} |
||||
defer lich.Teardown() |
||||
cmds := strings.Split(flags["run"], " ") |
||||
cmd := exec.Command(cmds[0], cmds[1:]...) |
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr |
||||
if err := cmd.Run(); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
## testgen UT代码自动生成器 |
||||
解放你的双手,让你的UT一步到位! |
||||
|
||||
### 功能和特性 |
||||
- 支持生成 Dao|Service 层UT代码功能(每个方法包含一个正向用例) |
||||
- 支持生成 Dao|Service 层测试入口文件dao_test.go, service_test.go(用于控制初始化,控制测试流程等) |
||||
- 支持生成Mock代码(使用GoMock框架) |
||||
- 支持选择不同模式生成不同代码(使用"–m mode"指定) |
||||
- 生成单元测试代码时,同时支持传入目录或文件 |
||||
- 支持指定方法追加生成测试用例(使用"–func funcName"指定) |
||||
|
||||
### 编译安装 |
||||
#### Method 1. With go get |
||||
```shell |
||||
go get -u github.com/bilibili/kratos/tool/testgen |
||||
$GOPATH/bin/testgen -h |
||||
``` |
||||
#### Method 2. Build with Go |
||||
```shell |
||||
cd github.com/bilibili/kratos/tool/testgen |
||||
go build -o $GOPATH/bin/testgen |
||||
$GOPATH/bin/testgen -h |
||||
``` |
||||
### 运行 |
||||
#### 生成Dao/Service层单元UT |
||||
```shell |
||||
$GOPATH/bin/testgen YOUR_PROJECT/dao # default mode |
||||
$GOPATH/bin/testgen --m test path/to/your/pkg |
||||
$GOPATH/bin/testgen --func functionName path/to/your/pkg |
||||
``` |
||||
|
||||
#### 生成接口类型 |
||||
```shell |
||||
$GOPATH/bin/testgen --m interface YOUR_PROJECT/dao #当前仅支持传目录,如目录包含子目录也会做处理 |
||||
``` |
||||
|
||||
#### 生成Mock代码 |
||||
```shell |
||||
$GOPATH/bin/testgen --m mock YOUR_PROJECT/dao #仅传入包路径即可 |
||||
``` |
||||
|
||||
#### 生成Monkey代码 |
||||
```shell |
||||
$GOPATH/bin/testgen --m monkey yourCodeDirPath #仅传入包路径即可 |
||||
``` |
||||
### 赋诗一首 |
||||
``` |
||||
莫生气 莫生气 |
||||
代码辣鸡非我意 |
||||
自己动手分田地 |
||||
谈笑风生活长命 |
||||
``` |
@ -0,0 +1,419 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"github.com/otokaze/mock/mockgen" |
||||
"github.com/otokaze/mock/mockgen/model" |
||||
) |
||||
|
||||
func genTest(parses []*parse) (err error) { |
||||
for _, p := range parses { |
||||
switch { |
||||
case strings.HasSuffix(p.Path, "_mock.go") || |
||||
strings.HasSuffix(p.Path, ".intf.go"): |
||||
continue |
||||
case strings.HasSuffix(p.Path, "dao.go") || |
||||
strings.HasSuffix(p.Path, "service.go"): |
||||
err = p.genTestMain() |
||||
default: |
||||
err = p.genUTTest() |
||||
} |
||||
if err != nil { |
||||
break |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (p *parse) genUTTest() (err error) { |
||||
var ( |
||||
buffer bytes.Buffer |
||||
impts = strings.Join([]string{ |
||||
`"context"`, |
||||
`"testing"`, |
||||
`. "github.com/smartystreets/goconvey/convey"`, |
||||
}, "\n\t") |
||||
content []byte |
||||
) |
||||
filename := strings.Replace(p.Path, ".go", "_test.go", -1) |
||||
if _, err = os.Stat(filename); (_func == "" && err == nil) || |
||||
(err != nil && os.IsExist(err)) { |
||||
err = nil |
||||
return |
||||
} |
||||
for _, impt := range p.Imports { |
||||
impts += "\n\t\"" + impt.V + "\"" |
||||
} |
||||
if _func == "" { |
||||
buffer.WriteString(fmt.Sprintf(tpPackage, p.Package)) |
||||
buffer.WriteString(fmt.Sprintf(tpImport, impts)) |
||||
} |
||||
for _, parseFunc := range p.Funcs { |
||||
if _func != "" && _func != parseFunc.Name { |
||||
continue |
||||
} |
||||
var ( |
||||
methodK string |
||||
tpVars string |
||||
vars []string |
||||
val []string |
||||
notice = "Then " |
||||
reset string |
||||
) |
||||
if method := ConvertMethod(p.Path); method != "" { |
||||
methodK = method + "." |
||||
} |
||||
tpTestFuncs := fmt.Sprintf(tpTestFunc, strings.Title(p.Package), parseFunc.Name, "", parseFunc.Name, "%s", "%s", "%s") |
||||
tpTestFuncBeCall := methodK + parseFunc.Name + "(%s)\n\t\t\tConvey(\"%s\", func() {" |
||||
if parseFunc.Result == nil { |
||||
tpTestFuncBeCall = fmt.Sprintf(tpTestFuncBeCall, "%s", "No return values") |
||||
tpTestFuncs = fmt.Sprintf(tpTestFuncs, "%s", tpTestFuncBeCall, "%s") |
||||
} |
||||
for k, res := range parseFunc.Result { |
||||
if res.K == "" { |
||||
res.K = fmt.Sprintf("p%d", k+1) |
||||
} |
||||
var so string |
||||
if res.V == "error" { |
||||
res.K = "err" |
||||
so = fmt.Sprintf("\tSo(%s, ShouldBeNil)", res.K) |
||||
notice += "err should be nil." |
||||
} else { |
||||
so = fmt.Sprintf("\tSo(%s, ShouldNotBeNil)", res.K) |
||||
val = append(val, res.K) |
||||
} |
||||
if len(parseFunc.Result) <= k+1 { |
||||
if len(val) != 0 { |
||||
notice += strings.Join(val, ",") + " should not be nil." |
||||
} |
||||
tpTestFuncBeCall = fmt.Sprintf(tpTestFuncBeCall, "%s", notice) |
||||
res.K += " := " + tpTestFuncBeCall |
||||
} else { |
||||
res.K += ", %s" |
||||
} |
||||
tpTestFuncs = fmt.Sprintf(tpTestFuncs, "%s", res.K+"\n\t\t\t%s", "%s") |
||||
tpTestFuncs = fmt.Sprintf(tpTestFuncs, "%s", "%s", so, "%s") |
||||
} |
||||
if parseFunc.Params == nil { |
||||
tpTestFuncs = fmt.Sprintf(tpTestFuncs, "%s", "", "%s") |
||||
} |
||||
for k, pType := range parseFunc.Params { |
||||
if pType.K == "" { |
||||
pType.K = fmt.Sprintf("a%d", k+1) |
||||
} |
||||
var ( |
||||
init string |
||||
params = pType.K |
||||
) |
||||
switch { |
||||
case strings.HasPrefix(pType.V, "context"): |
||||
init = params + " = context.Background()" |
||||
case strings.HasPrefix(pType.V, "[]byte"): |
||||
init = params + " = " + pType.V + "(\"\")" |
||||
case strings.HasPrefix(pType.V, "[]"): |
||||
init = params + " = " + pType.V + "{}" |
||||
case strings.HasPrefix(pType.V, "int") || |
||||
strings.HasPrefix(pType.V, "uint") || |
||||
strings.HasPrefix(pType.V, "float") || |
||||
strings.HasPrefix(pType.V, "double"): |
||||
init = params + " = " + pType.V + "(0)" |
||||
case strings.HasPrefix(pType.V, "string"): |
||||
init = params + " = \"\"" |
||||
case strings.Contains(pType.V, "*xsql.Tx"): |
||||
init = params + ",_ = " + methodK + "BeginTran(c)" |
||||
reset += "\n\t" + params + ".Commit()" |
||||
case strings.HasPrefix(pType.V, "*"): |
||||
init = params + " = " + strings.Replace(pType.V, "*", "&", -1) + "{}" |
||||
case strings.Contains(pType.V, "chan"): |
||||
init = params + " = " + pType.V |
||||
case pType.V == "time.Time": |
||||
init = params + " = time.Now()" |
||||
case strings.Contains(pType.V, "chan"): |
||||
init = params + " = " + pType.V |
||||
default: |
||||
init = params + " " + pType.V |
||||
} |
||||
vars = append(vars, "\t\t"+init) |
||||
if len(parseFunc.Params) > k+1 { |
||||
params += ", %s" |
||||
} |
||||
tpTestFuncs = fmt.Sprintf(tpTestFuncs, "%s", params, "%s") |
||||
} |
||||
if len(vars) > 0 { |
||||
tpVars = fmt.Sprintf(tpVar, strings.Join(vars, "\n\t")) |
||||
} |
||||
tpTestFuncs = fmt.Sprintf(tpTestFuncs, tpVars, "%s") |
||||
if reset != "" { |
||||
tpTestResets := fmt.Sprintf(tpTestReset, reset) |
||||
tpTestFuncs = fmt.Sprintf(tpTestFuncs, tpTestResets) |
||||
} else { |
||||
tpTestFuncs = fmt.Sprintf(tpTestFuncs, "") |
||||
} |
||||
buffer.WriteString(tpTestFuncs) |
||||
} |
||||
var ( |
||||
file *os.File |
||||
flag = os.O_RDWR | os.O_CREATE | os.O_APPEND |
||||
) |
||||
if file, err = os.OpenFile(filename, flag, 0644); err != nil { |
||||
return |
||||
} |
||||
if _func == "" { |
||||
content, _ = GoImport(filename, buffer.Bytes()) |
||||
} else { |
||||
content = buffer.Bytes() |
||||
} |
||||
if _, err = file.Write(content); err != nil { |
||||
return |
||||
} |
||||
if err = file.Close(); err != nil { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (p *parse) genTestMain() (err error) { |
||||
var ( |
||||
new bool |
||||
buffer bytes.Buffer |
||||
impts string |
||||
vars, mainFunc string |
||||
content []byte |
||||
instance, confFunc string |
||||
tomlPath = "**PUT PATH TO YOUR CONFIG FILES HERE**" |
||||
filename = strings.Replace(p.Path, ".go", "_test.go", -1) |
||||
) |
||||
if p.Imports["paladin"] != nil { |
||||
new = true |
||||
} |
||||
// if _intfMode {
|
||||
// imptsList = append(imptsList, `"github.com/golang/mock/gomock"`)
|
||||
// for _, field := range p.Structs {
|
||||
// var hit bool
|
||||
// pkgName := strings.Split(field.V, ".")[0]
|
||||
// interfaceName := strings.Split(field.V, ".")[1]
|
||||
// if p.Imports[pkgName] != nil {
|
||||
// if hit, err = checkInterfaceMock(strings.Split(field.V, ".")[1], p.Imports[pkgName].V); err != nil {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// if hit {
|
||||
// imptsList = append(imptsList, "mock"+p.Imports[pkgName].K+" \""+p.Imports[pkgName].V+"/mock\"")
|
||||
// pkgName = "mock" + strings.Title(pkgName)
|
||||
// interfaceName = "Mock" + interfaceName
|
||||
// varsList = append(varsList, "mock"+strings.Title(field.K)+" *"+pkgName+"."+interfaceName)
|
||||
// mockStmt += "\tmock" + strings.Title(field.K) + " = " + pkgName + ".New" + interfaceName + "(mockCtrl)\n"
|
||||
// newStmt += "\t\t" + field.K + ":\tmock" + strings.Title(field.K) + ",\n"
|
||||
// } else {
|
||||
// pkgName = subString(field.V, "*", ".")
|
||||
// if p.Imports[pkgName] != nil && pkgName != "conf" {
|
||||
// imptsList = append(imptsList, p.Imports[pkgName].K+" \""+p.Imports[pkgName].V+"\"")
|
||||
// }
|
||||
// switch {
|
||||
// case strings.HasPrefix(field.V, "*conf."):
|
||||
// newStmt += "\t\t" + field.K + ":\tconf.Conf,\n"
|
||||
// case strings.HasPrefix(field.V, "*"):
|
||||
// newStmt += "\t\t" + field.K + ":\t" + strings.Replace(field.V, "*", "&", -1) + "{},\n"
|
||||
// default:
|
||||
// newStmt += "\t\t" + field.K + ":\t" + field.V + ",\n"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// mockStmt = fmt.Sprintf(_tpTestServiceMainMockStmt, mockStmt)
|
||||
// newStmt = fmt.Sprintf(_tpTestServiceMainNewStmt, newStmt)
|
||||
// }
|
||||
if instance = ConvertMethod(p.Path); instance == "s" { |
||||
vars = strings.Join([]string{"s *Service"}, "\n\t") |
||||
mainFunc = tpTestServiceMain |
||||
} else { |
||||
vars = strings.Join([]string{"d *Dao"}, "\n\t") |
||||
mainFunc = tpTestDaoMain |
||||
} |
||||
if new { |
||||
impts = strings.Join([]string{`"os"`, `"flag"`, `"testing"`, p.Imports["paladin"].V}, "\n\t") |
||||
confFunc = fmt.Sprintf(tpTestMainNew, instance+" = New()") |
||||
} else { |
||||
impts = strings.Join(append([]string{`"os"`, `"flag"`, `"testing"`}), "\n\t") |
||||
confFunc = fmt.Sprintf(tpTestMainOld, instance+" = New(conf.Conf)") |
||||
} |
||||
if _, err := os.Stat(filename); os.IsNotExist(err) { |
||||
buffer.WriteString(fmt.Sprintf(tpPackage, p.Package)) |
||||
buffer.WriteString(fmt.Sprintf(tpImport, impts)) |
||||
buffer.WriteString(fmt.Sprintf(tpVar, vars)) |
||||
buffer.WriteString(fmt.Sprintf(mainFunc, tomlPath, confFunc)) |
||||
content, _ = GoImport(filename, buffer.Bytes()) |
||||
ioutil.WriteFile(filename, content, 0644) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func genInterface(parses []*parse) (err error) { |
||||
var ( |
||||
parse *parse |
||||
pkg = make(map[string]string) |
||||
) |
||||
for _, parse = range parses { |
||||
if strings.Contains(parse.Path, ".intf.go") { |
||||
continue |
||||
} |
||||
dirPath := filepath.Dir(parse.Path) |
||||
for _, parseFunc := range parse.Funcs { |
||||
if (parseFunc.Method == nil) || |
||||
!(parseFunc.Name[0] >= 'A' && parseFunc.Name[0] <= 'Z') { |
||||
continue |
||||
} |
||||
var ( |
||||
params string |
||||
results string |
||||
) |
||||
for k, param := range parseFunc.Params { |
||||
params += param.K + " " + param.P + param.V |
||||
if len(parseFunc.Params) > k+1 { |
||||
params += ", " |
||||
} |
||||
} |
||||
for k, res := range parseFunc.Result { |
||||
results += res.K + " " + res.P + res.V |
||||
if len(parseFunc.Result) > k+1 { |
||||
results += ", " |
||||
} |
||||
} |
||||
if len(results) != 0 { |
||||
results = "(" + results + ")" |
||||
} |
||||
pkg[dirPath] += "\t" + fmt.Sprintf(tpIntfcFunc, parseFunc.Name, params, results) |
||||
} |
||||
} |
||||
for k, v := range pkg { |
||||
var buffer bytes.Buffer |
||||
pathSplit := strings.Split(k, "/") |
||||
filename := k + "/" + pathSplit[len(pathSplit)-1] + ".intf.go" |
||||
if _, exist := os.Stat(filename); os.IsExist(exist) { |
||||
continue |
||||
} |
||||
buffer.WriteString(fmt.Sprintf(tpPackage, pathSplit[len(pathSplit)-1])) |
||||
buffer.WriteString(fmt.Sprintf(tpInterface, strings.Title(pathSplit[len(pathSplit)-1]), v)) |
||||
content, _ := GoImport(filename, buffer.Bytes()) |
||||
err = ioutil.WriteFile(filename, content, 0644) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func genMock(files ...string) (err error) { |
||||
for _, file := range files { |
||||
var pkg *model.Package |
||||
if pkg, err = mockgen.ParseFile(file); err != nil { |
||||
return |
||||
} |
||||
if len(pkg.Interfaces) == 0 { |
||||
continue |
||||
} |
||||
var mockDir = pkg.SrcDir + "/mock" |
||||
if _, err = os.Stat(mockDir); os.IsNotExist(err) { |
||||
err = nil |
||||
os.Mkdir(mockDir, 0744) |
||||
} |
||||
var mockPath = mockDir + "/" + pkg.Name + "_mock.go" |
||||
if _, exist := os.Stat(mockPath); os.IsExist(exist) { |
||||
continue |
||||
} |
||||
var g = &mockgen.Generator{Filename: file} |
||||
if err = g.Generate(pkg, "mock", mockPath); err != nil { |
||||
return |
||||
} |
||||
if err = ioutil.WriteFile(mockPath, g.Output(), 0644); err != nil { |
||||
return |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func genMonkey(parses []*parse) (err error) { |
||||
var ( |
||||
pkg = make(map[string]string) |
||||
) |
||||
for _, parse := range parses { |
||||
if strings.Contains(parse.Path, "monkey.go") || |
||||
strings.Contains(parse.Path, "/mock/") { |
||||
continue |
||||
} |
||||
var ( |
||||
path = strings.Split(filepath.Dir(parse.Path), "/") |
||||
pack = ConvertHump(path[len(path)-1]) |
||||
refer = path[len(path)-1] |
||||
mockVar, mockType, srcDir string |
||||
) |
||||
for i := len(path) - 1; i > len(path)-4; i-- { |
||||
if path[i] == "dao" || path[i] == "service" { |
||||
srcDir = strings.Join(path[:i+1], "/") |
||||
break |
||||
} |
||||
pack = ConvertHump(path[i-1]) + pack |
||||
} |
||||
if mockVar = ConvertMethod(parse.Path); mockType == "d" { |
||||
mockType = "*" + refer + ".Dao" |
||||
} else { |
||||
mockType = "*" + refer + ".Service" |
||||
} |
||||
for _, parseFunc := range parse.Funcs { |
||||
if (parseFunc.Method == nil) || (parseFunc.Result == nil) || |
||||
!(parseFunc.Name[0] >= 'A' && parseFunc.Name[0] <= 'Z') { |
||||
continue |
||||
} |
||||
var ( |
||||
funcParams, funcResults, mockKey, mockValue, funcName string |
||||
) |
||||
funcName = pack + parseFunc.Name |
||||
for k, param := range parseFunc.Params { |
||||
funcParams += "_ " + param.V |
||||
if len(parseFunc.Params) > k+1 { |
||||
funcParams += ", " |
||||
} |
||||
} |
||||
for k, res := range parseFunc.Result { |
||||
if res.K == "" { |
||||
if res.V == "error" { |
||||
res.K = "err" |
||||
} else { |
||||
res.K = fmt.Sprintf("p%d", k+1) |
||||
} |
||||
} |
||||
mockKey += res.K |
||||
mockValue += res.V |
||||
funcResults += res.K + " " + res.P + res.V |
||||
if len(parseFunc.Result) > k+1 { |
||||
mockKey += ", " |
||||
mockValue += ", " |
||||
funcResults += ", " |
||||
} |
||||
} |
||||
pkg[srcDir+"."+refer] += fmt.Sprintf(tpMonkeyFunc, funcName, funcName, mockVar, mockType, funcResults, mockVar, parseFunc.Name, mockType, funcParams, mockValue, mockKey) |
||||
} |
||||
} |
||||
for path, content := range pkg { |
||||
var ( |
||||
buffer bytes.Buffer |
||||
dir = strings.Split(path, ".") |
||||
mockDir = dir[0] + "/mock" |
||||
filename = mockDir + "/monkey_" + dir[1] + ".go" |
||||
) |
||||
if _, err = os.Stat(mockDir); os.IsNotExist(err) { |
||||
err = nil |
||||
os.Mkdir(mockDir, 0744) |
||||
} |
||||
if _, err := os.Stat(filename); os.IsExist(err) { |
||||
continue |
||||
} |
||||
buffer.WriteString(fmt.Sprintf(tpPackage, "mock")) |
||||
buffer.WriteString(content) |
||||
content, _ := GoImport(filename, buffer.Bytes()) |
||||
ioutil.WriteFile(filename, content, 0644) |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,57 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"flag" |
||||
"fmt" |
||||
"os" |
||||
) |
||||
|
||||
var ( |
||||
err error |
||||
_mode, _func string |
||||
files []string |
||||
parses []*parse |
||||
) |
||||
|
||||
func main() { |
||||
flag.StringVar(&_mode, "m", "test", "Generating code by Working mode. [test|interface|mock...]") |
||||
flag.StringVar(&_func, "func", "", "Generating code by function.") |
||||
flag.Parse() |
||||
if len(os.Args) == 1 { |
||||
println("Creater is a tool for generating code.\n\nUsage: creater [-m]") |
||||
flag.PrintDefaults() |
||||
return |
||||
} |
||||
if err = parseArgs(os.Args[1:], &files, 0); err != nil { |
||||
panic(err) |
||||
} |
||||
switch _mode { |
||||
case "monkey": |
||||
if parses, err = parseFile(files...); err != nil { |
||||
panic(err) |
||||
} |
||||
if err = genMonkey(parses); err != nil { |
||||
panic(err) |
||||
} |
||||
case "test": |
||||
if parses, err = parseFile(files...); err != nil { |
||||
panic(err) |
||||
} |
||||
if err = genTest(parses); err != nil { |
||||
panic(err) |
||||
} |
||||
case "interface": |
||||
if parses, err = parseFile(files...); err != nil { |
||||
panic(err) |
||||
} |
||||
if err = genInterface(parses); err != nil { |
||||
panic(err) |
||||
} |
||||
case "mock": |
||||
if err = genMock(files...); err != nil { |
||||
panic(err) |
||||
} |
||||
default: |
||||
} |
||||
fmt.Println(print) |
||||
} |
@ -0,0 +1,193 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/ast" |
||||
"go/parser" |
||||
"go/token" |
||||
"io/ioutil" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
) |
||||
|
||||
type param struct{ K, V, P string } |
||||
|
||||
type parse struct { |
||||
Path string |
||||
Package string |
||||
// Imports []string
|
||||
Imports map[string]*param |
||||
// Structs []*param
|
||||
// Interfaces []string
|
||||
Funcs []*struct { |
||||
Name string |
||||
Method, Params, Result []*param |
||||
} |
||||
} |
||||
|
||||
func parseArgs(args []string, res *[]string, index int) (err error) { |
||||
if len(args) <= index { |
||||
return |
||||
} |
||||
if strings.HasPrefix(args[index], "-") { |
||||
index += 2 |
||||
parseArgs(args, res, index) |
||||
return |
||||
} |
||||
var f os.FileInfo |
||||
if f, err = os.Stat(args[index]); err != nil { |
||||
return |
||||
} |
||||
if f.IsDir() { |
||||
if !strings.HasSuffix(args[index], "/") { |
||||
args[index] += "/" |
||||
} |
||||
var fs []os.FileInfo |
||||
if fs, err = ioutil.ReadDir(args[index]); err != nil { |
||||
return |
||||
} |
||||
for _, f = range fs { |
||||
path, _ := filepath.Abs(args[index] + f.Name()) |
||||
args = append(args, path) |
||||
} |
||||
} else { |
||||
if strings.HasSuffix(args[index], ".go") && |
||||
!strings.HasSuffix(args[index], "_test.go") { |
||||
*res = append(*res, args[index]) |
||||
} |
||||
} |
||||
index++ |
||||
return parseArgs(args, res, index) |
||||
} |
||||
|
||||
func parseFile(files ...string) (parses []*parse, err error) { |
||||
for _, file := range files { |
||||
var ( |
||||
astFile *ast.File |
||||
fSet = token.NewFileSet() |
||||
parse = &parse{ |
||||
Imports: make(map[string]*param), |
||||
} |
||||
) |
||||
if astFile, err = parser.ParseFile(fSet, file, nil, 0); err != nil { |
||||
return |
||||
} |
||||
if astFile.Name != nil { |
||||
parse.Path = file |
||||
parse.Package = astFile.Name.Name |
||||
} |
||||
for _, decl := range astFile.Decls { |
||||
switch decl.(type) { |
||||
case *ast.GenDecl: |
||||
if specs := decl.(*ast.GenDecl).Specs; len(specs) > 0 { |
||||
parse.Imports = parseImports(specs) |
||||
} |
||||
case *ast.FuncDecl: |
||||
var ( |
||||
dec = decl.(*ast.FuncDecl) |
||||
parseFunc = &struct { |
||||
Name string |
||||
Method, Params, Result []*param |
||||
}{Name: dec.Name.Name} |
||||
) |
||||
if dec.Recv != nil { |
||||
parseFunc.Method = parserParams(dec.Recv.List) |
||||
} |
||||
if dec.Type.Params != nil { |
||||
parseFunc.Params = parserParams(dec.Type.Params.List) |
||||
} |
||||
if dec.Type.Results != nil { |
||||
parseFunc.Result = parserParams(dec.Type.Results.List) |
||||
} |
||||
parse.Funcs = append(parse.Funcs, parseFunc) |
||||
} |
||||
} |
||||
parses = append(parses, parse) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func parserParams(fields []*ast.Field) (params []*param) { |
||||
for _, field := range fields { |
||||
p := ¶m{} |
||||
p.V = parseType(field.Type) |
||||
if field.Names == nil { |
||||
params = append(params, p) |
||||
} |
||||
for _, name := range field.Names { |
||||
sp := ¶m{} |
||||
sp.K = name.Name |
||||
sp.V = p.V |
||||
sp.P = p.P |
||||
params = append(params, sp) |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func parseType(expr ast.Expr) string { |
||||
switch expr.(type) { |
||||
case *ast.Ident: |
||||
return expr.(*ast.Ident).Name |
||||
case *ast.StarExpr: |
||||
return "*" + parseType(expr.(*ast.StarExpr).X) |
||||
case *ast.ArrayType: |
||||
return "[" + parseType(expr.(*ast.ArrayType).Len) + "]" + parseType(expr.(*ast.ArrayType).Elt) |
||||
case *ast.SelectorExpr: |
||||
return parseType(expr.(*ast.SelectorExpr).X) + "." + expr.(*ast.SelectorExpr).Sel.Name |
||||
case *ast.MapType: |
||||
return "map[" + parseType(expr.(*ast.MapType).Key) + "]" + parseType(expr.(*ast.MapType).Value) |
||||
case *ast.StructType: |
||||
return "struct{}" |
||||
case *ast.InterfaceType: |
||||
return "interface{}" |
||||
case *ast.FuncType: |
||||
var ( |
||||
pTemp string |
||||
rTemp string |
||||
) |
||||
pTemp = parseFuncType(pTemp, expr.(*ast.FuncType).Params) |
||||
if expr.(*ast.FuncType).Results != nil { |
||||
rTemp = parseFuncType(rTemp, expr.(*ast.FuncType).Results) |
||||
return fmt.Sprintf("func(%s) (%s)", pTemp, rTemp) |
||||
} |
||||
return fmt.Sprintf("func(%s)", pTemp) |
||||
case *ast.ChanType: |
||||
return fmt.Sprintf("make(chan %s)", parseType(expr.(*ast.ChanType).Value)) |
||||
case *ast.Ellipsis: |
||||
return parseType(expr.(*ast.Ellipsis).Elt) |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
func parseFuncType(temp string, data *ast.FieldList) string { |
||||
var params = parserParams(data.List) |
||||
for i, param := range params { |
||||
if i == 0 { |
||||
temp = param.K + " " + param.V |
||||
continue |
||||
} |
||||
t := param.K + " " + param.V |
||||
temp = fmt.Sprintf("%s, %s", temp, t) |
||||
} |
||||
return temp |
||||
} |
||||
|
||||
func parseImports(specs []ast.Spec) (params map[string]*param) { |
||||
params = make(map[string]*param) |
||||
for _, spec := range specs { |
||||
switch spec.(type) { |
||||
case *ast.ImportSpec: |
||||
p := ¶m{V: strings.Replace(spec.(*ast.ImportSpec).Path.Value, "\"", "", -1)} |
||||
if spec.(*ast.ImportSpec).Name != nil { |
||||
p.K = spec.(*ast.ImportSpec).Name.Name |
||||
params[p.K] = p |
||||
} else { |
||||
vs := strings.Split(p.V, "/") |
||||
params[vs[len(vs)-1]] = p |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,41 @@ |
||||
package main |
||||
|
||||
var ( |
||||
tpPackage = "package %s\n\n" |
||||
tpImport = "import (\n\t%s\n)\n\n" |
||||
tpVar = "var (\n\t%s\n)\n" |
||||
tpInterface = "type %sInterface interface {\n%s}\n" |
||||
tpIntfcFunc = "%s(%s) %s\n" |
||||
tpMonkeyFunc = "// Mock%s .\nfunc Mock%s(%s %s,%s) (guard *monkey.PatchGuard) {\n\treturn monkey.PatchInstanceMethod(reflect.TypeOf(%s), \"%s\", func(_ %s, %s) (%s) {\n\t\treturn %s\n\t})\n}\n\n" |
||||
tpTestReset = "\n\t\tReset(func() {%s\n\t\t})" |
||||
tpTestFunc = "func Test%s%s(t *testing.T){%s\n\tConvey(\"%s\", t, func(){\n\t\t%s\tConvey(\"When everything goes positive\", func(){\n\t\t\t%s\n\t\t\t})\n\t\t})%s\n\t})\n}\n\n" |
||||
tpTestDaoMain = `func TestMain(m *testing.M) { |
||||
flag.Set("conf", "%s") |
||||
flag.Parse() |
||||
%s |
||||
os.Exit(m.Run()) |
||||
} |
||||
` |
||||
tpTestServiceMain = `func TestMain(m *testing.M){ |
||||
flag.Set("conf", "%s") |
||||
flag.Parse() |
||||
%s |
||||
os.Exit(m.Run()) |
||||
} |
||||
` |
||||
tpTestMainNew = `if err := paladin.Init(); err != nil { |
||||
panic(err) |
||||
} |
||||
%s` |
||||
tpTestMainOld = `if err := conf.Init(); err != nil { |
||||
panic(err) |
||||
} |
||||
%s` |
||||
print = `Generation success!
|
||||
莫生气 |
||||
代码辣鸡非我意, |
||||
自己动手分田地; |
||||
你若气死谁如意? |
||||
谈笑风生活长命. |
||||
// Release 1.2.3. Powered by Kratos`
|
||||
) |
@ -0,0 +1,42 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"golang.org/x/tools/imports" |
||||
) |
||||
|
||||
// GoImport Use golang.org/x/tools/imports auto import pkg
|
||||
func GoImport(file string, bytes []byte) (res []byte, err error) { |
||||
options := &imports.Options{ |
||||
TabWidth: 8, |
||||
TabIndent: true, |
||||
Comments: true, |
||||
Fragment: true, |
||||
} |
||||
if res, err = imports.Process(file, bytes, options); err != nil { |
||||
fmt.Printf("GoImport(%s) error(%v)", file, err) |
||||
res = bytes |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// ConvertMethod checkout the file belongs to dao or not
|
||||
func ConvertMethod(path string) (method string) { |
||||
switch { |
||||
case strings.Contains(path, "/dao"): |
||||
method = "d" |
||||
case strings.Contains(path, "/service"): |
||||
method = "s" |
||||
default: |
||||
method = "" |
||||
} |
||||
return |
||||
} |
||||
|
||||
//ConvertHump convert words to hump style
|
||||
func ConvertHump(words string) string { |
||||
return strings.ToUpper(words[0:1]) + words[1:] |
||||
} |
Loading…
Reference in new issue