From dd509fed546ce6704e4e0062caf32b90c860b482 Mon Sep 17 00:00:00 2001 From: Otokaze Date: Fri, 27 Sep 2019 19:32:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=98=AF=E6=88=91=E9=9D=99=E9=A6=99=E4=B8=8D?= =?UTF-8?q?=E5=A4=9F=E9=AA=9A=EF=BC=8C=E8=BF=98=E6=98=AF=E4=BD=A0=E8=83=96?= =?UTF-8?q?=E8=99=8E=E5=BC=80=E5=A7=8B=E9=A3=98=EF=BD=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 3 + go.sum | 49 ++++ pkg/testing/lich/README.md | 4 + pkg/testing/lich/composer.go | 130 +++++++++ pkg/testing/lich/composer_test.go | 23 ++ pkg/testing/lich/docker-compose.yaml | 25 ++ pkg/testing/lich/healthcheck.go | 85 ++++++ pkg/testing/lich/model.go | 88 ++++++ tool/testcli/README.MD | 154 ++++++++++ tool/testcli/main.go | 60 ++++ tool/testgen/README.md | 52 ++++ tool/testgen/gen.go | 419 +++++++++++++++++++++++++++ tool/testgen/main.go | 57 ++++ tool/testgen/parser.go | 193 ++++++++++++ tool/testgen/templete.go | 41 +++ tool/testgen/utils.go | 42 +++ 16 files changed, 1425 insertions(+) create mode 100644 pkg/testing/lich/README.md create mode 100644 pkg/testing/lich/composer.go create mode 100644 pkg/testing/lich/composer_test.go create mode 100644 pkg/testing/lich/docker-compose.yaml create mode 100644 pkg/testing/lich/healthcheck.go create mode 100644 pkg/testing/lich/model.go create mode 100644 tool/testcli/README.MD create mode 100644 tool/testcli/main.go create mode 100644 tool/testgen/README.md create mode 100644 tool/testgen/gen.go create mode 100644 tool/testgen/main.go create mode 100644 tool/testgen/parser.go create mode 100644 tool/testgen/templete.go create mode 100644 tool/testgen/utils.go diff --git a/go.mod b/go.mod index 0b64a546d..012bb76a0 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/mattn/go-colorable v0.1.2 // indirect github.com/montanaflynn/stats v0.5.0 github.com/openzipkin/zipkin-go v0.2.1 + github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v1.0.0 github.com/prometheus/client_model v0.0.0-20190220174349-fd36f4220a90 // indirect @@ -38,6 +39,7 @@ require ( github.com/shirou/gopsutil v2.19.6+incompatible github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 github.com/sirupsen/logrus v1.4.2 + github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 github.com/stretchr/testify v1.3.0 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/tsuna/gohbase v0.0.0-20190502052937-24ffed0537aa @@ -49,6 +51,7 @@ require ( golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect golang.org/x/time v0.0.0-20190513212739-9d24e82272b4 // indirect + golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b google.golang.org/appengine v1.6.1 // indirect google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 google.golang.org/grpc v1.22.0 diff --git a/go.sum b/go.sum index 0e964f7dc..5f25a2715 100644 --- a/go.sum +++ b/go.sum @@ -4,16 +4,20 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v0.0.0-20190523213609-cbe66965904d h1:VWP4o43LuzNbykZJzMUv5b9DWLgn0sn3GUj3RUyWMMQ= github.com/StackExchange/wmi v0.0.0-20190523213609-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aristanetworks/goarista v0.0.0-20190712234253-ed1100a1c015 h1:7ABPr1+uJdqESAdlVevnc/2FJGiC/K3uMg1JiELeF+0= github.com/aristanetworks/goarista v0.0.0-20190712234253-ed1100a1c015/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190531201743-edce55837238 h1:uNljlOxtOHrPnRoPPx+JanqjAGZpNiqAGVBfGskd/pg= github.com/cockroachdb/datadriven v0.0.0-20190531201743-edce55837238/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -21,14 +25,18 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf h1:CAKfRE2YtTUIjjh1bkBt github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8= github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso= github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 h1:MZRmHqDBd0vxNwenEbKSQqRVT24d3C05ft8kduSwlqM= github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= @@ -43,6 +51,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= @@ -57,10 +66,13 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -68,33 +80,47 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.4.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.4 h1:5xLhQjsk4zqPf9EHCrja2qFZMx+yBqkO3XgJ14bNnU0= github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= @@ -109,7 +135,9 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.5.0 h1:2EkzeTSqBB4V4bJwWrt5gIIrZmpJBcoIRGS2kWLgzmk= github.com/montanaflynn/stats v0.5.0/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= @@ -121,6 +149,10 @@ github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 h1:zjmNboC3QFuMdJSaZJ7Qvi3HUxWXPdj7wb3rc4jH5HI= +github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3/go.mod h1:pLR8n2aimFxvvDJ6n8JuQWthMGezCYMjuhlaTjPTZf0= +github.com/otokaze/mock v1.1.1 h1:mQSWY25OMRIdBBuJO3AJ5CZ77WK6QtY0leU4qKGj4ok= +github.com/otokaze/mock v1.1.1/go.mod h1:pLR8n2aimFxvvDJ6n8JuQWthMGezCYMjuhlaTjPTZf0= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -147,6 +179,7 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20190806203942-babf20351dd7e3ac320adedbbe5eb311aec8763c h1:eED6LswgZ3TfAl9fb+L2TfdSlXpYdg21iWZMdHuoSks= github.com/remyoudompheng/bigfft v0.0.0-20190806203942-babf20351dd7e3ac320adedbbe5eb311aec8763c/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY= @@ -160,8 +193,14 @@ github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjM github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -170,12 +209,15 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tsuna/gohbase v0.0.0-20190502052937-24ffed0537aa h1:V/ABqiqsgqmpoIcLDSpJ1KqPfbxRmkcfET5+BRy9ctM= github.com/tsuna/gohbase v0.0.0-20190502052937-24ffed0537aa/go.mod h1:3HfLQly3YNLGxNv/2YOfmz30vcjG9hbuME1GpxoLlGs= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20190720005121-fe86a786a4c3 h1:LlCFU/KJ9P/8QKB73kkd1z/zbm7ZJ2V4HEgEHI3N7gk= go.etcd.io/etcd v0.0.0-20190720005121-fe86a786a4c3/go.mod h1:N0RPWo9FXJYZQI4BTkDtQylrstIigYHeR18ONnyTufk= @@ -191,6 +233,7 @@ golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -236,11 +279,14 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b h1:mSUCVIwDx4hfXJfWsOPfdzEHxzb2Xjl6BQ8YgPnazQA= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180608181217-32ee49c4dd80/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -254,10 +300,12 @@ google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= @@ -269,4 +317,5 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/testing/lich/README.md b/pkg/testing/lich/README.md new file mode 100644 index 000000000..180224fb5 --- /dev/null +++ b/pkg/testing/lich/README.md @@ -0,0 +1,4 @@ +## testing/lich 运行环境构建 +基于 docker-compose 实现跨平台跨语言环境的容器依赖管理方案,以解决运行ut场景下的 (mysql, redis, mc)容器依赖问题。 + +使用说明参见:https://github.com/bilibili/kratos/tree/master/tool/testcli/README.md \ No newline at end of file diff --git a/pkg/testing/lich/composer.go b/pkg/testing/lich/composer.go new file mode 100644 index 000000000..87db90e61 --- /dev/null +++ b/pkg/testing/lich/composer.go @@ -0,0 +1,130 @@ +package lich + +import ( + "bytes" + "crypto/md5" + "encoding/json" + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "time" +) + +var ( + retry int + yamlPath string + pathHash string + services map[string]*Container +) + +func init() { + flag.StringVar(&yamlPath, "f", "docker-compose.yaml", "composer yaml path.") +} + +// Setup setup UT related environment dependence for everything. +func Setup() (err error) { + if _, err = os.Stat(yamlPath); os.IsNotExist(err) { + log.Println("composer yaml is not exist!", yamlPath) + return + } + if yamlPath, err = filepath.Abs(yamlPath); err != nil { + log.Printf("filepath.Abs(%s) error(%v)", yamlPath, err) + return + } + pathHash = fmt.Sprintf("%x", md5.Sum([]byte(yamlPath)))[:9] + var args = []string{"-f", yamlPath, "-p", pathHash, "up", "-d"} + if err = exec.Command("docker-compose", args...).Run(); err != nil { + log.Printf("exec.Command(docker-composer) args(%v) error(%v)", args, err) + Teardown() + return + } + // 拿到yaml文件中的服务名,同时通过服务名获取到启动的容器ID + if _, err = getServices(); err != nil { + Teardown() + return + } + // 通过容器ID检测容器的状态,包括容器服务的状态 + if _, err = checkServices(); err != nil { + Teardown() + return + } + return +} + +// Teardown unsetup all environment dependence. +func Teardown() (err error) { + if _, err = os.Stat(yamlPath); os.IsNotExist(err) { + log.Println("composer yaml is not exist!") + return + } + if yamlPath, err = filepath.Abs(yamlPath); err != nil { + log.Printf("filepath.Abs(%s) error(%v)", yamlPath, err) + return + } + pathHash = fmt.Sprintf("%x", md5.Sum([]byte(yamlPath)))[:9] + args := []string{"-f", yamlPath, "-p", pathHash, "down"} + if output, err := exec.Command("docker-compose", args...).CombinedOutput(); err != nil { + log.Fatalf("exec.Command(docker-composer) args(%v) stdout(%s) error(%v)", args, string(output), err) + return err + } + return +} + +func getServices() (output []byte, err error) { + var args = []string{"-f", yamlPath, "-p", pathHash, "config", "--services"} + if output, err = exec.Command("docker-compose", args...).CombinedOutput(); err != nil { + log.Printf("exec.Command(docker-composer) args(%v) stdout(%s) error(%v)", args, string(output), err) + return + } + services = make(map[string]*Container) + output = bytes.TrimSpace(output) + for _, svr := range bytes.Split(output, []byte("\n")) { + args = []string{"-f", yamlPath, "-p", pathHash, "ps", "-a", "-q", string(svr)} + if output, err = exec.Command("docker-compose", args...).CombinedOutput(); err != nil { + log.Printf("exec.Command(docker-composer) args(%v) stdout(%s) error(%v)", args, string(output), err) + return + } + var id = string(bytes.TrimSpace(output)) + args = []string{"inspect", id, "--format", "'{{json .}}'"} + if output, err = exec.Command("docker", args...).CombinedOutput(); err != nil { + log.Printf("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.Printf("exec.Command(docker) args(%v) error(%v)", args, err) + return + } + var c = &Container{} + if err = json.Unmarshal(bytes.Trim(output, "'"), c); err != nil { + log.Printf("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.Printf("healthcheck(%s) error(%v) retrying %d times...", svr, err, 5-retry) + return + } + // TODO About container check and more... + } + return +} diff --git a/pkg/testing/lich/composer_test.go b/pkg/testing/lich/composer_test.go new file mode 100644 index 000000000..9bc181503 --- /dev/null +++ b/pkg/testing/lich/composer_test.go @@ -0,0 +1,23 @@ +package lich + +import ( + "testing" + + "github.com/smartystreets/goconvey/convey" +) + +func TestComposer(t *testing.T) { + convey.Convey("Composer testing....", t, func(convCtx convey.C) { + convCtx.Convey("When Setup everything goes positive", func(convCtx convey.C) { + convCtx.Convey("Then err should be nil.", func(convCtx convey.C) { + convCtx.So(Setup(), convey.ShouldBeNil) + }) + }) + + convCtx.Convey("When UnSetup everything goes positive", func(convCtx convey.C) { + convCtx.Convey("Then err should be nil.", func(convCtx convey.C) { + convCtx.So(Teardown(), convey.ShouldBeNil) + }) + }) + }) +} diff --git a/pkg/testing/lich/docker-compose.yaml b/pkg/testing/lich/docker-compose.yaml new file mode 100644 index 000000000..afff139a2 --- /dev/null +++ b/pkg/testing/lich/docker-compose.yaml @@ -0,0 +1,25 @@ +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 + + memcached: + image: memcached + ports: + - 11211:11211 \ No newline at end of file diff --git a/pkg/testing/lich/healthcheck.go b/pkg/testing/lich/healthcheck.go new file mode 100644 index 000000000..dfda14901 --- /dev/null +++ b/pkg/testing/lich/healthcheck.go @@ -0,0 +1,85 @@ +package lich + +import ( + "fmt" + "log" + "net" + "strconv" + "strings" + + "database/sql" + // 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.Printf("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.Printf("container: %s proto(%s) unsupported.", id, proto) + continue + } + for _, pulish := range ports { + var ( + ip = net.ParseIP(pulish.HostIP) + port, _ = strconv.Atoi(pulish.HostPort) + tcpAddr = &net.TCPAddr{IP: ip, Port: port} + tcpConn *net.TCPConn + ) + if tcpConn, err = net.DialTCP("tcp", nil, tcpAddr); err != nil { + log.Printf("net.DialTCP(%s:%s) error(%v)", pulish.HostIP, pulish.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.Printf("sql.Open(mysql) dsn(%s) error(%v)", dsn, err) + return + } + if err = db.Ping(); err != nil { + log.Printf("ping(db) dsn(%s) error(%v)", dsn, err) + } + defer db.Close() + return +} diff --git a/pkg/testing/lich/model.go b/pkg/testing/lich/model.go new file mode 100644 index 000000000..64653a681 --- /dev/null +++ b/pkg/testing/lich/model.go @@ -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 +} diff --git a/tool/testcli/README.MD b/tool/testcli/README.MD new file mode 100644 index 000000000..7a0ef7a0e --- /dev/null +++ b/tool/testcli/README.MD @@ -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* + +#### 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 { + lich.Teardown() + os.Exit(code) + } +} + ``` +## 注意 +因为启动mysql容器较为缓慢,健康检测的机制会重试3次,每次暂留5秒钟,基本在10s内mysql就能从creating到服务正常启动! + +当然你也可以在使用 testcli 时加上 --nodown,使其不用每次跑都新建容器,只在第一次跑的时候会初始化容器,后面都进行复用,这样速度会快很多。 + +成功启动后就欢乐奔放的玩耍吧~ Good Lucky! \ No newline at end of file diff --git a/tool/testcli/main.go b/tool/testcli/main.go new file mode 100644 index 000000000..95eaca050 --- /dev/null +++ b/tool/testcli/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "flag" + "os" + "os/exec" + "strings" + + "github.com/bilibili/kratos/pkg/testing/lich" +) + +var ( + noDown bool +) + +func init() { + flag.BoolVar(&noDown, "nodown", false, "containers are not recycled.") +} + +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) + } + if !noDown { + 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) + } +} diff --git a/tool/testgen/README.md b/tool/testgen/README.md new file mode 100644 index 000000000..3ec686e7f --- /dev/null +++ b/tool/testgen/README.md @@ -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 #仅传入包路径即可 +``` +### 赋诗一首 +``` +莫生气 莫生气 +代码辣鸡非我意 +自己动手分田地 +谈笑风生活长命 +``` \ No newline at end of file diff --git a/tool/testgen/gen.go b/tool/testgen/gen.go new file mode 100644 index 000000000..b515fda29 --- /dev/null +++ b/tool/testgen/gen.go @@ -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 +} diff --git a/tool/testgen/main.go b/tool/testgen/main.go new file mode 100644 index 000000000..4d94e6aec --- /dev/null +++ b/tool/testgen/main.go @@ -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) +} diff --git a/tool/testgen/parser.go b/tool/testgen/parser.go new file mode 100644 index 000000000..ec363fd04 --- /dev/null +++ b/tool/testgen/parser.go @@ -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 +} diff --git a/tool/testgen/templete.go b/tool/testgen/templete.go new file mode 100644 index 000000000..a84811983 --- /dev/null +++ b/tool/testgen/templete.go @@ -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 主站质保团队` +) diff --git a/tool/testgen/utils.go b/tool/testgen/utils.go new file mode 100644 index 000000000..025fb91a7 --- /dev/null +++ b/tool/testgen/utils.go @@ -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:] +}