delete_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cmd
  14. import (
  15. "bytes"
  16. "net/http"
  17. "strings"
  18. "testing"
  19. "k8s.io/kubernetes/pkg/api"
  20. "k8s.io/kubernetes/pkg/api/errors"
  21. "k8s.io/kubernetes/pkg/api/testapi"
  22. "k8s.io/kubernetes/pkg/api/unversioned"
  23. "k8s.io/kubernetes/pkg/client/restclient"
  24. "k8s.io/kubernetes/pkg/client/unversioned/fake"
  25. )
  26. func TestDeleteObjectByTuple(t *testing.T) {
  27. _, _, rc := testData()
  28. f, tf, codec, ns := NewAPIFactory()
  29. tf.Printer = &testPrinter{}
  30. tf.Client = &fake.RESTClient{
  31. NegotiatedSerializer: ns,
  32. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  33. switch p, m := req.URL.Path, req.Method; {
  34. case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
  35. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
  36. default:
  37. // Ensures no GET is performed when deleting by name
  38. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  39. return nil, nil
  40. }
  41. }),
  42. }
  43. tf.Namespace = "test"
  44. buf := bytes.NewBuffer([]byte{})
  45. cmd := NewCmdDelete(f, buf)
  46. cmd.Flags().Set("namespace", "test")
  47. cmd.Flags().Set("cascade", "false")
  48. cmd.Flags().Set("output", "name")
  49. cmd.Run(cmd, []string{"replicationcontrollers/redis-master-controller"})
  50. if buf.String() != "replicationcontroller/redis-master-controller\n" {
  51. t.Errorf("unexpected output: %s", buf.String())
  52. }
  53. }
  54. func TestDeleteNamedObject(t *testing.T) {
  55. _, _, rc := testData()
  56. f, tf, codec, ns := NewAPIFactory()
  57. tf.Printer = &testPrinter{}
  58. tf.Client = &fake.RESTClient{
  59. NegotiatedSerializer: ns,
  60. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  61. switch p, m := req.URL.Path, req.Method; {
  62. case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
  63. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
  64. default:
  65. // Ensures no GET is performed when deleting by name
  66. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  67. return nil, nil
  68. }
  69. }),
  70. }
  71. tf.Namespace = "test"
  72. buf := bytes.NewBuffer([]byte{})
  73. cmd := NewCmdDelete(f, buf)
  74. cmd.Flags().Set("namespace", "test")
  75. cmd.Flags().Set("cascade", "false")
  76. cmd.Flags().Set("output", "name")
  77. cmd.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"})
  78. if buf.String() != "replicationcontroller/redis-master-controller\n" {
  79. t.Errorf("unexpected output: %s", buf.String())
  80. }
  81. }
  82. func TestDeleteObject(t *testing.T) {
  83. _, _, rc := testData()
  84. f, tf, codec, ns := NewAPIFactory()
  85. tf.Printer = &testPrinter{}
  86. tf.Client = &fake.RESTClient{
  87. NegotiatedSerializer: ns,
  88. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  89. switch p, m := req.URL.Path, req.Method; {
  90. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  91. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
  92. default:
  93. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  94. return nil, nil
  95. }
  96. }),
  97. }
  98. tf.Namespace = "test"
  99. buf := bytes.NewBuffer([]byte{})
  100. cmd := NewCmdDelete(f, buf)
  101. cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
  102. cmd.Flags().Set("cascade", "false")
  103. cmd.Flags().Set("output", "name")
  104. cmd.Run(cmd, []string{})
  105. // uses the name from the file, not the response
  106. if buf.String() != "replicationcontroller/redis-master\n" {
  107. t.Errorf("unexpected output: %s", buf.String())
  108. }
  109. }
  110. func TestDeleteObjectNotFound(t *testing.T) {
  111. f, tf, _, ns := NewAPIFactory()
  112. tf.Printer = &testPrinter{}
  113. tf.Client = &fake.RESTClient{
  114. NegotiatedSerializer: ns,
  115. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  116. switch p, m := req.URL.Path, req.Method; {
  117. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  118. return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
  119. default:
  120. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  121. return nil, nil
  122. }
  123. }),
  124. }
  125. tf.Namespace = "test"
  126. buf := bytes.NewBuffer([]byte{})
  127. cmd := NewCmdDelete(f, buf)
  128. options := &DeleteOptions{
  129. Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"},
  130. }
  131. cmd.Flags().Set("cascade", "false")
  132. cmd.Flags().Set("output", "name")
  133. err := RunDelete(f, buf, cmd, []string{}, options)
  134. if err == nil || !errors.IsNotFound(err) {
  135. t.Errorf("unexpected error: expected NotFound, got %v", err)
  136. }
  137. }
  138. func TestDeleteObjectIgnoreNotFound(t *testing.T) {
  139. f, tf, _, ns := NewAPIFactory()
  140. tf.Printer = &testPrinter{}
  141. tf.Client = &fake.RESTClient{
  142. NegotiatedSerializer: ns,
  143. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  144. switch p, m := req.URL.Path, req.Method; {
  145. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  146. return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
  147. default:
  148. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  149. return nil, nil
  150. }
  151. }),
  152. }
  153. tf.Namespace = "test"
  154. buf := bytes.NewBuffer([]byte{})
  155. cmd := NewCmdDelete(f, buf)
  156. cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
  157. cmd.Flags().Set("cascade", "false")
  158. cmd.Flags().Set("ignore-not-found", "true")
  159. cmd.Flags().Set("output", "name")
  160. cmd.Run(cmd, []string{})
  161. if buf.String() != "" {
  162. t.Errorf("unexpected output: %s", buf.String())
  163. }
  164. }
  165. func TestDeleteAllNotFound(t *testing.T) {
  166. _, svc, _ := testData()
  167. f, tf, codec, ns := NewAPIFactory()
  168. // Add an item to the list which will result in a 404 on delete
  169. svc.Items = append(svc.Items, api.Service{ObjectMeta: api.ObjectMeta{Name: "foo"}})
  170. notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
  171. tf.Printer = &testPrinter{}
  172. tf.Client = &fake.RESTClient{
  173. NegotiatedSerializer: ns,
  174. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  175. switch p, m := req.URL.Path, req.Method; {
  176. case p == "/namespaces/test/services" && m == "GET":
  177. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
  178. case p == "/namespaces/test/services/foo" && m == "DELETE":
  179. return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, notFoundError)}, nil
  180. case p == "/namespaces/test/services/baz" && m == "DELETE":
  181. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
  182. default:
  183. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  184. return nil, nil
  185. }
  186. }),
  187. }
  188. tf.Namespace = "test"
  189. buf := bytes.NewBuffer([]byte{})
  190. cmd := NewCmdDelete(f, buf)
  191. cmd.Flags().Set("all", "true")
  192. cmd.Flags().Set("cascade", "false")
  193. // Make sure we can explicitly choose to fail on NotFound errors, even with --all
  194. cmd.Flags().Set("ignore-not-found", "false")
  195. cmd.Flags().Set("output", "name")
  196. err := RunDelete(f, buf, cmd, []string{"services"}, &DeleteOptions{})
  197. if err == nil || !errors.IsNotFound(err) {
  198. t.Errorf("unexpected error: expected NotFound, got %v", err)
  199. }
  200. }
  201. func TestDeleteAllIgnoreNotFound(t *testing.T) {
  202. _, svc, _ := testData()
  203. f, tf, codec, ns := NewAPIFactory()
  204. // Add an item to the list which will result in a 404 on delete
  205. svc.Items = append(svc.Items, api.Service{ObjectMeta: api.ObjectMeta{Name: "foo"}})
  206. notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
  207. tf.Printer = &testPrinter{}
  208. tf.Client = &fake.RESTClient{
  209. NegotiatedSerializer: ns,
  210. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  211. switch p, m := req.URL.Path, req.Method; {
  212. case p == "/namespaces/test/services" && m == "GET":
  213. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
  214. case p == "/namespaces/test/services/foo" && m == "DELETE":
  215. return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, notFoundError)}, nil
  216. case p == "/namespaces/test/services/baz" && m == "DELETE":
  217. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
  218. default:
  219. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  220. return nil, nil
  221. }
  222. }),
  223. }
  224. tf.Namespace = "test"
  225. buf := bytes.NewBuffer([]byte{})
  226. cmd := NewCmdDelete(f, buf)
  227. cmd.Flags().Set("all", "true")
  228. cmd.Flags().Set("cascade", "false")
  229. cmd.Flags().Set("output", "name")
  230. cmd.Run(cmd, []string{"services"})
  231. if buf.String() != "service/baz\n" {
  232. t.Errorf("unexpected output: %s", buf.String())
  233. }
  234. }
  235. func TestDeleteMultipleObject(t *testing.T) {
  236. _, svc, rc := testData()
  237. f, tf, codec, ns := NewAPIFactory()
  238. tf.Printer = &testPrinter{}
  239. tf.Client = &fake.RESTClient{
  240. NegotiatedSerializer: ns,
  241. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  242. switch p, m := req.URL.Path, req.Method; {
  243. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  244. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
  245. case p == "/namespaces/test/services/frontend" && m == "DELETE":
  246. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
  247. default:
  248. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  249. return nil, nil
  250. }
  251. }),
  252. }
  253. tf.Namespace = "test"
  254. buf := bytes.NewBuffer([]byte{})
  255. cmd := NewCmdDelete(f, buf)
  256. cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
  257. cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml")
  258. cmd.Flags().Set("cascade", "false")
  259. cmd.Flags().Set("output", "name")
  260. cmd.Run(cmd, []string{})
  261. if buf.String() != "replicationcontroller/redis-master\nservice/frontend\n" {
  262. t.Errorf("unexpected output: %s", buf.String())
  263. }
  264. }
  265. func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
  266. _, svc, _ := testData()
  267. f, tf, codec, ns := NewAPIFactory()
  268. tf.Printer = &testPrinter{}
  269. tf.Client = &fake.RESTClient{
  270. NegotiatedSerializer: ns,
  271. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  272. switch p, m := req.URL.Path, req.Method; {
  273. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  274. return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
  275. case p == "/namespaces/test/services/frontend" && m == "DELETE":
  276. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
  277. default:
  278. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  279. return nil, nil
  280. }
  281. }),
  282. }
  283. tf.Namespace = "test"
  284. buf := bytes.NewBuffer([]byte{})
  285. cmd := NewCmdDelete(f, buf)
  286. options := &DeleteOptions{
  287. Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml", "../../../examples/guestbook/frontend-service.yaml"},
  288. }
  289. cmd.Flags().Set("cascade", "false")
  290. cmd.Flags().Set("output", "name")
  291. err := RunDelete(f, buf, cmd, []string{}, options)
  292. if err == nil || !errors.IsNotFound(err) {
  293. t.Errorf("unexpected error: expected NotFound, got %v", err)
  294. }
  295. if buf.String() != "service/frontend\n" {
  296. t.Errorf("unexpected output: %s", buf.String())
  297. }
  298. }
  299. func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
  300. _, svc, rc := testData()
  301. f, tf, codec, ns := NewAPIFactory()
  302. tf.Printer = &testPrinter{}
  303. tf.Client = &fake.RESTClient{
  304. NegotiatedSerializer: ns,
  305. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  306. switch p, m := req.URL.Path, req.Method; {
  307. case p == "/namespaces/test/replicationcontrollers/baz" && m == "DELETE":
  308. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
  309. case p == "/namespaces/test/replicationcontrollers/foo" && m == "DELETE":
  310. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
  311. case p == "/namespaces/test/services/baz" && m == "DELETE":
  312. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
  313. case p == "/namespaces/test/services/foo" && m == "DELETE":
  314. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
  315. default:
  316. // Ensures no GET is performed when deleting by name
  317. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  318. return nil, nil
  319. }
  320. }),
  321. }
  322. tf.Namespace = "test"
  323. buf := bytes.NewBuffer([]byte{})
  324. cmd := NewCmdDelete(f, buf)
  325. cmd.Flags().Set("namespace", "test")
  326. cmd.Flags().Set("cascade", "false")
  327. cmd.Flags().Set("output", "name")
  328. cmd.Run(cmd, []string{"replicationcontrollers,services", "baz", "foo"})
  329. if buf.String() != "replicationcontroller/baz\nreplicationcontroller/foo\nservice/baz\nservice/foo\n" {
  330. t.Errorf("unexpected output: %s", buf.String())
  331. }
  332. }
  333. func TestDeleteDirectory(t *testing.T) {
  334. _, _, rc := testData()
  335. f, tf, codec, ns := NewAPIFactory()
  336. tf.Printer = &testPrinter{}
  337. tf.Client = &fake.RESTClient{
  338. NegotiatedSerializer: ns,
  339. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  340. switch p, m := req.URL.Path, req.Method; {
  341. case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE":
  342. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
  343. default:
  344. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  345. return nil, nil
  346. }
  347. }),
  348. }
  349. tf.Namespace = "test"
  350. buf := bytes.NewBuffer([]byte{})
  351. cmd := NewCmdDelete(f, buf)
  352. cmd.Flags().Set("filename", "../../../examples/guestbook/legacy")
  353. cmd.Flags().Set("cascade", "false")
  354. cmd.Flags().Set("output", "name")
  355. cmd.Run(cmd, []string{})
  356. if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n" {
  357. t.Errorf("unexpected output: %s", buf.String())
  358. }
  359. }
  360. func TestDeleteMultipleSelector(t *testing.T) {
  361. pods, svc, _ := testData()
  362. f, tf, codec, ns := NewAPIFactory()
  363. tf.Printer = &testPrinter{}
  364. tf.Client = &fake.RESTClient{
  365. NegotiatedSerializer: ns,
  366. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  367. switch p, m := req.URL.Path, req.Method; {
  368. case p == "/namespaces/test/pods" && m == "GET":
  369. if req.URL.Query().Get(unversioned.LabelSelectorQueryParam(testapi.Default.GroupVersion().String())) != "a=b" {
  370. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  371. }
  372. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
  373. case p == "/namespaces/test/services" && m == "GET":
  374. if req.URL.Query().Get(unversioned.LabelSelectorQueryParam(testapi.Default.GroupVersion().String())) != "a=b" {
  375. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  376. }
  377. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
  378. case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
  379. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
  380. case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
  381. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
  382. default:
  383. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  384. return nil, nil
  385. }
  386. }),
  387. }
  388. tf.Namespace = "test"
  389. buf := bytes.NewBuffer([]byte{})
  390. cmd := NewCmdDelete(f, buf)
  391. cmd.Flags().Set("selector", "a=b")
  392. cmd.Flags().Set("cascade", "false")
  393. cmd.Flags().Set("output", "name")
  394. cmd.Run(cmd, []string{"pods,services"})
  395. if buf.String() != "pod/foo\npod/bar\nservice/baz\n" {
  396. t.Errorf("unexpected output: %s", buf.String())
  397. }
  398. }
  399. func TestResourceErrors(t *testing.T) {
  400. testCases := map[string]struct {
  401. args []string
  402. flags map[string]string
  403. errFn func(error) bool
  404. }{
  405. "no args": {
  406. args: []string{},
  407. errFn: func(err error) bool { return strings.Contains(err.Error(), "you must provide one or more resources") },
  408. },
  409. "resources but no selectors": {
  410. args: []string{"pods"},
  411. errFn: func(err error) bool {
  412. return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
  413. },
  414. },
  415. "multiple resources but no selectors": {
  416. args: []string{"pods,deployments"},
  417. errFn: func(err error) bool {
  418. return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
  419. },
  420. },
  421. }
  422. for k, testCase := range testCases {
  423. f, tf, _, _ := NewAPIFactory()
  424. tf.Printer = &testPrinter{}
  425. tf.Namespace = "test"
  426. tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}
  427. buf := bytes.NewBuffer([]byte{})
  428. cmd := NewCmdDelete(f, buf)
  429. cmd.SetOutput(buf)
  430. for k, v := range testCase.flags {
  431. cmd.Flags().Set(k, v)
  432. }
  433. err := RunDelete(f, buf, cmd, testCase.args, &DeleteOptions{})
  434. if !testCase.errFn(err) {
  435. t.Errorf("%s: unexpected error: %v", k, err)
  436. continue
  437. }
  438. if tf.Printer.(*testPrinter).Objects != nil {
  439. t.Errorf("unexpected print to default printer")
  440. }
  441. if buf.Len() > 0 {
  442. t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
  443. }
  444. }
  445. }