k8s.io/client-go@v0.22.2/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 Config: Config{}, 167 Limiter: flowcontrol.NewTokenBucketRateLimiter(5, 10), 168 }, 169 { 170 Config: Config{QPS: 10}, 171 Limiter: flowcontrol.NewTokenBucketRateLimiter(10, 10), 172 }, 173 { 174 Config: Config{QPS: -1}, 175 Limiter: nil, 176 }, 177 { 178 Config: Config{ 179 RateLimiter: flowcontrol.NewTokenBucketRateLimiter(11, 12), 180 }, 181 Limiter: flowcontrol.NewTokenBucketRateLimiter(11, 12), 182 }, 183 } 184 for _, testCase := range testCases { 185 t.Run("Versioned_"+testCase.Name, func(t *testing.T) { 186 config := testCase.Config 187 config.Host = "127.0.0.1" 188 config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs} 189 client, err := RESTClientFor(&config) 190 if err != nil { 191 t.Fatalf("unexpected error: %v", err) 192 } 193 if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) { 194 t.Fatalf("unexpected rate limiter: %#v", client.rateLimiter) 195 } 196 }) 197 t.Run("Unversioned_"+testCase.Name, func(t *testing.T) { 198 config := testCase.Config 199 config.Host = "127.0.0.1" 200 config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs} 201 client, err := UnversionedRESTClientFor(&config) 202 if err != nil { 203 t.Fatalf("unexpected error: %v", err) 204 } 205 if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) { 206 t.Fatalf("unexpected rate limiter: %#v", client.rateLimiter) 207 } 208 }) 209 } 210 } 211 212 type fakeLimiter struct { 213 FakeSaturation float64 214 FakeQPS float32 215 } 216 217 func (t *fakeLimiter) TryAccept() bool { 218 return true 219 } 220 221 func (t *fakeLimiter) Saturation() float64 { 222 return t.FakeSaturation 223 } 224 225 func (t *fakeLimiter) QPS() float32 { 226 return t.FakeQPS 227 } 228 229 func (t *fakeLimiter) Wait(ctx context.Context) error { 230 return nil 231 } 232 233 func (t *fakeLimiter) Stop() {} 234 235 func (t *fakeLimiter) Accept() {} 236 237 type fakeCodec struct{} 238 239 func (c *fakeCodec) Decode([]byte, *schema.GroupVersionKind, runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 240 return nil, nil, nil 241 } 242 243 func (c *fakeCodec) Encode(obj runtime.Object, stream io.Writer) error { 244 return nil 245 } 246 247 func (c *fakeCodec) Identifier() runtime.Identifier { 248 return runtime.Identifier("fake") 249 } 250 251 type fakeRoundTripper struct{} 252 253 func (r *fakeRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { 254 return nil, nil 255 } 256 257 var fakeWrapperFunc = func(http.RoundTripper) http.RoundTripper { 258 return &fakeRoundTripper{} 259 } 260 261 type fakeWarningHandler struct{} 262 263 func (f fakeWarningHandler) HandleWarningHeader(code int, agent string, message string) {} 264 265 type fakeNegotiatedSerializer struct{} 266 267 func (n *fakeNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { 268 return nil 269 } 270 271 func (n *fakeNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { 272 return &fakeCodec{} 273 } 274 275 func (n *fakeNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { 276 return &fakeCodec{} 277 } 278 279 var fakeDialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) { 280 return nil, fakeDialerError 281 } 282 283 var fakeDialerError = errors.New("fakedialer") 284 285 func fakeProxyFunc(*http.Request) (*url.URL, error) { 286 return nil, errors.New("fakeproxy") 287 } 288 289 type fakeAuthProviderConfigPersister struct{} 290 291 func (fakeAuthProviderConfigPersister) Persist(map[string]string) error { 292 return fakeAuthProviderConfigPersisterError 293 } 294 295 var fakeAuthProviderConfigPersisterError = errors.New("fakeAuthProviderConfigPersisterError") 296 297 func TestAnonymousConfig(t *testing.T) { 298 f := fuzz.New().NilChance(0.0).NumElements(1, 1) 299 f.Funcs( 300 func(r *runtime.Codec, f fuzz.Continue) { 301 codec := &fakeCodec{} 302 f.Fuzz(codec) 303 *r = codec 304 }, 305 func(r *http.RoundTripper, f fuzz.Continue) { 306 roundTripper := &fakeRoundTripper{} 307 f.Fuzz(roundTripper) 308 *r = roundTripper 309 }, 310 func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) { 311 *fn = fakeWrapperFunc 312 }, 313 func(fn *transport.WrapperFunc, f fuzz.Continue) { 314 *fn = fakeWrapperFunc 315 }, 316 func(r *runtime.NegotiatedSerializer, f fuzz.Continue) { 317 serializer := &fakeNegotiatedSerializer{} 318 f.Fuzz(serializer) 319 *r = serializer 320 }, 321 func(r *flowcontrol.RateLimiter, f fuzz.Continue) { 322 limiter := &fakeLimiter{} 323 f.Fuzz(limiter) 324 *r = limiter 325 }, 326 func(h *WarningHandler, f fuzz.Continue) { 327 *h = &fakeWarningHandler{} 328 }, 329 // Authentication does not require fuzzer 330 func(r *AuthProviderConfigPersister, f fuzz.Continue) {}, 331 func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) { 332 r.Config = map[string]string{} 333 }, 334 func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) { 335 *r = fakeDialFunc 336 }, 337 func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) { 338 *r = fakeProxyFunc 339 }, 340 func(r *runtime.Object, f fuzz.Continue) { 341 unknown := &runtime.Unknown{} 342 f.Fuzz(unknown) 343 *r = unknown 344 }, 345 ) 346 for i := 0; i < 20; i++ { 347 original := &Config{} 348 f.Fuzz(original) 349 actual := AnonymousClientConfig(original) 350 expected := *original 351 352 // this is the list of known security related fields, add to this list if a new field 353 // is added to Config, update AnonymousClientConfig to preserve the field otherwise. 354 expected.Impersonate = ImpersonationConfig{} 355 expected.BearerToken = "" 356 expected.BearerTokenFile = "" 357 expected.Username = "" 358 expected.Password = "" 359 expected.AuthProvider = nil 360 expected.AuthConfigPersister = nil 361 expected.ExecProvider = nil 362 expected.TLSClientConfig.CertData = nil 363 expected.TLSClientConfig.CertFile = "" 364 expected.TLSClientConfig.KeyData = nil 365 expected.TLSClientConfig.KeyFile = "" 366 expected.Transport = nil 367 expected.WrapTransport = nil 368 369 if actual.Dial != nil { 370 _, actualError := actual.Dial(context.Background(), "", "") 371 _, expectedError := expected.Dial(context.Background(), "", "") 372 if !reflect.DeepEqual(expectedError, actualError) { 373 t.Fatalf("AnonymousClientConfig dropped the Dial field") 374 } 375 } 376 actual.Dial = nil 377 expected.Dial = nil 378 379 if actual.Proxy != nil { 380 _, actualError := actual.Proxy(nil) 381 _, expectedError := expected.Proxy(nil) 382 if !reflect.DeepEqual(expectedError, actualError) { 383 t.Fatalf("AnonymousClientConfig dropped the Proxy field") 384 } 385 } 386 actual.Proxy = nil 387 expected.Proxy = nil 388 389 if diff := cmp.Diff(*actual, expected); diff != "" { 390 t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff) 391 } 392 } 393 } 394 395 func TestCopyConfig(t *testing.T) { 396 f := fuzz.New().NilChance(0.0).NumElements(1, 1) 397 f.Funcs( 398 func(r *runtime.Codec, f fuzz.Continue) { 399 codec := &fakeCodec{} 400 f.Fuzz(codec) 401 *r = codec 402 }, 403 func(r *http.RoundTripper, f fuzz.Continue) { 404 roundTripper := &fakeRoundTripper{} 405 f.Fuzz(roundTripper) 406 *r = roundTripper 407 }, 408 func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) { 409 *fn = fakeWrapperFunc 410 }, 411 func(fn *transport.WrapperFunc, f fuzz.Continue) { 412 *fn = fakeWrapperFunc 413 }, 414 func(r *runtime.NegotiatedSerializer, f fuzz.Continue) { 415 serializer := &fakeNegotiatedSerializer{} 416 f.Fuzz(serializer) 417 *r = serializer 418 }, 419 func(r *flowcontrol.RateLimiter, f fuzz.Continue) { 420 limiter := &fakeLimiter{} 421 f.Fuzz(limiter) 422 *r = limiter 423 }, 424 func(h *WarningHandler, f fuzz.Continue) { 425 *h = &fakeWarningHandler{} 426 }, 427 func(r *AuthProviderConfigPersister, f fuzz.Continue) { 428 *r = fakeAuthProviderConfigPersister{} 429 }, 430 func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) { 431 *r = fakeDialFunc 432 }, 433 func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) { 434 *r = fakeProxyFunc 435 }, 436 func(r *runtime.Object, f fuzz.Continue) { 437 unknown := &runtime.Unknown{} 438 f.Fuzz(unknown) 439 *r = unknown 440 }, 441 ) 442 for i := 0; i < 20; i++ { 443 original := &Config{} 444 f.Fuzz(original) 445 actual := CopyConfig(original) 446 expected := *original 447 448 // this is the list of known risky fields, add to this list if a new field 449 // is added to Config, update CopyConfig to preserve the field otherwise. 450 451 // The DeepEqual cannot handle the func comparison, so we just verify if the 452 // function return the expected object. 453 if actual.WrapTransport == nil || !reflect.DeepEqual(expected.WrapTransport(nil), &fakeRoundTripper{}) { 454 t.Fatalf("CopyConfig dropped the WrapTransport field") 455 } 456 actual.WrapTransport = nil 457 expected.WrapTransport = nil 458 459 if actual.Dial != nil { 460 _, actualError := actual.Dial(context.Background(), "", "") 461 _, expectedError := expected.Dial(context.Background(), "", "") 462 if !reflect.DeepEqual(expectedError, actualError) { 463 t.Fatalf("CopyConfig dropped the Dial field") 464 } 465 } 466 actual.Dial = nil 467 expected.Dial = nil 468 469 if actual.AuthConfigPersister != nil { 470 actualError := actual.AuthConfigPersister.Persist(nil) 471 expectedError := expected.AuthConfigPersister.Persist(nil) 472 if !reflect.DeepEqual(expectedError, actualError) { 473 t.Fatalf("CopyConfig dropped the Dial field") 474 } 475 } 476 actual.AuthConfigPersister = nil 477 expected.AuthConfigPersister = nil 478 479 if actual.Proxy != nil { 480 _, actualError := actual.Proxy(nil) 481 _, expectedError := expected.Proxy(nil) 482 if !reflect.DeepEqual(expectedError, actualError) { 483 t.Fatalf("CopyConfig dropped the Proxy field") 484 } 485 } 486 actual.Proxy = nil 487 expected.Proxy = nil 488 489 if diff := cmp.Diff(*actual, expected); diff != "" { 490 t.Fatalf("CopyConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff) 491 } 492 } 493 } 494 495 func TestConfigStringer(t *testing.T) { 496 formatBytes := func(b []byte) string { 497 // %#v for []byte always pre-pends "[]byte{". 498 // %#v for struct with []byte field always pre-pends "[]uint8{". 499 return strings.Replace(fmt.Sprintf("%#v", b), "byte", "uint8", 1) 500 } 501 tests := []struct { 502 desc string 503 c *Config 504 expectContent []string 505 prohibitContent []string 506 }{ 507 { 508 desc: "nil config", 509 c: nil, 510 expectContent: []string{"<nil>"}, 511 }, 512 { 513 desc: "non-sensitive config", 514 c: &Config{ 515 Host: "localhost:8080", 516 APIPath: "v1", 517 UserAgent: "gobot", 518 }, 519 expectContent: []string{"localhost:8080", "v1", "gobot"}, 520 }, 521 { 522 desc: "sensitive config", 523 c: &Config{ 524 Host: "localhost:8080", 525 Username: "gopher", 526 Password: "g0ph3r", 527 BearerToken: "1234567890", 528 TLSClientConfig: TLSClientConfig{ 529 CertFile: "a.crt", 530 KeyFile: "a.key", 531 CertData: []byte("fake cert"), 532 KeyData: []byte("fake key"), 533 }, 534 AuthProvider: &clientcmdapi.AuthProviderConfig{ 535 Config: map[string]string{"secret": "s3cr3t"}, 536 }, 537 ExecProvider: &clientcmdapi.ExecConfig{ 538 Args: []string{"secret"}, 539 Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, 540 Config: &runtime.Unknown{Raw: []byte("here is some config data")}, 541 }, 542 }, 543 expectContent: []string{ 544 "localhost:8080", 545 "gopher", 546 "a.crt", 547 "a.key", 548 "--- REDACTED ---", 549 formatBytes([]byte("--- REDACTED ---")), 550 formatBytes([]byte("--- TRUNCATED ---")), 551 }, 552 prohibitContent: []string{ 553 "g0ph3r", 554 "1234567890", 555 formatBytes([]byte("fake cert")), 556 formatBytes([]byte("fake key")), 557 "secret", 558 "s3cr3t", 559 "here is some config data", 560 formatBytes([]byte("super secret password")), 561 }, 562 }, 563 } 564 565 for _, tt := range tests { 566 t.Run(tt.desc, func(t *testing.T) { 567 got := tt.c.String() 568 t.Logf("formatted config: %q", got) 569 570 for _, expect := range tt.expectContent { 571 if !strings.Contains(got, expect) { 572 t.Errorf("missing expected string %q", expect) 573 } 574 } 575 for _, prohibit := range tt.prohibitContent { 576 if strings.Contains(got, prohibit) { 577 t.Errorf("found prohibited string %q", prohibit) 578 } 579 } 580 }) 581 } 582 } 583 584 func TestConfigSprint(t *testing.T) { 585 c := &Config{ 586 Host: "localhost:8080", 587 APIPath: "v1", 588 ContentConfig: ContentConfig{ 589 AcceptContentTypes: "application/json", 590 ContentType: "application/json", 591 }, 592 Username: "gopher", 593 Password: "g0ph3r", 594 BearerToken: "1234567890", 595 Impersonate: ImpersonationConfig{ 596 UserName: "gopher2", 597 }, 598 AuthProvider: &clientcmdapi.AuthProviderConfig{ 599 Name: "gopher", 600 Config: map[string]string{"secret": "s3cr3t"}, 601 }, 602 AuthConfigPersister: fakeAuthProviderConfigPersister{}, 603 ExecProvider: &clientcmdapi.ExecConfig{ 604 Command: "sudo", 605 Args: []string{"secret"}, 606 Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, 607 ProvideClusterInfo: true, 608 Config: &runtime.Unknown{Raw: []byte("super secret password")}, 609 }, 610 TLSClientConfig: TLSClientConfig{ 611 CertFile: "a.crt", 612 KeyFile: "a.key", 613 CertData: []byte("fake cert"), 614 KeyData: []byte("fake key"), 615 NextProtos: []string{"h2", "http/1.1"}, 616 }, 617 UserAgent: "gobot", 618 Transport: &fakeRoundTripper{}, 619 WrapTransport: fakeWrapperFunc, 620 QPS: 1, 621 Burst: 2, 622 RateLimiter: &fakeLimiter{}, 623 WarningHandler: fakeWarningHandler{}, 624 Timeout: 3 * time.Second, 625 Dial: fakeDialFunc, 626 Proxy: fakeProxyFunc, 627 } 628 want := fmt.Sprintf( 629 `&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", 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)}`, 630 c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc, 631 ) 632 633 for _, f := range []string{"%s", "%v", "%+v", "%#v"} { 634 if got := fmt.Sprintf(f, c); want != got { 635 t.Errorf("fmt.Sprintf(%q, c)\ngot: %q\nwant: %q", f, got, want) 636 } 637 } 638 }