k8s.io/client-go@v0.31.1/rest/config_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package rest 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "net" 25 "net/http" 26 "net/url" 27 "path/filepath" 28 "reflect" 29 "strings" 30 "testing" 31 "time" 32 33 v1 "k8s.io/api/core/v1" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/client-go/kubernetes/scheme" 37 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 38 "k8s.io/client-go/transport" 39 "k8s.io/client-go/util/flowcontrol" 40 41 "github.com/google/go-cmp/cmp" 42 fuzz "github.com/google/gofuzz" 43 "github.com/stretchr/testify/assert" 44 ) 45 46 func TestIsConfigTransportTLS(t *testing.T) { 47 testCases := []struct { 48 Config *Config 49 TransportTLS bool 50 }{ 51 { 52 Config: &Config{}, 53 TransportTLS: false, 54 }, 55 { 56 Config: &Config{ 57 Host: "https://localhost", 58 }, 59 TransportTLS: true, 60 }, 61 { 62 Config: &Config{ 63 Host: "localhost", 64 TLSClientConfig: TLSClientConfig{ 65 CertFile: "foo", 66 }, 67 }, 68 TransportTLS: true, 69 }, 70 { 71 Config: &Config{ 72 Host: "///:://localhost", 73 TLSClientConfig: TLSClientConfig{ 74 CertFile: "foo", 75 }, 76 }, 77 TransportTLS: false, 78 }, 79 { 80 Config: &Config{ 81 Host: "1.2.3.4:567", 82 TLSClientConfig: TLSClientConfig{ 83 Insecure: true, 84 }, 85 }, 86 TransportTLS: true, 87 }, 88 } 89 for _, testCase := range testCases { 90 if err := SetKubernetesDefaults(testCase.Config); err != nil { 91 t.Errorf("setting defaults failed for %#v: %v", testCase.Config, err) 92 continue 93 } 94 useTLS := IsConfigTransportTLS(*testCase.Config) 95 if testCase.TransportTLS != useTLS { 96 t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config) 97 } 98 } 99 } 100 101 func TestSetKubernetesDefaultsUserAgent(t *testing.T) { 102 config := &Config{} 103 if err := SetKubernetesDefaults(config); err != nil { 104 t.Errorf("unexpected error: %v", err) 105 } 106 if !strings.Contains(config.UserAgent, "kubernetes/") { 107 t.Errorf("no user agent set: %#v", config) 108 } 109 } 110 111 func TestAdjustVersion(t *testing.T) { 112 assert := assert.New(t) 113 assert.Equal("1.2.3", adjustVersion("1.2.3-alpha4")) 114 assert.Equal("1.2.3", adjustVersion("1.2.3-alpha")) 115 assert.Equal("1.2.3", adjustVersion("1.2.3")) 116 assert.Equal("unknown", adjustVersion("")) 117 } 118 119 func TestAdjustCommit(t *testing.T) { 120 assert := assert.New(t) 121 assert.Equal("1234567", adjustCommit("1234567890")) 122 assert.Equal("123456", adjustCommit("123456")) 123 assert.Equal("unknown", adjustCommit("")) 124 } 125 126 func TestAdjustCommand(t *testing.T) { 127 assert := assert.New(t) 128 assert.Equal("beans", adjustCommand(filepath.Join("home", "bob", "Downloads", "beans"))) 129 assert.Equal("beans", adjustCommand(filepath.Join(".", "beans"))) 130 assert.Equal("beans", adjustCommand("beans")) 131 assert.Equal("unknown", adjustCommand("")) 132 } 133 134 func TestBuildUserAgent(t *testing.T) { 135 assert.New(t).Equal( 136 "lynx/nicest (beos/itanium) kubernetes/baaaaaaaaad", 137 buildUserAgent( 138 "lynx", "nicest", 139 "beos", "itanium", "baaaaaaaaad")) 140 } 141 142 // This function untestable since it doesn't accept arguments. 143 func TestDefaultKubernetesUserAgent(t *testing.T) { 144 assert.New(t).Contains(DefaultKubernetesUserAgent(), "kubernetes") 145 } 146 147 func TestRESTClientRequires(t *testing.T) { 148 if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{NegotiatedSerializer: scheme.Codecs}}); err == nil { 149 t.Errorf("unexpected non-error") 150 } 151 if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &v1.SchemeGroupVersion}}); err == nil { 152 t.Errorf("unexpected non-error") 153 } 154 if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}}); err != nil { 155 t.Errorf("unexpected error: %v", err) 156 } 157 } 158 159 func TestRESTClientLimiter(t *testing.T) { 160 testCases := []struct { 161 Name string 162 Config Config 163 Limiter flowcontrol.RateLimiter 164 }{ 165 { 166 Name: "with no QPS", 167 Config: Config{}, 168 Limiter: flowcontrol.NewTokenBucketRateLimiter(5, 10), 169 }, 170 { 171 Name: "with QPS:10", 172 Config: Config{QPS: 10}, 173 Limiter: flowcontrol.NewTokenBucketRateLimiter(10, 10), 174 }, 175 { 176 Name: "with QPS:-1", 177 Config: Config{QPS: -1}, 178 Limiter: nil, 179 }, 180 { 181 Name: "with RateLimiter", 182 Config: Config{ 183 RateLimiter: flowcontrol.NewTokenBucketRateLimiter(11, 12), 184 }, 185 Limiter: flowcontrol.NewTokenBucketRateLimiter(11, 12), 186 }, 187 } 188 for _, testCase := range testCases { 189 t.Run("Versioned_"+testCase.Name, func(t *testing.T) { 190 config := testCase.Config 191 config.Host = "127.0.0.1" 192 config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs} 193 client, err := RESTClientFor(&config) 194 if err != nil { 195 t.Fatalf("unexpected error: %v", err) 196 } 197 if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) { 198 t.Fatalf("unexpected rate limiter: %#v, expected %#v at %s", client.rateLimiter, testCase.Limiter, testCase.Name) 199 } 200 }) 201 t.Run("Unversioned_"+testCase.Name, func(t *testing.T) { 202 config := testCase.Config 203 config.Host = "127.0.0.1" 204 config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs} 205 client, err := UnversionedRESTClientFor(&config) 206 if err != nil { 207 t.Fatalf("unexpected error: %v", err) 208 } 209 if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) { 210 t.Fatalf("unexpected rate limiter: %#v, expected %#v at %s", client.rateLimiter, testCase.Limiter, testCase.Name) 211 } 212 }) 213 } 214 } 215 216 type fakeLimiter struct { 217 FakeSaturation float64 218 FakeQPS float32 219 } 220 221 func (t *fakeLimiter) TryAccept() bool { 222 return true 223 } 224 225 func (t *fakeLimiter) Saturation() float64 { 226 return t.FakeSaturation 227 } 228 229 func (t *fakeLimiter) QPS() float32 { 230 return t.FakeQPS 231 } 232 233 func (t *fakeLimiter) Wait(ctx context.Context) error { 234 return nil 235 } 236 237 func (t *fakeLimiter) Stop() {} 238 239 func (t *fakeLimiter) Accept() {} 240 241 type fakeCodec struct{} 242 243 func (c *fakeCodec) Decode([]byte, *schema.GroupVersionKind, runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 244 return nil, nil, nil 245 } 246 247 func (c *fakeCodec) Encode(obj runtime.Object, stream io.Writer) error { 248 return nil 249 } 250 251 func (c *fakeCodec) Identifier() runtime.Identifier { 252 return runtime.Identifier("fake") 253 } 254 255 type fakeRoundTripper struct{} 256 257 func (r *fakeRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { 258 return nil, nil 259 } 260 261 var fakeWrapperFunc = func(http.RoundTripper) http.RoundTripper { 262 return &fakeRoundTripper{} 263 } 264 265 type fakeWarningHandler struct{} 266 267 func (f fakeWarningHandler) HandleWarningHeader(code int, agent string, message string) {} 268 269 type fakeNegotiatedSerializer struct{} 270 271 func (n *fakeNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { 272 return nil 273 } 274 275 func (n *fakeNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { 276 return &fakeCodec{} 277 } 278 279 func (n *fakeNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { 280 return &fakeCodec{} 281 } 282 283 var fakeDialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) { 284 return nil, fakeDialerError 285 } 286 287 var fakeDialerError = errors.New("fakedialer") 288 289 func fakeProxyFunc(*http.Request) (*url.URL, error) { 290 return nil, errors.New("fakeproxy") 291 } 292 293 type fakeAuthProviderConfigPersister struct{} 294 295 func (fakeAuthProviderConfigPersister) Persist(map[string]string) error { 296 return fakeAuthProviderConfigPersisterError 297 } 298 299 var fakeAuthProviderConfigPersisterError = errors.New("fakeAuthProviderConfigPersisterError") 300 301 func TestAnonymousAuthConfig(t *testing.T) { 302 f := fuzz.New().NilChance(0.0).NumElements(1, 1) 303 f.Funcs( 304 func(r *runtime.Codec, f fuzz.Continue) { 305 codec := &fakeCodec{} 306 f.Fuzz(codec) 307 *r = codec 308 }, 309 func(r *http.RoundTripper, f fuzz.Continue) { 310 roundTripper := &fakeRoundTripper{} 311 f.Fuzz(roundTripper) 312 *r = roundTripper 313 }, 314 func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) { 315 *fn = fakeWrapperFunc 316 }, 317 func(fn *transport.WrapperFunc, f fuzz.Continue) { 318 *fn = fakeWrapperFunc 319 }, 320 func(r *runtime.NegotiatedSerializer, f fuzz.Continue) { 321 serializer := &fakeNegotiatedSerializer{} 322 f.Fuzz(serializer) 323 *r = serializer 324 }, 325 func(r *flowcontrol.RateLimiter, f fuzz.Continue) { 326 limiter := &fakeLimiter{} 327 f.Fuzz(limiter) 328 *r = limiter 329 }, 330 func(h *WarningHandler, f fuzz.Continue) { 331 *h = &fakeWarningHandler{} 332 }, 333 // Authentication does not require fuzzer 334 func(r *AuthProviderConfigPersister, f fuzz.Continue) {}, 335 func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) { 336 r.Config = map[string]string{} 337 }, 338 func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) { 339 *r = fakeDialFunc 340 }, 341 func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) { 342 *r = fakeProxyFunc 343 }, 344 func(r *runtime.Object, f fuzz.Continue) { 345 unknown := &runtime.Unknown{} 346 f.Fuzz(unknown) 347 *r = unknown 348 }, 349 ) 350 for i := 0; i < 20; i++ { 351 original := &Config{} 352 f.Fuzz(original) 353 actual := AnonymousClientConfig(original) 354 expected := *original 355 356 // this is the list of known security related fields, add to this list if a new field 357 // is added to Config, update AnonymousClientConfig to preserve the field otherwise. 358 expected.Impersonate = ImpersonationConfig{} 359 expected.BearerToken = "" 360 expected.BearerTokenFile = "" 361 expected.Username = "" 362 expected.Password = "" 363 expected.AuthProvider = nil 364 expected.AuthConfigPersister = nil 365 expected.ExecProvider = nil 366 expected.TLSClientConfig.CertData = nil 367 expected.TLSClientConfig.CertFile = "" 368 expected.TLSClientConfig.KeyData = nil 369 expected.TLSClientConfig.KeyFile = "" 370 expected.Transport = nil 371 expected.WrapTransport = nil 372 373 if actual.Dial != nil { 374 _, actualError := actual.Dial(context.Background(), "", "") 375 _, expectedError := expected.Dial(context.Background(), "", "") 376 if !reflect.DeepEqual(expectedError, actualError) { 377 t.Fatalf("AnonymousClientConfig dropped the Dial field") 378 } 379 } 380 actual.Dial = nil 381 expected.Dial = nil 382 383 if actual.Proxy != nil { 384 _, actualError := actual.Proxy(nil) 385 _, expectedError := expected.Proxy(nil) 386 if !reflect.DeepEqual(expectedError, actualError) { 387 t.Fatalf("AnonymousClientConfig dropped the Proxy field") 388 } 389 } 390 actual.Proxy = nil 391 expected.Proxy = nil 392 393 if diff := cmp.Diff(*actual, expected); diff != "" { 394 t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff) 395 } 396 } 397 } 398 399 func TestCopyConfig(t *testing.T) { 400 f := fuzz.New().NilChance(0.0).NumElements(1, 1) 401 f.Funcs( 402 func(r *runtime.Codec, f fuzz.Continue) { 403 codec := &fakeCodec{} 404 f.Fuzz(codec) 405 *r = codec 406 }, 407 func(r *http.RoundTripper, f fuzz.Continue) { 408 roundTripper := &fakeRoundTripper{} 409 f.Fuzz(roundTripper) 410 *r = roundTripper 411 }, 412 func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) { 413 *fn = fakeWrapperFunc 414 }, 415 func(fn *transport.WrapperFunc, f fuzz.Continue) { 416 *fn = fakeWrapperFunc 417 }, 418 func(r *runtime.NegotiatedSerializer, f fuzz.Continue) { 419 serializer := &fakeNegotiatedSerializer{} 420 f.Fuzz(serializer) 421 *r = serializer 422 }, 423 func(r *flowcontrol.RateLimiter, f fuzz.Continue) { 424 limiter := &fakeLimiter{} 425 f.Fuzz(limiter) 426 *r = limiter 427 }, 428 func(h *WarningHandler, f fuzz.Continue) { 429 *h = &fakeWarningHandler{} 430 }, 431 func(r *AuthProviderConfigPersister, f fuzz.Continue) { 432 *r = fakeAuthProviderConfigPersister{} 433 }, 434 func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) { 435 *r = fakeDialFunc 436 }, 437 func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) { 438 *r = fakeProxyFunc 439 }, 440 func(r *runtime.Object, f fuzz.Continue) { 441 unknown := &runtime.Unknown{} 442 f.Fuzz(unknown) 443 *r = unknown 444 }, 445 ) 446 for i := 0; i < 20; i++ { 447 original := &Config{} 448 f.Fuzz(original) 449 actual := CopyConfig(original) 450 expected := *original 451 452 // this is the list of known risky fields, add to this list if a new field 453 // is added to Config, update CopyConfig to preserve the field otherwise. 454 455 // The DeepEqual cannot handle the func comparison, so we just verify if the 456 // function return the expected object. 457 if actual.WrapTransport == nil || !reflect.DeepEqual(expected.WrapTransport(nil), &fakeRoundTripper{}) { 458 t.Fatalf("CopyConfig dropped the WrapTransport field") 459 } 460 actual.WrapTransport = nil 461 expected.WrapTransport = nil 462 463 if actual.Dial != nil { 464 _, actualError := actual.Dial(context.Background(), "", "") 465 _, expectedError := expected.Dial(context.Background(), "", "") 466 if !reflect.DeepEqual(expectedError, actualError) { 467 t.Fatalf("CopyConfig dropped the Dial field") 468 } 469 } 470 actual.Dial = nil 471 expected.Dial = nil 472 473 if actual.AuthConfigPersister != nil { 474 actualError := actual.AuthConfigPersister.Persist(nil) 475 expectedError := expected.AuthConfigPersister.Persist(nil) 476 if !reflect.DeepEqual(expectedError, actualError) { 477 t.Fatalf("CopyConfig dropped the Dial field") 478 } 479 } 480 actual.AuthConfigPersister = nil 481 expected.AuthConfigPersister = nil 482 483 if actual.Proxy != nil { 484 _, actualError := actual.Proxy(nil) 485 _, expectedError := expected.Proxy(nil) 486 if !reflect.DeepEqual(expectedError, actualError) { 487 t.Fatalf("CopyConfig dropped the Proxy field") 488 } 489 } 490 actual.Proxy = nil 491 expected.Proxy = nil 492 493 if diff := cmp.Diff(*actual, expected); diff != "" { 494 t.Fatalf("CopyConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff) 495 } 496 } 497 } 498 499 func TestConfigStringer(t *testing.T) { 500 formatBytes := func(b []byte) string { 501 // %#v for []byte always pre-pends "[]byte{". 502 // %#v for struct with []byte field always pre-pends "[]uint8{". 503 return strings.Replace(fmt.Sprintf("%#v", b), "byte", "uint8", 1) 504 } 505 tests := []struct { 506 desc string 507 c *Config 508 expectContent []string 509 prohibitContent []string 510 }{ 511 { 512 desc: "nil config", 513 c: nil, 514 expectContent: []string{"<nil>"}, 515 }, 516 { 517 desc: "non-sensitive config", 518 c: &Config{ 519 Host: "localhost:8080", 520 APIPath: "v1", 521 UserAgent: "gobot", 522 }, 523 expectContent: []string{"localhost:8080", "v1", "gobot"}, 524 }, 525 { 526 desc: "sensitive config", 527 c: &Config{ 528 Host: "localhost:8080", 529 Username: "gopher", 530 Password: "g0ph3r", 531 BearerToken: "1234567890", 532 TLSClientConfig: TLSClientConfig{ 533 CertFile: "a.crt", 534 KeyFile: "a.key", 535 CertData: []byte("fake cert"), 536 KeyData: []byte("fake key"), 537 }, 538 AuthProvider: &clientcmdapi.AuthProviderConfig{ 539 Config: map[string]string{"secret": "s3cr3t"}, 540 }, 541 ExecProvider: &clientcmdapi.ExecConfig{ 542 Args: []string{"secret"}, 543 Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, 544 Config: &runtime.Unknown{Raw: []byte("here is some config data")}, 545 }, 546 }, 547 expectContent: []string{ 548 "localhost:8080", 549 "gopher", 550 "a.crt", 551 "a.key", 552 "--- REDACTED ---", 553 formatBytes([]byte("--- REDACTED ---")), 554 formatBytes([]byte("--- TRUNCATED ---")), 555 }, 556 prohibitContent: []string{ 557 "g0ph3r", 558 "1234567890", 559 formatBytes([]byte("fake cert")), 560 formatBytes([]byte("fake key")), 561 "secret", 562 "s3cr3t", 563 "here is some config data", 564 formatBytes([]byte("super secret password")), 565 }, 566 }, 567 } 568 569 for _, tt := range tests { 570 t.Run(tt.desc, func(t *testing.T) { 571 got := tt.c.String() 572 t.Logf("formatted config: %q", got) 573 574 for _, expect := range tt.expectContent { 575 if !strings.Contains(got, expect) { 576 t.Errorf("missing expected string %q", expect) 577 } 578 } 579 for _, prohibit := range tt.prohibitContent { 580 if strings.Contains(got, prohibit) { 581 t.Errorf("found prohibited string %q", prohibit) 582 } 583 } 584 }) 585 } 586 } 587 588 func TestConfigSprint(t *testing.T) { 589 c := &Config{ 590 Host: "localhost:8080", 591 APIPath: "v1", 592 ContentConfig: ContentConfig{ 593 AcceptContentTypes: "application/json", 594 ContentType: "application/json", 595 }, 596 Username: "gopher", 597 Password: "g0ph3r", 598 BearerToken: "1234567890", 599 Impersonate: ImpersonationConfig{ 600 UserName: "gopher2", 601 UID: "uid123", 602 }, 603 AuthProvider: &clientcmdapi.AuthProviderConfig{ 604 Name: "gopher", 605 Config: map[string]string{"secret": "s3cr3t"}, 606 }, 607 AuthConfigPersister: fakeAuthProviderConfigPersister{}, 608 ExecProvider: &clientcmdapi.ExecConfig{ 609 Command: "sudo", 610 Args: []string{"secret"}, 611 Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, 612 ProvideClusterInfo: true, 613 Config: &runtime.Unknown{Raw: []byte("super secret password")}, 614 }, 615 TLSClientConfig: TLSClientConfig{ 616 CertFile: "a.crt", 617 KeyFile: "a.key", 618 CertData: []byte("fake cert"), 619 KeyData: []byte("fake key"), 620 NextProtos: []string{"h2", "http/1.1"}, 621 }, 622 UserAgent: "gobot", 623 Transport: &fakeRoundTripper{}, 624 WrapTransport: fakeWrapperFunc, 625 QPS: 1, 626 Burst: 2, 627 RateLimiter: &fakeLimiter{}, 628 WarningHandler: fakeWarningHandler{}, 629 Timeout: 3 * time.Second, 630 Dial: fakeDialFunc, 631 Proxy: fakeProxyFunc, 632 } 633 want := fmt.Sprintf( 634 `&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", UID:"uid123", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.ExecConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: "", ProvideClusterInfo: true, Config: runtime.Object(--- REDACTED ---), StdinUnavailable: false}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`, 635 c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc, 636 ) 637 638 for _, f := range []string{"%s", "%v", "%+v", "%#v"} { 639 if got := fmt.Sprintf(f, c); want != got { 640 t.Errorf("fmt.Sprintf(%q, c)\ngot: %q\nwant: %q", f, got, want) 641 } 642 } 643 }