From 05378212e01d40c587b23ced0900ce86cd743374 Mon Sep 17 00:00:00 2001 From: weekface Date: Wed, 12 Sep 2018 12:30:12 +0800 Subject: [PATCH] Add a new RequeueError type (#80) * add RequeueError * make lint happy * use pingcap/errors.Find --- Gopkg.lock | 118 ++-- Gopkg.toml | 5 + pkg/controller/controller_utils.go | 20 + .../tidbcluster/tidb_cluster_control.go | 19 +- .../tidbcluster/tidb_cluster_controller.go | 7 +- pkg/manager/member/tikv_member_manager.go | 2 +- pkg/manager/member/tikv_scaler.go | 2 +- pkg/manager/member/tikv_scaler_test.go | 50 +- vendor/github.com/pkg/errors/.gitignore | 24 + vendor/github.com/pkg/errors/.travis.yml | 14 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/README.md | 52 ++ vendor/github.com/pkg/errors/appveyor.yml | 32 ++ vendor/github.com/pkg/errors/bench_test.go | 63 +++ vendor/github.com/pkg/errors/errors.go | 337 +++++++++++ vendor/github.com/pkg/errors/errors_test.go | 371 ++++++++++++ vendor/github.com/pkg/errors/example_test.go | 205 +++++++ vendor/github.com/pkg/errors/format_test.go | 534 ++++++++++++++++++ vendor/github.com/pkg/errors/group.go | 33 ++ vendor/github.com/pkg/errors/juju_adaptor.go | 76 +++ vendor/github.com/pkg/errors/stack.go | 170 ++++++ vendor/github.com/pkg/errors/stack_test.go | 289 ++++++++++ 22 files changed, 2357 insertions(+), 89 deletions(-) create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/bench_test.go create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/errors_test.go create mode 100644 vendor/github.com/pkg/errors/example_test.go create mode 100644 vendor/github.com/pkg/errors/format_test.go create mode 100644 vendor/github.com/pkg/errors/group.go create mode 100644 vendor/github.com/pkg/errors/juju_adaptor.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/github.com/pkg/errors/stack_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 26b1059222..8935fb0f3a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,7 +2,7 @@ [[projects]] - digest = "1:e4b30804a381d7603b8a344009987c1ba351c26043501b23b8c7ce21f0b67474" + digest = "1:1ddbdd9b402308eab3d8299ec4c55d259708f803c3adaea701241b9f96a8102e" name = "github.com/BurntSushi/toml" packages = ["."] pruneopts = "" @@ -10,14 +10,14 @@ [[projects]] branch = "master" - digest = "1:c0bec5f9b98d0bc872ff5e834fac186b807b656683bd29cb82fb207a1513fabb" + digest = "1:fca298802a2ab834d6eb0e284788ae037ebc324c0f325ff92c5eea592d189cc5" name = "github.com/beorn7/perks" packages = ["quantile"] pruneopts = "" revision = "3a771d992973f24aa725d07868b467d1ddfceafb" [[projects]] - digest = "1:15d8c38b5661cc3c1e82f0141feaf9743bf84a4cafd87d94893cf1f417a19a60" + digest = "1:f5ba6b8c701a9e8da7cdc0521beac4351ead5795f5469f2815ebbb3bacb80750" name = "github.com/coreos/bbolt" packages = ["."] pruneopts = "" @@ -25,7 +25,7 @@ version = "v1.3.1-coreos.6" [[projects]] - digest = "1:4786be5bb84ba9f1c385823387a774e8b0bf6ca738b8229ff40254387a31b854" + digest = "1:8c38908870225da8d5c33bb54a9f8eb22eba82563ea58ac4c9b10950b98c88f8" name = "github.com/coreos/etcd" packages = [ "auth", @@ -110,7 +110,7 @@ version = "v0.2.0" [[projects]] - digest = "1:1a99671bb8620f32c88d20f65c7069a8c3745d1533bd486b9c0c538a1d981a5c" + digest = "1:be43db6928b30e7117903af471d3cd4e82a5f059c368bbfc7681882725ee06d8" name = "github.com/coreos/go-systemd" packages = ["journal"] pruneopts = "" @@ -118,7 +118,7 @@ version = "v17" [[projects]] - digest = "1:b168b5f54103bbb328987b268867f656c4536b70f7b6fec4726a4619b0ebb5f0" + digest = "1:43a80f48792995e161474ab87a031d11bc6486a79bba9bfd1d579b1fc5c8bad1" name = "github.com/coreos/pkg" packages = ["capnslog"] pruneopts = "" @@ -126,7 +126,7 @@ version = "v4" [[projects]] - digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b" + digest = "1:0a39ec8bf5629610a4bc7873a92039ee509246da3cef1a0ea60f1ed7e5f9cea5" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "" @@ -134,7 +134,7 @@ version = "v1.1.0" [[projects]] - digest = "1:6098222470fe0172157ce9bbef5d2200df4edde17ee649c5d6e48330e4afa4c6" + digest = "1:2426da75f49e5b8507a6ed5d4c49b06b2ff795f4aec401c106b7db8fb2625cd7" name = "github.com/dgrijalva/jwt-go" packages = ["."] pruneopts = "" @@ -143,7 +143,7 @@ [[projects]] branch = "master" - digest = "1:f1a75a8e00244e5ea77ff274baa9559eb877437b240ee7b278f3fc560d9f08bf" + digest = "1:c367c68b4bf22ef91069ae442422025da1a3a57049b370252a7b4a895c3fdd6b" name = "github.com/dustin/go-humanize" packages = ["."] pruneopts = "" @@ -158,7 +158,7 @@ version = "v1.0.0" [[projects]] - digest = "1:c07de423ca37dc2765396d6971599ab652a339538084b9b58c9f7fc533b28525" + digest = "1:dc876ae7727280d95f97af5320308131278b93d6c6f5cf953065e18cb8c88fd2" name = "github.com/go-sql-driver/mysql" packages = ["."] pruneopts = "" @@ -166,7 +166,7 @@ version = "v1.4.0" [[projects]] - digest = "1:6e73003ecd35f4487a5e88270d3ca0a81bc80dc88053ac7e4dcfec5fba30d918" + digest = "1:673df1d02ca0c6f51458fe94bbb6fae0b05e54084a31db2288f1c4321255c2da" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -188,14 +188,14 @@ [[projects]] branch = "master" - digest = "1:1d8a57fce1f68298ce54967c0752a2ab54bf55dff261d245b8f3440a217700cb" + digest = "1:109e5c9d8e3e044804978d6a1f70cc75b2a01d94889f1abfeea93aa5e708b230" name = "github.com/golang/groupcache" packages = ["lru"] pruneopts = "" revision = "24b0969c4cb722950103eed87108c8d291a8df00" [[projects]] - digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b" + digest = "1:b1d3041d568e065ab4d76f7477844458e9209c0bb241eaccdc0770bf0a13b120" name = "github.com/golang/protobuf" packages = [ "jsonpb", @@ -227,7 +227,7 @@ revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" [[projects]] - digest = "1:16b2837c8b3cf045fa2cdc82af0cf78b19582701394484ae76b2c3bc3c99ad73" + digest = "1:1962b5d00f5285d08504697049627d45ad876912894528d31cdc1c05cdc853f6" name = "github.com/googleapis/gnostic" packages = [ "OpenAPIv2", @@ -255,7 +255,7 @@ version = "v1.6.2" [[projects]] - digest = "1:64d212c703a2b94054be0ce470303286b177ad260b2f89a307e3d1bb6c073ef6" + digest = "1:fe1b4d4cbe48c0d55507c55f8663aa4185576cc58fa0c8be03bb8f19dfe17a9c" name = "github.com/gorilla/websocket" packages = ["."] pruneopts = "" @@ -263,7 +263,7 @@ version = "v1.2.0" [[projects]] - digest = "1:e24dc5ef44694848785de507f439a24e9e6d96d7b43b8cf3d6cfa857aa1e2186" + digest = "1:54e4c08b0830ba2aa34883e86e2344da901e3a8affdd989a0ffd3564b9b89fc9" name = "github.com/grpc-ecosystem/go-grpc-prometheus" packages = ["."] pruneopts = "" @@ -271,7 +271,7 @@ version = "v1.2.0" [[projects]] - digest = "1:b563eec078077ba5cedc795462cbd6d7c75a106a4d2e3d02940093c80e28de28" + digest = "1:0aca7b23c05e475e2223c5f9d363519fa6a1a40f59c809cf90ebe12c45915add" name = "github.com/grpc-ecosystem/grpc-gateway" packages = [ "runtime", @@ -294,7 +294,7 @@ revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" [[projects]] - digest = "1:b3c5b95e56c06f5aa72cb2500e6ee5f44fcd122872d4fec2023a488e561218bc" + digest = "1:dfe6012fbd6aeb1f6e656f2463eae75fa535c7e6a73086da16844cecdf9e17cc" name = "github.com/hpcloud/tail" packages = [ ".", @@ -316,7 +316,7 @@ version = "v0.1.0" [[projects]] - digest = "1:b79fc583e4dc7055ed86742e22164ac41bf8c0940722dbcb600f1a3ace1a8cb5" + digest = "1:78c8b7738bc04b822e6779c31f21b08c8662a0b78f42a1636f91fe6af3742038" name = "github.com/json-iterator/go" packages = ["."] pruneopts = "" @@ -332,7 +332,7 @@ revision = "22422dad46e14561a0854ad42497a75af9b61909" [[projects]] - digest = "1:63722a4b1e1717be7b98fc686e0b30d5e7f734b9e93d7dee86293b6deab7ea28" + digest = "1:49a8b01a6cd6558d504b65608214ca40a78000e1b343ed0da5c6a9ccd83d6d30" name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] pruneopts = "" @@ -356,7 +356,7 @@ version = "1.0.1" [[projects]] - digest = "1:8707789730adf8e55b0dde54c0f92ca8100c11c30d36191466c56be4b8ccb524" + digest = "1:18b75338acf28919ea02b875a04053bae69476d9520c428a287253f16eb96030" name = "github.com/montanaflynn/stats" packages = ["."] pruneopts = "" @@ -364,7 +364,7 @@ version = "0.3.0" [[projects]] - digest = "1:a7fd918fb5bd2188436785c0424f8a50b4addfedf37a2b14d796be2a927b8007" + digest = "1:f34f9c72cd54175c366e7ee9fe113dcc63a1138fd920cf6ff8c5f26f15b1cabc" name = "github.com/onsi/ginkgo" packages = [ ".", @@ -391,7 +391,7 @@ version = "v1.6.0" [[projects]] - digest = "1:a2453f1424ddc8c28a93b2bb59f9503f61f4a1a3a4719b007c8d48a2cada631b" + digest = "1:4fdaf58dd71c7ae53c695becac1653269f6899602a9e02f53d33b7bca4cf31c8" name = "github.com/onsi/gomega" packages = [ ".", @@ -413,14 +413,14 @@ [[projects]] branch = "master" - digest = "1:c24598ffeadd2762552269271b3b1510df2d83ee6696c1e543a0ff653af494bc" + digest = "1:b7be9a944fe102bf466420fa8a064534dd12547a0482f5b684d228425b559b56" name = "github.com/petar/GoLLRB" packages = ["llrb"] pruneopts = "" revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4" [[projects]] - digest = "1:a13f2f7bf6e033304d725463d4ae7340b1f031c13d75b12357fb97acc3c2e969" + digest = "1:0217da809f7631b7c5682a4e7d41bc7356052dd72d9275870611b835f9e323a4" name = "github.com/pingcap/kvproto" packages = [ "pkg/eraftpb", @@ -431,7 +431,7 @@ revision = "b7ba8ea1c0b4915d333a146eb0e674fb5b33854f" [[projects]] - digest = "1:bf0b199186ef979e88dc018d17143a023b97b9a219ced459d7beed99189a8f7c" + digest = "1:4c21621be13845940a8292443bf2424d435b2b07182ab80e62a78fc3109a8c20" name = "github.com/pingcap/pd" packages = [ "pkg/apiutil", @@ -452,7 +452,16 @@ version = "v2.1.0-beta" [[projects]] - digest = "1:4142d94383572e74b42352273652c62afec5b23f325222ed09198f46009022d1" + digest = "1:f9526b57642018153ff8223fb8ac281ed73b877e5233742defe47d68792831dd" + name = "github.com/pkg/errors" + packages = ["."] + pruneopts = "" + revision = "9316aeb006f59424c65ff505c217f90c43d6445d" + source = "https://github.com/pingcap/errors.git" + version = "v0.9.0" + +[[projects]] + digest = "1:981835985f655d1d380cc6aa7d9fa9ad7abfaf40c75da200fd40d864cd05a7c3" name = "github.com/prometheus/client_golang" packages = [ "prometheus", @@ -465,7 +474,7 @@ [[projects]] branch = "master" - digest = "1:185cf55b1f44a1bf243558901c3f06efa5c64ba62cfdcbb1bf7bbe8c3fb68561" + digest = "1:562d53e436b244a9bb5c1ff43bcaf4882e007575d34ec37717b15751c65cc63a" name = "github.com/prometheus/client_model" packages = ["go"] pruneopts = "" @@ -473,7 +482,7 @@ [[projects]] branch = "master" - digest = "1:f477ef7b65d94fb17574fc6548cef0c99a69c1634ea3b6da248b63a61ebe0498" + digest = "1:8a871dca636f82a927daffe10e9866fee6aa97a215905d56e97ecc73c07d5d44" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -485,7 +494,7 @@ [[projects]] branch = "master" - digest = "1:e04aaa0e8f8da0ed3d6c0700bd77eda52a47f38510063209d72d62f0ef807d5e" + digest = "1:7f298639cea3d8fe88aeefe6a7af1d642bca295cd125e172d320f57064c956c2" name = "github.com/prometheus/procfs" packages = [ ".", @@ -497,7 +506,7 @@ revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92" [[projects]] - digest = "1:3fcbf733a8d810a21265a7f2fe08a3353db2407da052b233f8b204b5afc03d9b" + digest = "1:2c38661f5fb038bfb95197e0e5bc7a8f050d1f992c5ddaa01945e58fe2ef00de" name = "github.com/sirupsen/logrus" packages = ["."] pruneopts = "" @@ -522,14 +531,14 @@ [[projects]] branch = "master" - digest = "1:09a040821931764c271471639b42e2610b0aae3607840056e5dd61c2b8acdf3f" + digest = "1:74e767d6ab72d1037ba07a4d53609d8289289f5cea98afc6e16cddac738c632d" name = "github.com/tmc/grpc-websocket-proxy" packages = ["wsproxy"] pruneopts = "" revision = "830351dc03c6f07d625727d5f993a463babb20e1" [[projects]] - digest = "1:2e7f653483e51243b6cd6de60ce39bde0d6927d10a3c24295ab0f82cb1efeae2" + digest = "1:ccbc34f054a28f6326a237859395a8c4bd6bd5271196a87b79abc265406ea284" name = "github.com/ugorji/go" packages = ["codec"] pruneopts = "" @@ -538,7 +547,7 @@ [[projects]] branch = "master" - digest = "1:e86c998121d4bbb5b3170caa2b927d671dcccb21ded7eaafff5f65b46830b7fc" + digest = "1:36480b62d00af472e7d65ea9a9e51b564a63e90898dc2d2971e31acfc4df15e6" name = "github.com/unrolled/render" packages = ["."] pruneopts = "" @@ -553,7 +562,7 @@ version = "0.0.1" [[projects]] - digest = "1:74f86c458e82e1c4efbab95233e0cf51b7cc02dc03193be9f62cd81224e10401" + digest = "1:5e30725e7522642910b34208061b21bb0cd77b8ce115c3133a1431c52054e004" name = "go.uber.org/atomic" packages = ["."] pruneopts = "" @@ -569,7 +578,7 @@ version = "v1.1.0" [[projects]] - digest = "1:246f378f80fba6fcf0f191c486b6613265abd2bc0f2fa55a36b928c67352021e" + digest = "1:2fabb14a874994210af33633091bd0eb070b50aa527767abaac1b5483db03d75" name = "go.uber.org/zap" packages = [ ".", @@ -585,7 +594,7 @@ [[projects]] branch = "master" - digest = "1:53c4b75f22ea7757dea07eae380ea42de547ae6865a5e3b41866754a8a8219c9" + digest = "1:16b935c128f178647036048862a21e8bfd66d1e83fb19787a8b356bdcf0de899" name = "golang.org/x/crypto" packages = [ "bcrypt", @@ -597,7 +606,7 @@ [[projects]] branch = "master" - digest = "1:67c2d940f2d5c017ef88e9847709dca9b38d5fe82f1e33fb42ace515219f22f1" + digest = "1:974f69f72b057983eb795d8514f19256f777661a7745df661659037a506ea33f" name = "golang.org/x/net" packages = [ "context", @@ -616,7 +625,7 @@ [[projects]] branch = "master" - digest = "1:00355c1b032fcd05c29dad217f43f38f7f46d691a519d86416edaf345e677c21" + digest = "1:37b9612de0dbbe0fde993b89df8294e5344a25508987cb54d8032b12c962b681" name = "golang.org/x/sys" packages = [ "unix", @@ -626,7 +635,7 @@ revision = "acbc56fc7007d2a01796d5bde54f39e3b3e95945" [[projects]] - digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4" + digest = "1:af9bfca4298ef7502c52b1459df274eed401a4f5498b900e9a92d28d3d87ac5a" name = "golang.org/x/text" packages = [ "collate", @@ -670,7 +679,7 @@ [[projects]] branch = "master" - digest = "1:c73b8c7b4bfb2e69de55a3549d6a8089d7757899cc5b62ff1c180bd76e9ee7f6" + digest = "1:1be4aac9059608d957fbdd15fc03c8b8ee7b089cdd09bc405941258c255da3f8" name = "golang.org/x/tools" packages = [ "go/ast/astutil", @@ -681,7 +690,7 @@ revision = "ca6481ae56504398949d597084558e50ad07117a" [[projects]] - digest = "1:c1771ca6060335f9768dff6558108bc5ef6c58506821ad43377ee23ff059e472" + digest = "1:eede11c81b63c8f6fd06ef24ba0a640dc077196ec9b7a58ecde03c82eee2f151" name = "google.golang.org/appengine" packages = ["cloudsql"] pruneopts = "" @@ -690,14 +699,14 @@ [[projects]] branch = "master" - digest = "1:c7ecd434ece8887311c33ea3c731e2fb42f43092a43545e350b493b9dcb023b2" + digest = "1:0e67017a8aac6ee3a3121a0953b0b2f8bc4eabb58f7ea93484d15bf5fc4aca93" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "" revision = "daca94659cb50e9f37c1b834680f2e46358f10b0" [[projects]] - digest = "1:72f0e0c3092355544e3522115e558002de5231f1b18d9edbad44f3b440e447ef" + digest = "1:705144599450e943f4c12963dd83f7173b686fe956dfe767c8811f7930a873c5" name = "google.golang.org/grpc" packages = [ ".", @@ -733,7 +742,7 @@ version = "v1.12.0" [[projects]] - digest = "1:eb53021a8aa3f599d29c7102e65026242bdedce998a54837dc67f14b6a97c5fd" + digest = "1:b2106f1668ea5efc1ecc480f7e922a093adb9563fd9ce58585292871f0d0f229" name = "gopkg.in/fsnotify.v1" packages = ["."] pruneopts = "" @@ -774,7 +783,7 @@ version = "v2.2.1" [[projects]] - digest = "1:4d235221f43d5243b4929e098993fba365cd9a6448c613f4f8e59ea869ac094f" + digest = "1:5514686068022eb717b11b2d09370fcd7e3f225c7f0a2f2eccc9a110ac116376" name = "k8s.io/api" packages = [ "admissionregistration/v1alpha1", @@ -811,7 +820,7 @@ version = "kubernetes-1.10.2" [[projects]] - digest = "1:d037855f9ba5021b3ee4032e82601686a54c31ced8ff9d0a1c95661a8baa391b" + digest = "1:dd8a4862b27c67565c910bef0b5bca0db73907687dd4e8fb602c6cbda0edac04" name = "k8s.io/apiextensions-apiserver" packages = ["pkg/features"] pruneopts = "" @@ -819,7 +828,7 @@ version = "kubernetes-1.10.2" [[projects]] - digest = "1:9b07c796baf391a2dfa8c64bd5ddc28cbeb00723389f2f3da2e3d09b961f2e31" + digest = "1:74b6a5df550acd43357bbdbd6ab6b9e46431aa64fcf9653989724ac9f84988f0" name = "k8s.io/apimachinery" packages = [ "pkg/api/equality", @@ -871,7 +880,7 @@ [[projects]] branch = "master" - digest = "1:dda6cace45dfdf859aed68eee808b479cd7fe4134680f5117394ed7cc83b6652" + digest = "1:a31f146176608ad1531c6c6bb9c9d0a7e875ffea497729e5b621b1b7c7f148d1" name = "k8s.io/apiserver" packages = [ "pkg/features", @@ -882,7 +891,7 @@ revision = "1b22bcf04547184729fc941c8ee717748e48d667" [[projects]] - digest = "1:2f8f79ed14d9c04f93679d0395b49ffca69b860eac101ba503296293da88aeb1" + digest = "1:34031a0488546e25b01e755bf371894ca927c3405006fd5d45619d463e898559" name = "k8s.io/client-go" packages = [ "discovery", @@ -1038,7 +1047,7 @@ version = "kubernetes-1.10.2" [[projects]] - digest = "1:a85379f6270659224a302cadbbcc769f140eb1949bdc4ed4b978e7406dda3828" + digest = "1:b4e7434131151e4dd32e8f598a65351db60b58c85b341eb906a40765a6be11ca" name = "k8s.io/code-generator" packages = [ "cmd/client-gen", @@ -1067,7 +1076,7 @@ [[projects]] branch = "master" - digest = "1:74eb4556b4379d0d76a3a5ada504ff6c5ef76cd85cbf1347cb649e4c1cc8ca9e" + digest = "1:ac4ba41d430b5e58e126799b1732a9f678d30b3b76256ded2564a97ba7b360ee" name = "k8s.io/gengo" packages = [ "args", @@ -1084,14 +1093,14 @@ [[projects]] branch = "master" - digest = "1:951bc2047eea6d316a17850244274554f26fd59189360e45f4056b424dadf2c1" + digest = "1:c5c576f648e159de43ee1a401cf5f66e3ad66308cab9d34ccd738aa2ef11c43d" name = "k8s.io/kube-openapi" packages = ["pkg/util/proto"] pruneopts = "" revision = "e3762e86a74c878ffed47484592986685639c2cd" [[projects]] - digest = "1:ad247ab9725165a7f289779d46747da832e33a4efe8ae264461afc571f65dac8" + digest = "1:a70c25103d90f6bed9e1ff0741232d14c2b843b4758a841e3a8b4ab30d065f63" name = "k8s.io/kubernetes" packages = [ "pkg/features", @@ -1113,6 +1122,7 @@ "github.com/pingcap/kvproto/pkg/pdpb", "github.com/pingcap/pd/pkg/typeutil", "github.com/pingcap/pd/server", + "github.com/pkg/errors", "k8s.io/api/apps/v1beta1", "k8s.io/api/core/v1", "k8s.io/apimachinery/pkg/api/equality", diff --git a/Gopkg.toml b/Gopkg.toml index 6bfa8c7f9f..7ae00105bf 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -69,3 +69,8 @@ required = [ [[override]] name = "github.com/BurntSushi/toml" revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.9.0" + source = "https://github.com/pingcap/errors.git" diff --git a/pkg/controller/controller_utils.go b/pkg/controller/controller_utils.go index 4242adfcff..3fec4bc529 100644 --- a/pkg/controller/controller_utils.go +++ b/pkg/controller/controller_utils.go @@ -38,6 +38,26 @@ const ( defaultPushgatewayImage = "prom/pushgateway:v0.3.1" ) +// RequeueError is used to requeue the item, this error type should't be considered as a real error +type RequeueError struct { + s string +} + +func (re *RequeueError) Error() string { + return re.s +} + +// RequeueErrorf returns a RequeueError +func RequeueErrorf(format string, a ...interface{}) error { + return &RequeueError{fmt.Sprintf(format, a...)} +} + +// IsRequeueError returns whether err is a RequeueError +func IsRequeueError(err error) bool { + _, ok := err.(*RequeueError) + return ok +} + // GetOwnerRef returns TidbCluster's OwnerReference func GetOwnerRef(tc *v1alpha1.TidbCluster) metav1.OwnerReference { controller := true diff --git a/pkg/controller/tidbcluster/tidb_cluster_control.go b/pkg/controller/tidbcluster/tidb_cluster_control.go index 64e8c5f983..be55f52a2a 100644 --- a/pkg/controller/tidbcluster/tidb_cluster_control.go +++ b/pkg/controller/tidbcluster/tidb_cluster_control.go @@ -20,6 +20,7 @@ import ( "github.com/pingcap/tidb-operator/pkg/manager" "github.com/pingcap/tidb-operator/pkg/util" apiequality "k8s.io/apimachinery/pkg/api/equality" + errorutils "k8s.io/apimachinery/pkg/util/errors" "k8s.io/client-go/tools/record" ) @@ -69,22 +70,30 @@ type defaultTidbClusterControl struct { // UpdateStatefulSet executes the core logic loop for a tidbcluster. func (tcc *defaultTidbClusterControl) UpdateTidbCluster(tc *v1alpha1.TidbCluster) error { + // perform the main update function and get the status + oldStatus := tc.Status.DeepCopy() oldPDReplicas := tc.Spec.PD.Replicas + oldTiKVReplicas := tc.Spec.TiKV.Replicas + oldTiDBReplicas := tc.Spec.TiDB.Replicas + var errs []error err := tcc.updateTidbCluster(tc) if err != nil { - return err + errs = append(errs, err) } - if !apiequality.Semantic.DeepEqual(&tc.Status, oldStatus) || tc.Spec.PD.Replicas != oldPDReplicas { - tc, err = tcc.tcControl.UpdateTidbCluster(tc.DeepCopy()) + replicasChanged := tc.Spec.PD.Replicas != oldPDReplicas || + tc.Spec.TiKV.Replicas != oldTiKVReplicas || + tc.Spec.TiDB.Replicas != oldTiDBReplicas + if !apiequality.Semantic.DeepEqual(&tc.Status, oldStatus) || replicasChanged { + _, err := tcc.tcControl.UpdateTidbCluster(tc.DeepCopy()) if err != nil { - return err + errs = append(errs, err) } } - return nil + return errorutils.NewAggregate(errs) } func (tcc *defaultTidbClusterControl) updateTidbCluster(tc *v1alpha1.TidbCluster) error { diff --git a/pkg/controller/tidbcluster/tidb_cluster_controller.go b/pkg/controller/tidbcluster/tidb_cluster_controller.go index ae1d5fca47..78d463acf6 100644 --- a/pkg/controller/tidbcluster/tidb_cluster_controller.go +++ b/pkg/controller/tidbcluster/tidb_cluster_controller.go @@ -25,6 +25,7 @@ import ( "github.com/pingcap/tidb-operator/pkg/controller" mm "github.com/pingcap/tidb-operator/pkg/manager/member" "github.com/pingcap/tidb-operator/pkg/manager/meta" + perrors "github.com/pkg/errors" apps "k8s.io/api/apps/v1beta1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -226,7 +227,11 @@ func (tcc *Controller) processNextWorkItem() bool { } defer tcc.queue.Done(key) if err := tcc.sync(key.(string)); err != nil { - utilruntime.HandleError(fmt.Errorf("Error syncing TidbCluster %v, requeuing: %v", key.(string), err)) + if perrors.Find(err, controller.IsRequeueError) != nil { + glog.Infof("TidbCluster: %v, still need sync: %v, requeuing", key.(string), err) + } else { + utilruntime.HandleError(fmt.Errorf("TidbCluster: %v, sync failed %v, requeuing", key.(string), err)) + } tcc.queue.AddRateLimited(key) } else { tcc.queue.Forget(key) diff --git a/pkg/manager/member/tikv_member_manager.go b/pkg/manager/member/tikv_member_manager.go index 83dfadbaf5..b98698379c 100644 --- a/pkg/manager/member/tikv_member_manager.go +++ b/pkg/manager/member/tikv_member_manager.go @@ -544,7 +544,7 @@ func (tkmm *tikvMemberManager) setStoreLabelsForTiKV(pdClient controller.PDClien return err } - glog.V(2).Infof("Pod: [%s/%s] is on node: [%s]. Node: [%s]'s labels: %v", ns, podName, nodeName, nodeName, ls) + glog.V(4).Infof("Pod: [%s/%s] is on node: [%s]. Node: [%s]'s labels: %v", ns, podName, nodeName, nodeName, ls) if !tkmm.storeLabelsEqualNodeLabels(store.Store.Labels, ls) { updated, err := pdClient.SetStoreLabels(store.Store.Id, ls) if err != nil { diff --git a/pkg/manager/member/tikv_scaler.go b/pkg/manager/member/tikv_scaler.go index bb573fe5a2..5dfde7d235 100644 --- a/pkg/manager/member/tikv_scaler.go +++ b/pkg/manager/member/tikv_scaler.go @@ -86,7 +86,7 @@ func (tsd *tikvScaler) ScaleIn(tc *v1alpha1.TidbCluster, oldSet *apps.StatefulSe } } resetReplicas(newSet, oldSet) - return fmt.Errorf("TiKV %s/%s store %d still in cluster, state: %s", ns, podName, id, state) + return controller.RequeueErrorf("TiKV %s/%s store %d still in cluster, state: %s", ns, podName, id, state) } } for _, store := range tc.Status.TiKV.TombstoneStores { diff --git a/pkg/manager/member/tikv_scaler_test.go b/pkg/manager/member/tikv_scaler_test.go index 18b8fcdb65..7d099f54fd 100644 --- a/pkg/manager/member/tikv_scaler_test.go +++ b/pkg/manager/member/tikv_scaler_test.go @@ -37,7 +37,7 @@ func TestTiKVScalerScaleOut(t *testing.T) { hasPVC bool hasDeferAnn bool pvcDeleteErr bool - err bool + errExpectFn func(*GomegaWithT, error) changed bool } @@ -75,11 +75,7 @@ func TestTiKVScalerScaleOut(t *testing.T) { } err := scaler.ScaleOut(tc, oldSet, newSet) - if test.err { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).NotTo(HaveOccurred()) - } + test.errExpectFn(g, err) if test.changed { g.Expect(int(*newSet.Spec.Replicas)).To(Equal(6)) } else { @@ -94,7 +90,7 @@ func TestTiKVScalerScaleOut(t *testing.T) { hasPVC: true, hasDeferAnn: false, pvcDeleteErr: false, - err: false, + errExpectFn: errExpectNil, changed: true, }, { @@ -103,7 +99,7 @@ func TestTiKVScalerScaleOut(t *testing.T) { hasPVC: true, hasDeferAnn: false, pvcDeleteErr: false, - err: false, + errExpectFn: errExpectNil, changed: false, }, { @@ -112,7 +108,7 @@ func TestTiKVScalerScaleOut(t *testing.T) { hasPVC: false, hasDeferAnn: false, pvcDeleteErr: false, - err: false, + errExpectFn: errExpectNil, changed: true, }, { @@ -121,7 +117,7 @@ func TestTiKVScalerScaleOut(t *testing.T) { hasPVC: true, hasDeferAnn: true, pvcDeleteErr: true, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, } @@ -140,7 +136,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr bool hasPVC bool pvcUpdateErr bool - err bool + errExpectFn func(*GomegaWithT, error) changed bool } @@ -177,11 +173,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { } err := scaler.ScaleIn(tc, oldSet, newSet) - if test.err { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).NotTo(HaveOccurred()) - } + test.errExpectFn(g, err) if test.changed { g.Expect(int(*newSet.Spec.Replicas)).To(Equal(4)) } else { @@ -197,7 +189,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, { @@ -207,7 +199,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: false, + errExpectFn: errExpectNil, changed: false, }, { @@ -220,7 +212,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, { @@ -235,7 +227,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, { @@ -250,7 +242,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, { @@ -265,7 +257,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, { @@ -275,7 +267,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: true, + errExpectFn: errExpectRequeue, changed: false, }, { @@ -285,7 +277,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: false, + errExpectFn: errExpectNil, changed: true, }, { @@ -300,7 +292,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: false, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, { @@ -310,7 +302,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: false, pvcUpdateErr: false, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, { @@ -320,7 +312,7 @@ func TestTiKVScalerScaleIn(t *testing.T) { delStoreErr: false, hasPVC: true, pvcUpdateErr: true, - err: true, + errExpectFn: errExpectNotNil, changed: false, }, } @@ -361,3 +353,7 @@ func tombstoneStoreFun(tc *v1alpha1.TidbCluster) { }, } } + +func errExpectRequeue(g *GomegaWithT, err error) { + g.Expect(controller.IsRequeueError(err)).To(Equal(true)) +} diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100644 index 0000000000..daf913b1b3 --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml new file mode 100644 index 0000000000..15e5a19266 --- /dev/null +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -0,0 +1,14 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.4.x + - 1.5.x + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - 1.10.x + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 0000000000..835ba3e755 --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 0000000000..6483ba2afb --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,52 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## License + +BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml new file mode 100644 index 0000000000..a932eade02 --- /dev/null +++ b/vendor/github.com/pkg/errors/appveyor.yml @@ -0,0 +1,32 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\pkg\errors +shallow_clone: true # for startup speed + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +# http://www.appveyor.com/docs/installed-software +install: + # some helpful output for debugging builds + - go version + - go env + # pre-installed MinGW at C:\MinGW is 32bit only + # but MSYS2 at C:\msys64 has mingw64 + - set PATH=C:\msys64\mingw64\bin;%PATH% + - gcc --version + - g++ --version + +build_script: + - go install -v ./... + +test_script: + - set PATH=C:\gopath\bin;%PATH% + - go test -v ./... + +#artifacts: +# - path: '%GOPATH%\bin\*.exe' +deploy: off diff --git a/vendor/github.com/pkg/errors/bench_test.go b/vendor/github.com/pkg/errors/bench_test.go new file mode 100644 index 0000000000..903b5f2d48 --- /dev/null +++ b/vendor/github.com/pkg/errors/bench_test.go @@ -0,0 +1,63 @@ +// +build go1.7 + +package errors + +import ( + "fmt" + "testing" + + stderrors "errors" +) + +func noErrors(at, depth int) error { + if at >= depth { + return stderrors.New("no error") + } + return noErrors(at+1, depth) +} + +func yesErrors(at, depth int) error { + if at >= depth { + return New("ye error") + } + return yesErrors(at+1, depth) +} + +// GlobalE is an exported global to store the result of benchmark results, +// preventing the compiler from optimising the benchmark functions away. +var GlobalE error + +func BenchmarkErrors(b *testing.B) { + type run struct { + stack int + std bool + } + runs := []run{ + {10, false}, + {10, true}, + {100, false}, + {100, true}, + {1000, false}, + {1000, true}, + } + for _, r := range runs { + part := "pkg/errors" + if r.std { + part = "errors" + } + name := fmt.Sprintf("%s-stack-%d", part, r.stack) + b.Run(name, func(b *testing.B) { + var err error + f := yesErrors + if r.std { + f = noErrors + } + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = f(0, r.stack) + } + b.StopTimer() + GlobalE = err + }) + } +} diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 0000000000..0168061fd5 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,337 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Annotate function returns a new error that adds context to the +// original error by recording a stack trace at the point Annotate is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Annotate(err, "read failed") +// } +// +// If additional control is required the errors.AddStack and errors.WithMessage +// functions destructure errors.Annotate into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Annotate constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Annotate to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// errors.Unwrap is also available: this will retrieve the next error in the chain. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Annotate, and Annotatef record a stack trace at the point they are invoked. +// This information can be retrieved with the StackTracer interface that returns +// a StackTrace. Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if stacked := errors.GetStackTracer(err); stacked != nil { +// for _, f := range stacked.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// See the documentation for Frame.Format for more details. +// +// errors.Find can be used to search for an error in the error chain. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// StackTraceAware is an optimization to avoid repetitive traversals of an error chain. +// HasStack checks for this marker first. +// Annotate/Wrap and Annotatef/Wrapf will produce this marker. +type StackTraceAware interface { + HasStack() bool +} + +// HasStack tells whether a StackTracer exists in the error chain +func HasStack(err error) bool { + if errWithStack, ok := err.(StackTraceAware); ok { + return errWithStack.HasStack() + } + return GetStackTracer(err) != nil +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +// +// Deprecated: use AddStack +func WithStack(err error) error { + if err == nil { + return nil + } + + return &withStack{ + err, + callers(), + } +} + +// AddStack is similar to WithStack. +// However, it will first check with HasStack to see if a stack trace already exists in the causer chain before creating another one. +func AddStack(err error) error { + if HasStack(err) { + return err + } + return WithStack(err) +} + +// GetStackTracer will return the first StackTracer in the causer chain. +// This function is used by AddStack to avoid creating redundant stack traces. +// +// You can also use the StackTracer interface on the returned error to get the stack trace. +func GetStackTracer(origErr error) StackTracer { + var stacked StackTracer + WalkDeep(origErr, func(err error) bool { + if stackTracer, ok := err.(StackTracer); ok { + stacked = stackTracer + return true + } + return false + }) + return stacked +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Annotate is called, and the supplied message. +// If err is nil, Annotate returns nil. +// +// Deprecated: use Annotate instead +func Wrap(err error, message string) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: message, + causeHasStack: hasStack, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Annotatef is call, and the format specifier. +// If err is nil, Annotatef returns nil. +// +// Deprecated: use Annotatef instead +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + causeHasStack: hasStack, + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + causeHasStack: HasStack(err), + } +} + +type withMessage struct { + cause error + msg string + causeHasStack bool +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } +func (w *withMessage) HasStack() bool { return w.causeHasStack } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + cause := Unwrap(err) + if cause == nil { + return err + } + return Cause(cause) +} + +// Unwrap uses causer to return the next error in the chain or nil. +// This goes one-level deeper, whereas Cause goes as far as possible +func Unwrap(err error) error { + type causer interface { + Cause() error + } + if unErr, ok := err.(causer); ok { + return unErr.Cause() + } + return nil +} + +// Find an error in the chain that matches a test function. +// returns nil if no error is found. +func Find(origErr error, test func(error) bool) error { + var foundErr error + WalkDeep(origErr, func(err error) bool { + if test(err) { + foundErr = err + return true + } + return false + }) + return foundErr +} diff --git a/vendor/github.com/pkg/errors/errors_test.go b/vendor/github.com/pkg/errors/errors_test.go new file mode 100644 index 0000000000..7f5e225f23 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors_test.go @@ -0,0 +1,371 @@ +package errors + +import ( + "errors" + "fmt" + "io" + "reflect" + "strconv" + "testing" +) + +func TestNew(t *testing.T) { + tests := []struct { + err string + want error + }{ + {"", fmt.Errorf("")}, + {"foo", fmt.Errorf("foo")}, + {"foo", New("foo")}, + {"string with format specifiers: %v", errors.New("string with format specifiers: %v")}, + } + + for _, tt := range tests { + got := New(tt.err) + if got.Error() != tt.want.Error() { + t.Errorf("New.Error(): got: %q, want %q", got, tt.want) + } + } +} + +func TestWrapNil(t *testing.T) { + got := Annotate(nil, "no error") + if got != nil { + t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWrap(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {Annotate(io.EOF, "read error"), "client error", "client error: read error: EOF"}, + } + + for _, tt := range tests { + got := Annotate(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) + } + } +} + +type nilError struct{} + +func (nilError) Error() string { return "nil error" } + +func TestCause(t *testing.T) { + x := New("error") + tests := []struct { + err error + want error + }{{ + // nil error is nil + err: nil, + want: nil, + }, { + // explicit nil error is nil + err: (error)(nil), + want: nil, + }, { + // typed nil is nil + err: (*nilError)(nil), + want: (*nilError)(nil), + }, { + // uncaused error is unaffected + err: io.EOF, + want: io.EOF, + }, { + // caused error returns cause + err: Annotate(io.EOF, "ignored"), + want: io.EOF, + }, { + err: x, // return from errors.New + want: x, + }, { + WithMessage(nil, "whoops"), + nil, + }, { + WithMessage(io.EOF, "whoops"), + io.EOF, + }, { + WithStack(nil), + nil, + }, { + WithStack(io.EOF), + io.EOF, + }, { + AddStack(nil), + nil, + }, { + AddStack(io.EOF), + io.EOF, + }} + + for i, tt := range tests { + got := Cause(tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want) + } + } +} + +func TestWrapfNil(t *testing.T) { + got := Annotate(nil, "no error") + if got != nil { + t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWrapf(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {Annotatef(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, + {Annotatef(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, + } + + for _, tt := range tests { + got := Annotatef(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) + } + } +} + +func TestErrorf(t *testing.T) { + tests := []struct { + err error + want string + }{ + {Errorf("read error without format specifiers"), "read error without format specifiers"}, + {Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"}, + } + + for _, tt := range tests { + got := tt.err.Error() + if got != tt.want { + t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want) + } + } +} + +func TestWithStackNil(t *testing.T) { + got := WithStack(nil) + if got != nil { + t.Errorf("WithStack(nil): got %#v, expected nil", got) + } + got = AddStack(nil) + if got != nil { + t.Errorf("AddStack(nil): got %#v, expected nil", got) + } +} + +func TestWithStack(t *testing.T) { + tests := []struct { + err error + want string + }{ + {io.EOF, "EOF"}, + {WithStack(io.EOF), "EOF"}, + } + + for _, tt := range tests { + got := WithStack(tt.err).Error() + if got != tt.want { + t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want) + } + } +} + +func TestAddStack(t *testing.T) { + tests := []struct { + err error + want string + }{ + {io.EOF, "EOF"}, + {AddStack(io.EOF), "EOF"}, + } + + for _, tt := range tests { + got := AddStack(tt.err).Error() + if got != tt.want { + t.Errorf("AddStack(%v): got: %v, want %v", tt.err, got, tt.want) + } + } +} + +func TestGetStackTracer(t *testing.T) { + orig := io.EOF + if GetStackTracer(orig) != nil { + t.Errorf("GetStackTracer: got: %v, want %v", GetStackTracer(orig), nil) + } + stacked := AddStack(orig) + if GetStackTracer(stacked).(error) != stacked { + t.Errorf("GetStackTracer(stacked): got: %v, want %v", GetStackTracer(stacked), stacked) + } + final := AddStack(stacked) + if GetStackTracer(final).(error) != stacked { + t.Errorf("GetStackTracer(final): got: %v, want %v", GetStackTracer(final), stacked) + } +} + +func TestAddStackDedup(t *testing.T) { + stacked := WithStack(io.EOF) + err := AddStack(stacked) + if err != stacked { + t.Errorf("AddStack: got: %+v, want %+v", err, stacked) + } + err = WithStack(stacked) + if err == stacked { + t.Errorf("WithStack: got: %v, don't want %v", err, stacked) + } +} + +func TestWithMessageNil(t *testing.T) { + got := WithMessage(nil, "no error") + if got != nil { + t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWithMessage(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"}, + } + + for _, tt := range tests { + got := WithMessage(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) + } + } +} + +// errors.New, etc values are not expected to be compared by value +// but the change in errors#27 made them incomparable. Assert that +// various kinds of errors have a functional equality operator, even +// if the result of that equality is always false. +func TestErrorEquality(t *testing.T) { + vals := []error{ + nil, + io.EOF, + errors.New("EOF"), + New("EOF"), + Errorf("EOF"), + Annotate(io.EOF, "EOF"), + Annotatef(io.EOF, "EOF%d", 2), + WithMessage(nil, "whoops"), + WithMessage(io.EOF, "whoops"), + WithStack(io.EOF), + WithStack(nil), + AddStack(io.EOF), + AddStack(nil), + } + + for i := range vals { + for j := range vals { + _ = vals[i] == vals[j] // mustn't panic + } + } +} + +func TestFind(t *testing.T) { + eNew := errors.New("error") + wrapped := Annotate(nilError{}, "nil") + tests := []struct { + err error + finder func(error) bool + found error + }{ + {io.EOF, func(_ error) bool { return true }, io.EOF}, + {io.EOF, func(_ error) bool { return false }, nil}, + {io.EOF, func(err error) bool { return err == io.EOF }, io.EOF}, + {io.EOF, func(err error) bool { return err != io.EOF }, nil}, + + {eNew, func(err error) bool { return true }, eNew}, + {eNew, func(err error) bool { return false }, nil}, + + {nilError{}, func(err error) bool { return true }, nilError{}}, + {nilError{}, func(err error) bool { return false }, nil}, + {nilError{}, func(err error) bool { _, ok := err.(nilError); return ok }, nilError{}}, + + {wrapped, func(err error) bool { return true }, wrapped}, + {wrapped, func(err error) bool { return false }, nil}, + {wrapped, func(err error) bool { _, ok := err.(nilError); return ok }, nilError{}}, + } + + for _, tt := range tests { + got := Find(tt.err, tt.finder) + if got != tt.found { + t.Errorf("WithMessage(%v): got: %q, want %q", tt.err, got, tt.found) + } + } +} + +type errWalkTest struct { + cause error + sub []error + v int +} + +func (e *errWalkTest) Error() string { + return strconv.Itoa(e.v) +} + +func (e *errWalkTest) Cause() error { + return e.cause +} + +func (e *errWalkTest) Errors() []error { + return e.sub +} + +func testFind(err error, v int) bool { + return WalkDeep(err, func(err error) bool { + e := err.(*errWalkTest) + return e.v == v + }) +} + +func TestWalkDeep(t *testing.T) { + err := &errWalkTest{ + sub: []error{ + &errWalkTest{ + v: 10, + cause: &errWalkTest{v: 11}, + }, + &errWalkTest{ + v: 20, + cause: &errWalkTest{v: 21, cause: &errWalkTest{v: 22}}, + }, + &errWalkTest{ + v: 30, + cause: &errWalkTest{v: 31}, + }, + }, + } + + if !testFind(err, 11) { + t.Errorf("not found in first cause chain") + } + + if !testFind(err, 22) { + t.Errorf("not found in siblings") + } + + if testFind(err, 32) { + t.Errorf("found not exists") + } +} diff --git a/vendor/github.com/pkg/errors/example_test.go b/vendor/github.com/pkg/errors/example_test.go new file mode 100644 index 0000000000..c1fc13e384 --- /dev/null +++ b/vendor/github.com/pkg/errors/example_test.go @@ -0,0 +1,205 @@ +package errors_test + +import ( + "fmt" + + "github.com/pkg/errors" +) + +func ExampleNew() { + err := errors.New("whoops") + fmt.Println(err) + + // Output: whoops +} + +func ExampleNew_printf() { + err := errors.New("whoops") + fmt.Printf("%+v", err) + + // Example output: + // whoops + // github.com/pkg/errors_test.ExampleNew_printf + // /home/dfc/src/github.com/pkg/errors/example_test.go:17 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 +} + +func ExampleWithMessage() { + cause := errors.New("whoops") + err := errors.WithMessage(cause, "oh noes") + fmt.Println(err) + + // Output: oh noes: whoops +} + +func ExampleWithStack() { + cause := errors.New("whoops") + err := errors.WithStack(cause) + fmt.Println(err) + + // Output: whoops +} + +func ExampleWithStack_printf() { + cause := errors.New("whoops") + err := errors.WithStack(cause) + fmt.Printf("%+v", err) + + // Example Output: + // whoops + // github.com/pkg/errors_test.ExampleWithStack_printf + // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55 + // testing.runExample + // /usr/lib/go/src/testing/example.go:114 + // testing.RunExamples + // /usr/lib/go/src/testing/example.go:38 + // testing.(*M).Run + // /usr/lib/go/src/testing/testing.go:744 + // main.main + // github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /usr/lib/go/src/runtime/proc.go:183 + // runtime.goexit + // /usr/lib/go/src/runtime/asm_amd64.s:2086 + // github.com/pkg/errors_test.ExampleWithStack_printf + // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56 + // testing.runExample + // /usr/lib/go/src/testing/example.go:114 + // testing.RunExamples + // /usr/lib/go/src/testing/example.go:38 + // testing.(*M).Run + // /usr/lib/go/src/testing/testing.go:744 + // main.main + // github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /usr/lib/go/src/runtime/proc.go:183 + // runtime.goexit + // /usr/lib/go/src/runtime/asm_amd64.s:2086 +} + +func ExampleWrap() { + cause := errors.New("whoops") + err := errors.Wrap(cause, "oh noes") + fmt.Println(err) + + // Output: oh noes: whoops +} + +func fn() error { + e1 := errors.New("error") + e2 := errors.Wrap(e1, "inner") + e3 := errors.Wrap(e2, "middle") + return errors.Wrap(e3, "outer") +} + +func ExampleCause() { + err := fn() + fmt.Println(err) + fmt.Println(errors.Cause(err)) + + // Output: outer: middle: inner: error + // error +} + +func ExampleWrap_extended() { + err := fn() + fmt.Printf("%+v\n", err) + + // Example output: + // error + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:47 + // github.com/pkg/errors_test.ExampleCause_printf + // /home/dfc/src/github.com/pkg/errors/example_test.go:63 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:104 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer +} + +func ExampleWrapf() { + cause := errors.New("whoops") + err := errors.Wrapf(cause, "oh noes #%d", 2) + fmt.Println(err) + + // Output: oh noes #2: whoops +} + +func ExampleErrorf_extended() { + err := errors.Errorf("whoops: %s", "foo") + fmt.Printf("%+v", err) + + // Example output: + // whoops: foo + // github.com/pkg/errors_test.ExampleErrorf + // /home/dfc/src/github.com/pkg/errors/example_test.go:101 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:102 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 +} + +func Example_stackTrace() { + type stackTracer interface { + StackTrace() errors.StackTrace + } + + err, ok := errors.Cause(fn()).(stackTracer) + if !ok { + panic("oops, err does not implement stackTracer") + } + + st := err.StackTrace() + fmt.Printf("%+v", st[0:2]) // top two frames + + // Example output: + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:47 + // github.com/pkg/errors_test.Example_stackTrace + // /home/dfc/src/github.com/pkg/errors/example_test.go:127 +} + +func ExampleCause_printf() { + err := errors.Wrap(func() error { + return func() error { + return errors.Errorf("hello %s", fmt.Sprintf("world")) + }() + }(), "failed") + + fmt.Printf("%v", err) + + // Output: failed: hello world +} diff --git a/vendor/github.com/pkg/errors/format_test.go b/vendor/github.com/pkg/errors/format_test.go new file mode 100644 index 0000000000..30275a3f5f --- /dev/null +++ b/vendor/github.com/pkg/errors/format_test.go @@ -0,0 +1,534 @@ +package errors + +import ( + "errors" + "fmt" + "io" + "regexp" + "strings" + "testing" +) + +func TestFormatNew(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + New("error"), + "%s", + "error", + }, { + New("error"), + "%v", + "error", + }, { + New("error"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatNew\n" + + "\t.+/github.com/pkg/errors/format_test.go:26", + }, { + New("error"), + "%q", + `"error"`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatErrorf(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Errorf("%s", "error"), + "%s", + "error", + }, { + Errorf("%s", "error"), + "%v", + "error", + }, { + Errorf("%s", "error"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatErrorf\n" + + "\t.+/github.com/pkg/errors/format_test.go:56", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWrap(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Annotate(New("error"), "error2"), + "%s", + "error2: error", + }, { + Annotate(New("error"), "error2"), + "%v", + "error2: error", + }, { + Annotate(New("error"), "error2"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:82", + }, { + Annotate(io.EOF, "error"), + "%s", + "error: EOF", + }, { + Annotate(io.EOF, "error"), + "%v", + "error: EOF", + }, { + Annotate(io.EOF, "error"), + "%+v", + "EOF\n" + + "error\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:96", + }, { + Annotate(Annotate(io.EOF, "error1"), "error2"), + "%+v", + "EOF\n" + + "error1\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:103\n", + }, { + Annotate(New("error with space"), "context"), + "%q", + `context: error with space`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWrapf(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Annotatef(io.EOF, "error%d", 2), + "%s", + "error2: EOF", + }, { + Annotatef(io.EOF, "error%d", 2), + "%v", + "error2: EOF", + }, { + Annotatef(io.EOF, "error%d", 2), + "%+v", + "EOF\n" + + "error2\n" + + "github.com/pkg/errors.TestFormatWrapf\n" + + "\t.+/github.com/pkg/errors/format_test.go:134", + }, { + Annotatef(New("error"), "error%d", 2), + "%s", + "error2: error", + }, { + Annotatef(New("error"), "error%d", 2), + "%v", + "error2: error", + }, { + Annotatef(New("error"), "error%d", 2), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatWrapf\n" + + "\t.+/github.com/pkg/errors/format_test.go:149", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWithStack(t *testing.T) { + tests := []struct { + error + format string + want []string + }{{ + WithStack(io.EOF), + "%s", + []string{"EOF"}, + }, { + WithStack(io.EOF), + "%v", + []string{"EOF"}, + }, { + WithStack(io.EOF), + "%+v", + []string{"EOF", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:175"}, + }, { + WithStack(New("error")), + "%s", + []string{"error"}, + }, { + WithStack(New("error")), + "%v", + []string{"error"}, + }, { + WithStack(New("error")), + "%+v", + []string{"error", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:189", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:189"}, + }, { + WithStack(WithStack(io.EOF)), + "%+v", + []string{"EOF", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:197", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:197"}, + }, { + WithStack(WithStack(Annotatef(io.EOF, "message"))), + "%+v", + []string{"EOF", + "message", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205"}, + }, { + WithStack(Errorf("error%d", 1)), + "%+v", + []string{"error1", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:216", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:216"}, + }} + + for i, tt := range tests { + testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) + } +} + +func TestFormatWithMessage(t *testing.T) { + tests := []struct { + error + format string + want []string + }{{ + WithMessage(New("error"), "error2"), + "%s", + []string{"error2: error"}, + }, { + WithMessage(New("error"), "error2"), + "%v", + []string{"error2: error"}, + }, { + WithMessage(New("error"), "error2"), + "%+v", + []string{ + "error", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:244", + "error2"}, + }, { + WithMessage(io.EOF, "addition1"), + "%s", + []string{"addition1: EOF"}, + }, { + WithMessage(io.EOF, "addition1"), + "%v", + []string{"addition1: EOF"}, + }, { + WithMessage(io.EOF, "addition1"), + "%+v", + []string{"EOF", "addition1"}, + }, { + WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), + "%v", + []string{"addition2: addition1: EOF"}, + }, { + WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), + "%+v", + []string{"EOF", "addition1", "addition2"}, + }, { + Annotate(WithMessage(io.EOF, "error1"), "error2"), + "%+v", + []string{"EOF", "error1", "error2", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:272"}, + }, { + WithMessage(Errorf("error%d", 1), "error2"), + "%+v", + []string{"error1", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:278", + "error2"}, + }, { + WithMessage(WithStack(io.EOF), "error"), + "%+v", + []string{ + "EOF", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:285", + "error"}, + }, { + WithMessage(Annotate(WithStack(io.EOF), "inside-error"), "outside-error"), + "%+v", + []string{ + "EOF", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:293", + "inside-error", + "outside-error"}, + }} + + for i, tt := range tests { + testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) + } +} + +/*func TestFormatGeneric(t *testing.T) { + starts := []struct { + err error + want []string + }{ + {New("new-error"), []string{ + "new-error", + "github.com/pkg/errors.TestFormatGeneric\n" + + "\t.+/github.com/pkg/errors/format_test.go:313"}, + }, {Errorf("errorf-error"), []string{ + "errorf-error", + "github.com/pkg/errors.TestFormatGeneric\n" + + "\t.+/github.com/pkg/errors/format_test.go:317"}, + }, {errors.New("errors-new-error"), []string{ + "errors-new-error"}, + }, + } + + wrappers := []wrapper{ + { + func(err error) error { return WithMessage(err, "with-message") }, + []string{"with-message"}, + }, { + func(err error) error { return WithStack(err) }, + []string{ + "github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" + + ".+/github.com/pkg/errors/format_test.go:331", + }, + }, { + func(err error) error { return Annotate(err, "wrap-error") }, + []string{ + "wrap-error", + "github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" + + ".+/github.com/pkg/errors/format_test.go:337", + }, + }, { + func(err error) error { return Annotatef(err, "wrapf-error%d", 1) }, + []string{ + "wrapf-error1", + "github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" + + ".+/github.com/pkg/errors/format_test.go:346", + }, + }, + } + + for s := range starts { + err := starts[s].err + want := starts[s].want + testFormatCompleteCompare(t, s, err, "%+v", want, false) + testGenericRecursive(t, err, want, wrappers, 3) + } +}*/ + +func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { + t.Helper() + got := fmt.Sprintf(format, arg) + gotLines := strings.SplitN(got, "\n", -1) + wantLines := strings.SplitN(want, "\n", -1) + + if len(wantLines) > len(gotLines) { + t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want) + return + } + + for i, w := range wantLines { + match, err := regexp.MatchString(w, gotLines[i]) + if err != nil { + t.Fatal(err) + } + if !match { + t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want) + } + } +} + +var stackLineR = regexp.MustCompile(`\.`) + +// parseBlocks parses input into a slice, where: +// - incase entry contains a newline, its a stacktrace +// - incase entry contains no newline, its a solo line. +// +// Detecting stack boundaries only works incase the WithStack-calls are +// to be found on the same line, thats why it is optionally here. +// +// Example use: +// +// for _, e := range blocks { +// if strings.ContainsAny(e, "\n") { +// // Match as stack +// } else { +// // Match as line +// } +// } +// +func parseBlocks(input string, detectStackboundaries bool) ([]string, error) { + var blocks []string + + stack := "" + wasStack := false + lines := map[string]bool{} // already found lines + + for _, l := range strings.Split(input, "\n") { + isStackLine := stackLineR.MatchString(l) + + switch { + case !isStackLine && wasStack: + blocks = append(blocks, stack, l) + stack = "" + lines = map[string]bool{} + case isStackLine: + if wasStack { + // Detecting two stacks after another, possible cause lines match in + // our tests due to WithStack(WithStack(io.EOF)) on same line. + if detectStackboundaries { + if lines[l] { + if len(stack) == 0 { + return nil, errors.New("len of block must not be zero here") + } + + blocks = append(blocks, stack) + stack = l + lines = map[string]bool{l: true} + continue + } + } + + stack = stack + "\n" + l + } else { + stack = l + } + lines[l] = true + case !isStackLine && !wasStack: + blocks = append(blocks, l) + default: + return nil, errors.New("must not happen") + } + + wasStack = isStackLine + } + + // Use up stack + if stack != "" { + blocks = append(blocks, stack) + } + return blocks, nil +} + +func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) { + gotStr := fmt.Sprintf(format, arg) + + got, err := parseBlocks(gotStr, detectStackBoundaries) + if err != nil { + t.Fatal(err) + } + + if len(got) != len(want) { + t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q", + n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr) + } + + for i := range got { + if strings.ContainsAny(want[i], "\n") { + // Match as stack + match, err := regexp.MatchString(want[i], got[i]) + if err != nil { + t.Fatal(err) + } + if !match { + t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n", + n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want)) + } + } else { + // Match as message + if got[i] != want[i] { + t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i]) + } + } + } +} + +type wrapper struct { + wrap func(err error) error + want []string +} + +func prettyBlocks(blocks []string) string { + var out []string + + for _, b := range blocks { + out = append(out, fmt.Sprintf("%v", b)) + } + + return " " + strings.Join(out, "\n ") +} + +func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) { + if len(beforeWant) == 0 { + panic("beforeWant must not be empty") + } + for _, w := range list { + if len(w.want) == 0 { + panic("want must not be empty") + } + + err := w.wrap(beforeErr) + + // Copy required cause append(beforeWant, ..) modified beforeWant subtly. + beforeCopy := make([]string, len(beforeWant)) + copy(beforeCopy, beforeWant) + + beforeWant := beforeCopy + last := len(beforeWant) - 1 + var want []string + + // Merge two stacks behind each other. + if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") { + want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...) + } else { + want = append(beforeWant, w.want...) + } + + testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false) + if maxDepth > 0 { + testGenericRecursive(t, err, want, list, maxDepth-1) + } + } +} diff --git a/vendor/github.com/pkg/errors/group.go b/vendor/github.com/pkg/errors/group.go new file mode 100644 index 0000000000..003932c95e --- /dev/null +++ b/vendor/github.com/pkg/errors/group.go @@ -0,0 +1,33 @@ +package errors + +// ErrorGroup is an interface for multiple errors that are not a chain. +// This happens for example when executing multiple operations in parallel. +type ErrorGroup interface { + Errors() []error +} + +// WalkDeep does a depth-first traversal of all errors. +// Any ErrorGroup is traversed (after going deep). +// The visitor function can return true to end the traversal early +// In that case, WalkDeep will return true, otherwise false. +func WalkDeep(err error, visitor func(err error) bool) bool { + // Go deep + unErr := err + for unErr != nil { + if done := visitor(unErr); done { + return true + } + unErr = Unwrap(unErr) + } + + // Go wide + if group, ok := err.(ErrorGroup); ok { + for _, err := range group.Errors() { + if early := WalkDeep(err, visitor); early { + return true + } + } + } + + return false +} diff --git a/vendor/github.com/pkg/errors/juju_adaptor.go b/vendor/github.com/pkg/errors/juju_adaptor.go new file mode 100644 index 0000000000..773a197086 --- /dev/null +++ b/vendor/github.com/pkg/errors/juju_adaptor.go @@ -0,0 +1,76 @@ +package errors + +import ( + "fmt" +) + +// ==================== juju adaptor start ======================== + +// Trace annotates err with a stack trace at the point WithStack was called. +// If err is nil or already contain stack trace return directly. +func Trace(err error) error { + return AddStack(err) +} + +func Annotate(err error, message string) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: message, + causeHasStack: hasStack, + } + if hasStack { + return err + } + return &withStack{ + err, + callers(), + } +} + +func Annotatef(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + causeHasStack: hasStack, + } + if hasStack { + return err + } + return &withStack{ + err, + callers(), + } +} + +// ErrorStack will format a stack trace if it is available, otherwise it will be Error() +func ErrorStack(err error) string { + if err == nil { + return "" + } + return fmt.Sprintf("%+v", err) +} + +// NotFoundf represents an error with not found message. +func NotFoundf(format string, args ...interface{}) error { + return Errorf(format+" not found", args...) +} + +// BadRequestf represents an error with bad request message. +func BadRequestf(format string, args ...interface{}) error { + return Errorf(format+" bad request", args...) +} + +// NotSupportedf represents an error with not supported message. +func NotSupportedf(format string, args ...interface{}) error { + return Errorf(format+" not supported", args...) +} + +// ==================== juju adaptor end ======================== diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 0000000000..6edd7e5699 --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,170 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// StackTracer retrieves the StackTrace +// Generally you would want to use the GetStackTracer function to do that. +type StackTracer interface { + StackTrace() StackTrace +} + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + return callersSkip(4) +} + +func callersSkip(skip int) *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(skip, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +// NewStack is for library implementers that want to generate a stack trace. +// Normally you should insted use AddStack to get an error with a stack trace. +// +// The result of this function can be turned into a stack trace by calling .StackTrace() +// +// This function takes an argument for the number of stack frames to skip. +// This avoids putting stack generation function calls like this one in the stack trace. +// A value of 0 will give you the line that called NewStack(0) +// A library author wrapping this in their own function will want to use a value of at least 1. +func NewStack(skip int) StackTracer { + return callersSkip(skip + 3) +} diff --git a/vendor/github.com/pkg/errors/stack_test.go b/vendor/github.com/pkg/errors/stack_test.go new file mode 100644 index 0000000000..560342581b --- /dev/null +++ b/vendor/github.com/pkg/errors/stack_test.go @@ -0,0 +1,289 @@ +package errors + +import ( + "fmt" + "runtime" + "testing" +) + +var initpc, _, _, _ = runtime.Caller(0) + +func TestFrameLine(t *testing.T) { + var tests = []struct { + Frame + want int + }{{ + Frame(initpc), + 9, + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) + }(), + 20, + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(1) + return Frame(pc) + }(), + 28, + }, { + Frame(0), // invalid PC + 0, + }} + + for _, tt := range tests { + got := tt.Frame.line() + want := tt.want + if want != got { + t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) + } + } +} + +type X struct{} + +func (x X) val() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) +} + +func (x *X) ptr() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) +} + +func TestFrameFormat(t *testing.T) { + var tests = []struct { + Frame + format string + want string + }{{ + Frame(initpc), + "%s", + "stack_test.go", + }, { + Frame(initpc), + "%+s", + "github.com/pkg/errors.init\n" + + "\t.+/github.com/pkg/errors/stack_test.go", + }, { + Frame(0), + "%s", + "unknown", + }, { + Frame(0), + "%+s", + "unknown", + }, { + Frame(initpc), + "%d", + "9", + }, { + Frame(0), + "%d", + "0", + }, { + Frame(initpc), + "%n", + "init", + }, { + func() Frame { + var x X + return x.ptr() + }(), + "%n", + `\(\*X\).ptr`, + }, { + func() Frame { + var x X + return x.val() + }(), + "%n", + "X.val", + }, { + Frame(0), + "%n", + "", + }, { + Frame(initpc), + "%v", + "stack_test.go:9", + }, { + Frame(initpc), + "%+v", + "github.com/pkg/errors.init\n" + + "\t.+/github.com/pkg/errors/stack_test.go:9", + }, { + Frame(0), + "%v", + "unknown:0", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.Frame, tt.format, tt.want) + } +} + +func TestFuncname(t *testing.T) { + tests := []struct { + name, want string + }{ + {"", ""}, + {"runtime.main", "main"}, + {"github.com/pkg/errors.funcname", "funcname"}, + {"funcname", "funcname"}, + {"io.copyBuffer", "copyBuffer"}, + {"main.(*R).Write", "(*R).Write"}, + } + + for _, tt := range tests { + got := funcname(tt.name) + want := tt.want + if got != want { + t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got) + } + } +} + +func TestStackTrace(t *testing.T) { + tests := []struct { + err error + want []string + }{{ + New("ooh"), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:154", + }, + }, { + Annotate(New("ooh"), "ahh"), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:159", // this is the stack of Wrap, not New + }, + }, { + Cause(Annotate(New("ooh"), "ahh")), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:164", // this is the stack of New + }, + }, { + func() error { return New("ooh") }(), []string{ + `github.com/pkg/errors.(func·009|TestStackTrace.func1)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New's caller + }, + }, { + Cause(func() error { + return func() error { + return Errorf("hello %s", fmt.Sprintf("world")) + }() + }()), []string{ + `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:178", // this is the stack of Errorf + `github.com/pkg/errors.(func·011|TestStackTrace.func2)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:179", // this is the stack of Errorf's caller + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Errorf's caller's caller + }, + }} + for i, tt := range tests { + ste, ok := tt.err.(interface { + StackTrace() StackTrace + }) + if !ok { + ste = tt.err.(interface { + Cause() error + }).Cause().(interface { + StackTrace() StackTrace + }) + } + st := ste.StackTrace() + for j, want := range tt.want { + testFormatRegexp(t, i, st[j], "%+v", want) + } + } +} + +// This comment helps to maintain original line numbers +// Perhaps this test is too fragile :) +func stackTrace() StackTrace { + return NewStack(0).StackTrace() + // This comment helps to maintain original line numbers + // Perhaps this test is too fragile :) +} + +func TestStackTraceFormat(t *testing.T) { + tests := []struct { + StackTrace + format string + want string + }{{ + nil, + "%s", + `\[\]`, + }, { + nil, + "%v", + `\[\]`, + }, { + nil, + "%+v", + "", + }, { + nil, + "%#v", + `\[\]errors.Frame\(nil\)`, + }, { + make(StackTrace, 0), + "%s", + `\[\]`, + }, { + make(StackTrace, 0), + "%v", + `\[\]`, + }, { + make(StackTrace, 0), + "%+v", + "", + }, { + make(StackTrace, 0), + "%#v", + `\[\]errors.Frame{}`, + }, { + stackTrace()[:2], + "%s", + `\[stack_test.go stack_test.go\]`, + }, { + stackTrace()[:2], + "%v", + `[stack_test.go:207 stack_test.go:254]`, + }, { + stackTrace()[:2], + "%+v", + "\n" + + "github.com/pkg/errors.stackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:210\n" + + "github.com/pkg/errors.TestStackTraceFormat\n" + + "\t.+/github.com/pkg/errors/stack_test.go:261", + }, { + stackTrace()[:2], + "%#v", + `\[\]errors.Frame{stack_test.go:210, stack_test.go:269}`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) + } +} + +func TestNewStack(t *testing.T) { + got := NewStack(1).StackTrace() + want := NewStack(1).StackTrace() + if got[0] != want[0] { + t.Errorf("NewStack(remove NewStack): want: %v, got: %v", want, got) + } + gotFirst := fmt.Sprintf("%+v", got[0])[0:15] + if gotFirst != "testing.tRunner" { + t.Errorf("NewStack(): want: %v, got: %+v", "testing.tRunner", gotFirst) + } +}