github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/flags_test.go (about) 1 // Copyright 2015 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package cli 12 13 import ( 14 "context" 15 "flag" 16 "fmt" 17 "io/ioutil" 18 "os" 19 "path/filepath" 20 "reflect" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/cockroachdb/cockroach/pkg/base" 26 "github.com/cockroachdb/cockroach/pkg/cli/cliflags" 27 "github.com/cockroachdb/cockroach/pkg/gossip/resolver" 28 "github.com/cockroachdb/cockroach/pkg/server/status" 29 "github.com/cockroachdb/cockroach/pkg/testutils" 30 "github.com/cockroachdb/cockroach/pkg/testutils/buildutil" 31 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 32 "github.com/cockroachdb/cockroach/pkg/util/log/logflags" 33 "github.com/spf13/cobra" 34 ) 35 36 func TestStdFlagToPflag(t *testing.T) { 37 defer leaktest.AfterTest(t)() 38 cf := cockroachCmd.PersistentFlags() 39 flag.VisitAll(func(f *flag.Flag) { 40 if strings.HasPrefix(f.Name, "test.") { 41 return 42 } 43 switch f.Name { 44 case logflags.LogDirName, 45 logflags.LogFileMaxSizeName, 46 logflags.LogFilesCombinedMaxSizeName, 47 logflags.LogFileVerbosityThresholdName: 48 return 49 } 50 if pf := cf.Lookup(f.Name); pf == nil { 51 t.Errorf("unable to find \"%s\"", f.Name) 52 } 53 }) 54 } 55 56 func TestNoLinkForbidden(t *testing.T) { 57 defer leaktest.AfterTest(t)() 58 // Verify that the cockroach binary doesn't depend on certain packages. 59 buildutil.VerifyNoImports(t, 60 "github.com/cockroachdb/cockroach/pkg/cmd/cockroach", true, 61 []string{ 62 "testing", // defines flags 63 "go/build", // probably not something we want in the main binary 64 "github.com/cockroachdb/cockroach/pkg/security/securitytest", // contains certificates 65 }, 66 []string{ 67 "github.com/cockroachdb/cockroach/pkg/testutils", // meant for testing code only 68 }, 69 // Sentry and the errors library use go/build to determine 70 // the list of source directories (used to strip the source prefix 71 // in stack trace reports). 72 "github.com/cockroachdb/cockroach/vendor/github.com/cockroachdb/sentry-go", 73 "github.com/cockroachdb/cockroach/vendor/github.com/cockroachdb/errors/withstack", 74 ) 75 } 76 77 func TestCacheFlagValue(t *testing.T) { 78 defer leaktest.AfterTest(t)() 79 80 // Avoid leaking configuration changes after the test ends. 81 defer initCLIDefaults() 82 83 f := startCmd.Flags() 84 args := []string{"--cache", "100MB"} 85 if err := f.Parse(args); err != nil { 86 t.Fatal(err) 87 } 88 89 const expectedCacheSize = 100 * 1000 * 1000 90 if expectedCacheSize != serverCfg.CacheSize { 91 t.Errorf("expected %d, but got %d", expectedCacheSize, serverCfg.CacheSize) 92 } 93 } 94 95 func TestClusterNameFlag(t *testing.T) { 96 defer leaktest.AfterTest(t)() 97 98 // Avoid leaking configuration changes after the test ends. 99 defer initCLIDefaults() 100 101 testCases := []struct { 102 value string 103 expectedErr string 104 }{ 105 {"abc", ""}, 106 {"a-b", ""}, 107 {"a123", ""}, 108 {"", "cluster name cannot be empty"}, 109 {fmt.Sprintf("%*s", 1000, "a"), "cluster name can contain at most 256 characters"}, 110 {"a-b.c", errClusterNameInvalidFormat.Error()}, 111 {"a123.456", errClusterNameInvalidFormat.Error()}, 112 {"...", errClusterNameInvalidFormat.Error()}, 113 {"-abc", errClusterNameInvalidFormat.Error()}, 114 {"123a", errClusterNameInvalidFormat.Error()}, 115 {"abc.", errClusterNameInvalidFormat.Error()}, 116 {"_abc", errClusterNameInvalidFormat.Error()}, 117 {"a.b_c._.", errClusterNameInvalidFormat.Error()}, 118 } 119 120 for _, c := range testCases { 121 baseCfg.ClusterName = "" 122 f := startCmd.Flags() 123 args := []string{"--cluster-name", c.value} 124 err := f.Parse(args) 125 if !testutils.IsError(err, c.expectedErr) { 126 t.Fatal(err) 127 } 128 if err == nil { 129 if baseCfg.ClusterName != c.value { 130 t.Errorf("expected %q, got %q", c.value, baseCfg.ClusterName) 131 } 132 } 133 } 134 } 135 136 func TestSQLMemoryPoolFlagValue(t *testing.T) { 137 defer leaktest.AfterTest(t)() 138 139 // Avoid leaking configuration changes after the test ends. 140 defer initCLIDefaults() 141 142 f := startCmd.Flags() 143 144 // Check absolute values. 145 testCases := []struct { 146 value string 147 expected int64 148 }{ 149 {"100MB", 100 * 1000 * 1000}, 150 {".5GiB", 512 * 1024 * 1024}, 151 {"1.3", 1}, 152 } 153 for _, c := range testCases { 154 args := []string{"--max-sql-memory", c.value} 155 if err := f.Parse(args); err != nil { 156 t.Fatal(err) 157 } 158 if c.expected != serverCfg.MemoryPoolSize { 159 t.Errorf("expected %d, but got %d", c.expected, serverCfg.MemoryPoolSize) 160 } 161 } 162 163 for _, c := range []string{".30", "0.3"} { 164 args := []string{"--max-sql-memory", c} 165 if err := f.Parse(args); err != nil { 166 t.Fatal(err) 167 } 168 169 // Check fractional values. 170 maxMem, err := status.GetTotalMemory(context.Background()) 171 if err != nil { 172 t.Logf("total memory unknown: %v", err) 173 return 174 } 175 expectedLow := (maxMem * 28) / 100 176 expectedHigh := (maxMem * 32) / 100 177 if serverCfg.MemoryPoolSize < expectedLow || serverCfg.MemoryPoolSize > expectedHigh { 178 t.Errorf("expected %d-%d, but got %d", expectedLow, expectedHigh, serverCfg.MemoryPoolSize) 179 } 180 } 181 } 182 183 func TestClockOffsetFlagValue(t *testing.T) { 184 defer leaktest.AfterTest(t)() 185 186 // Avoid leaking configuration changes after the tests end. 187 defer initCLIDefaults() 188 189 f := startCmd.Flags() 190 testData := []struct { 191 args []string 192 expected time.Duration 193 }{ 194 {nil, base.DefaultMaxClockOffset}, 195 {[]string{"--max-offset", "200ms"}, 200 * time.Millisecond}, 196 } 197 198 for i, td := range testData { 199 initCLIDefaults() 200 201 if err := f.Parse(td.args); err != nil { 202 t.Fatal(err) 203 } 204 if td.expected != time.Duration(serverCfg.MaxOffset) { 205 t.Errorf("%d. MaxOffset expected %v, but got %v", i, td.expected, serverCfg.MaxOffset) 206 } 207 } 208 } 209 210 func TestClientURLFlagEquivalence(t *testing.T) { 211 defer leaktest.AfterTest(t)() 212 213 // Avoid leaking configuration changes after the tests end. 214 defer initCLIDefaults() 215 216 // Prepare a dummy default certificate directory. 217 defCertsDirPath, err := ioutil.TempDir("", "defCerts") 218 if err != nil { 219 t.Fatal(err) 220 } 221 defCertsDirPath, _ = filepath.Abs(defCertsDirPath) 222 cleanup := createTestCerts(defCertsDirPath) 223 defer func() { _ = cleanup() }() 224 225 // Prepare a custom certificate directory. 226 testCertsDirPath, err := ioutil.TempDir("", "customCerts") 227 if err != nil { 228 t.Fatal(err) 229 } 230 testCertsDirPath, _ = filepath.Abs(testCertsDirPath) 231 cleanup2 := createTestCerts(testCertsDirPath) 232 defer func() { _ = cleanup2() }() 233 234 anyCmd := []string{"sql", "quit"} 235 anyNonSQL := []string{"quit", "init"} 236 anySQL := []string{"sql", "dump"} 237 sqlShell := []string{"sql"} 238 anyNonSQLShell := []string{"dump", "quit"} 239 240 testData := []struct { 241 cmds []string 242 flags []string 243 refargs []string 244 expErr string 245 reparseErr string 246 }{ 247 // Check individual URL components. 248 {anyCmd, []string{"--url=http://foo"}, nil, `URL scheme must be "postgresql"`, ""}, 249 {anyCmd, []string{"--url=postgresql:foo/bar"}, nil, `unknown URL format`, ""}, 250 251 {anyCmd, []string{"--url=postgresql://foo"}, []string{"--host=foo"}, "", ""}, 252 {anyCmd, []string{"--url=postgresql://:foo"}, []string{"--port=foo"}, "invalid port \":foo\" after host", ""}, 253 {anyCmd, []string{"--url=postgresql://:12345"}, []string{"--port=12345"}, "", ""}, 254 255 {sqlShell, []string{"--url=postgresql:///foo"}, []string{"--database=foo"}, "", ""}, 256 {anyNonSQLShell, []string{"--url=postgresql://foo/bar"}, []string{"--host=foo" /*db ignored*/}, "", ""}, 257 258 {anySQL, []string{"--url=postgresql://foo@"}, []string{"--user=foo"}, "", ""}, 259 {anyNonSQL, []string{"--url=postgresql://foo@bar"}, []string{"--host=bar" /*user ignored*/}, "", ""}, 260 261 {sqlShell, []string{"--url=postgresql://a@b:12345/d"}, []string{"--user=a", "--host=b", "--port=12345", "--database=d"}, "", ""}, 262 {sqlShell, []string{"--url=postgresql://a@b:c/d"}, nil, `invalid port ":c" after host`, ""}, 263 {anySQL, []string{"--url=postgresql://a@b:12345"}, []string{"--user=a", "--host=b", "--port=12345"}, "", ""}, 264 {anySQL, []string{"--url=postgresql://a@b:c"}, nil, `invalid port ":c" after host`, ""}, 265 {anyNonSQL, []string{"--url=postgresql://b:12345"}, []string{"--host=b", "--port=12345"}, "", ""}, 266 {anyNonSQL, []string{"--url=postgresql://b:c"}, nil, `invalid port ":c" after host`, ""}, 267 268 {anyCmd, []string{"--url=postgresql://foo?application_name=abc"}, []string{"--host=foo", "--insecure"}, "", ""}, 269 {anyCmd, []string{"--url=postgresql://foo?sslmode=disable"}, []string{"--host=foo", "--insecure"}, "", ""}, 270 {anySQL, []string{"--url=postgresql://foo?sslmode=require"}, []string{"--host=foo", "--insecure=false"}, "", ""}, 271 {anyNonSQL, []string{"--url=postgresql://foo?sslmode=require"}, nil, "command .* only supports sslmode=disable or sslmode=verify-full", ""}, 272 {anyCmd, []string{"--url=postgresql://foo?sslmode=verify-full"}, []string{"--host=foo", "--insecure=false"}, "", ""}, 273 274 // URL picks up previous flags if component not specified. 275 {anyCmd, []string{"--host=baz", "--url=postgresql://:12345"}, []string{"--host=baz", "--port=12345"}, "", ""}, 276 {anyCmd, []string{"--host=baz", "--url=postgresql://:foo"}, nil, `invalid port ":foo" after host`, ""}, 277 {anyCmd, []string{"--port=12345", "--url=postgresql://foo"}, []string{"--host=foo", "--port=12345"}, "", ""}, 278 {anyCmd, []string{"--port=baz", "--url=postgresql://foo"}, []string{"--host=foo", "--port=baz"}, "", `invalid port ":baz" after host`}, 279 {sqlShell, []string{"--database=baz", "--url=postgresql://foo"}, []string{"--host=foo", "--database=baz"}, "", ""}, 280 {anySQL, []string{"--user=baz", "--url=postgresql://foo"}, []string{"--host=foo", "--user=baz"}, "", ""}, 281 {anyCmd, []string{"--insecure=false", "--url=postgresql://foo"}, []string{"--host=foo", "--insecure=false"}, "", ""}, 282 {anyCmd, []string{"--insecure", "--url=postgresql://foo"}, []string{"--host=foo", "--insecure"}, "", ""}, 283 284 // URL overrides previous flags if component specified. 285 {anyCmd, []string{"--host=baz", "--url=postgresql://bar"}, []string{"--host=bar"}, "", ""}, 286 {anyCmd, []string{"--port=baz", "--url=postgresql://foo:12345"}, []string{"--host=foo", "--port=12345"}, "", ""}, 287 {anyCmd, []string{"--port=baz", "--url=postgresql://foo:bar"}, nil, `invalid port ":bar" after host`, ""}, 288 {sqlShell, []string{"--database=baz", "--url=postgresql://foo/bar"}, []string{"--host=foo", "--database=bar"}, "", ""}, 289 {anySQL, []string{"--user=baz", "--url=postgresql://bar@foo"}, []string{"--host=foo", "--user=bar"}, "", ""}, 290 {anyCmd, []string{"--insecure=false", "--url=postgresql://foo?sslmode=disable"}, []string{"--host=foo", "--insecure"}, "", ""}, 291 {anyCmd, []string{"--insecure", "--url=postgresql://foo?sslmode=verify-full"}, []string{"--host=foo", "--insecure=false"}, "", ""}, 292 293 // Discrete flag overrides URL if specified afterwards. 294 {anyCmd, []string{"--url=postgresql://bar", "--host=baz"}, []string{"--host=baz"}, "", ""}, 295 {anyCmd, []string{"--url=postgresql://foo:12345", "--port=5678"}, []string{"--host=foo", "--port=5678"}, "", ""}, 296 {anyCmd, []string{"--url=postgresql://foo:12345", "--port=baz"}, []string{"--host=foo", "--port=baz"}, "", `invalid port ":baz" after host`}, 297 {anyCmd, []string{"--url=postgresql://foo:bar", "--port=baz"}, nil, `invalid port ":bar" after host`, ""}, 298 {sqlShell, []string{"--url=postgresql://foo/bar", "--database=baz"}, []string{"--host=foo", "--database=baz"}, "", ""}, 299 {anySQL, []string{"--url=postgresql://bar@foo", "--user=baz"}, []string{"--host=foo", "--user=baz"}, "", ""}, 300 {anyCmd, []string{"--url=postgresql://foo?sslmode=disable", "--insecure=false"}, []string{"--host=foo", "--insecure=false"}, "", ""}, 301 {anyCmd, []string{"--url=postgresql://foo?sslmode=verify-full", "--insecure"}, []string{"--host=foo", "--insecure"}, "", ""}, 302 303 // Check that the certs dir is extracted properly. 304 {anyNonSQL, []string{"--url=postgresql://foo?sslmode=verify-full&sslrootcert=" + testCertsDirPath + "/ca.crt"}, []string{"--host=foo", "--certs-dir=" + testCertsDirPath}, "", ""}, 305 {anyNonSQL, []string{"--certs-dir=blah", "--url=postgresql://foo?sslmode=verify-full&sslrootcert=blih/ca.crt"}, nil, "non-homogeneous certificate directory", ""}, 306 {anyNonSQL, []string{"--url=postgresql://foo?sslmode=verify-full&sslrootcert=blih/ca.crt&sslcert=blah/client.root.crt"}, nil, "non-homogeneous certificate directory", ""}, 307 308 // Check the cert component file names are checked. 309 {anyNonSQL, []string{"--url=postgresql://foo?sslmode=verify-full&sslrootcert=blih/loh.crt"}, nil, `invalid file name for "sslrootcert": expected .* got .*`, ""}, 310 {anyNonSQL, []string{"--url=postgresql://foo?sslmode=verify-full&sslcert=blih/loh.crt"}, nil, `invalid file name for "sslcert": expected .* got .*`, ""}, 311 {anyNonSQL, []string{"--url=postgresql://foo?sslmode=verify-full&sslkey=blih/loh.crt"}, nil, `invalid file name for "sslkey": expected .* got .*`, ""}, 312 } 313 314 type capturedFlags struct { 315 connHost string 316 connPort string 317 connUser string 318 connDatabase string 319 insecure bool 320 certsDir string 321 } 322 323 // capture saves the parameter values computed in a round of 324 // command-line argument parsing. 325 capture := func(cmd *cobra.Command) capturedFlags { 326 certsDir := defCertsDirPath 327 if f := cmd.Flags().Lookup(cliflags.CertsDir.Name); f != nil && f.Changed { 328 certsDir = baseCfg.SSLCertsDir 329 } 330 return capturedFlags{ 331 insecure: baseCfg.Insecure, 332 connUser: cliCtx.sqlConnUser, 333 connDatabase: cliCtx.sqlConnDBName, 334 connHost: cliCtx.clientConnHost, 335 connPort: cliCtx.clientConnPort, 336 certsDir: certsDir, 337 } 338 } 339 340 for _, test := range testData { 341 for _, cmdName := range test.cmds { 342 t.Run(fmt.Sprintf("%s/%s", cmdName, strings.Join(test.flags, " ")), func(t *testing.T) { 343 cmd, _, _ := cockroachCmd.Find([]string{cmdName}) 344 345 // Parse using the URL. 346 // This checks the URL parser works and/or that it produces the expected error. 347 initCLIDefaults() 348 cliCtx.SSLCertsDir = defCertsDirPath 349 err := cmd.ParseFlags(test.flags) 350 if !testutils.IsError(err, test.expErr) { 351 t.Fatalf("expected %q, got %v", test.expErr, err) 352 } 353 if err != nil { 354 return 355 } 356 urlParams := capture(cmd) 357 connURL, err := cliCtx.makeClientConnURL() 358 if err != nil { 359 t.Fatal(err) 360 } 361 resultURL := connURL.String() 362 363 // Parse using the discrete flags. 364 // We use this to generate the reference parameter values for the comparison below. 365 initCLIDefaults() 366 cliCtx.SSLCertsDir = defCertsDirPath 367 if err := cmd.ParseFlags(test.refargs); err != nil { 368 t.Fatal(err) 369 } 370 discreteParams := capture(cmd) 371 connURL, err = cliCtx.makeClientConnURL() 372 if err != nil { 373 t.Fatal(err) 374 } 375 defaultURL := connURL.String() 376 377 // Verify that parsing the URL produces the same parameters as parsing the discrete flags. 378 if urlParams != discreteParams { 379 t.Fatalf("mismatch: URL %q parses\n%+v,\ndiscrete parses\n%+v", resultURL, urlParams, discreteParams) 380 } 381 382 // Re-parse using the derived URL. 383 // We'll want to ensure below that the derived URL specifies the same parameters 384 // (i.e. check makeClientConnURL does its work properly). 385 initCLIDefaults() 386 cliCtx.SSLCertsDir = defCertsDirPath 387 if err := cmd.ParseFlags([]string{"--url=" + resultURL}); err != nil { 388 if !testutils.IsError(err, test.reparseErr) { 389 t.Fatal(err) 390 } 391 return 392 } 393 urlParams2 := capture(cmd) 394 395 // Verify that makeClientConnURL is still specifying the same things. 396 if urlParams2 != discreteParams { 397 t.Fatalf("mismatch during reparse: derived URL %q parses\n%+v,\ndiscrete parses\n%+v", resultURL, urlParams2, discreteParams) 398 } 399 400 // Re-parse using the derived URL from discrete parameters. 401 // We're checking here that makeClientConnURL is also doing its job 402 // properly when computing a URL from discrete parameters. 403 initCLIDefaults() 404 cliCtx.SSLCertsDir = defCertsDirPath 405 if err := cmd.ParseFlags([]string{"--url=" + defaultURL}); err != nil { 406 t.Fatal(err) 407 } 408 urlParams3 := capture(cmd) 409 410 if urlParams2 != urlParams3 { 411 t.Fatalf("mismatch during reparse 2: derived URL %q parses\n%+v,\ndefault URL %q reparses\n%+v", resultURL, urlParams2, defaultURL, urlParams3) 412 } 413 }) 414 } 415 } 416 } 417 418 func TestServerConnSettings(t *testing.T) { 419 defer leaktest.AfterTest(t)() 420 421 // Avoid leaking configuration changes after the tests end. 422 defer initCLIDefaults() 423 424 f := startCmd.Flags() 425 testData := []struct { 426 args []string 427 expectedAddr string 428 expectedAdvertiseAddr string 429 expSQLAddr string 430 expSQLAdvAddr string 431 }{ 432 {[]string{"start"}, 433 ":" + base.DefaultPort, ":" + base.DefaultPort, 434 ":" + base.DefaultPort, ":" + base.DefaultPort, 435 }, 436 {[]string{"start", "--listen-addr", "127.0.0.1"}, 437 "127.0.0.1:" + base.DefaultPort, "127.0.0.1:" + base.DefaultPort, 438 "127.0.0.1:" + base.DefaultPort, "127.0.0.1:" + base.DefaultPort, 439 }, 440 {[]string{"start", "--listen-addr", "192.168.0.111"}, 441 "192.168.0.111:" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 442 "192.168.0.111:" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 443 }, 444 {[]string{"start", "--listen-addr", ":"}, 445 ":", ":", 446 ":", ":", 447 }, 448 {[]string{"start", "--listen-addr", "127.0.0.1:"}, 449 "127.0.0.1:", "127.0.0.1:", 450 "127.0.0.1:", "127.0.0.1:", 451 }, 452 {[]string{"start", "--listen-addr", ":12345"}, 453 ":12345", ":12345", 454 ":12345", ":12345", 455 }, 456 {[]string{"start", "--listen-addr", "127.0.0.1:12345"}, 457 "127.0.0.1:12345", "127.0.0.1:12345", 458 "127.0.0.1:12345", "127.0.0.1:12345", 459 }, 460 {[]string{"start", "--listen-addr", "[::1]"}, 461 "[::1]:" + base.DefaultPort, "[::1]:" + base.DefaultPort, 462 "[::1]:" + base.DefaultPort, "[::1]:" + base.DefaultPort, 463 }, 464 {[]string{"start", "--listen-addr", "[::1]:12345"}, 465 "[::1]:12345", "[::1]:12345", 466 "[::1]:12345", "[::1]:12345", 467 }, 468 {[]string{"start", "--listen-addr", "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]"}, 469 "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]:" + base.DefaultPort, "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]:" + base.DefaultPort, 470 "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]:" + base.DefaultPort, "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]:" + base.DefaultPort, 471 }, 472 // confirm hostnames will work 473 {[]string{"start", "--listen-addr", "my.host.name"}, 474 "my.host.name:" + base.DefaultPort, "my.host.name:" + base.DefaultPort, 475 "my.host.name:" + base.DefaultPort, "my.host.name:" + base.DefaultPort, 476 }, 477 {[]string{"start", "--listen-addr", "myhostname"}, 478 "myhostname:" + base.DefaultPort, "myhostname:" + base.DefaultPort, 479 "myhostname:" + base.DefaultPort, "myhostname:" + base.DefaultPort, 480 }, 481 482 // SQL address override. 483 {[]string{"start", "--sql-addr", "127.0.0.1"}, 484 ":" + base.DefaultPort, ":" + base.DefaultPort, 485 "127.0.0.1:" + base.DefaultPort, "127.0.0.1:" + base.DefaultPort, 486 }, 487 {[]string{"start", "--sql-addr", ":1234"}, 488 ":" + base.DefaultPort, ":" + base.DefaultPort, 489 ":1234", ":1234", 490 }, 491 {[]string{"start", "--sql-addr", "127.0.0.1:1234"}, 492 ":" + base.DefaultPort, ":" + base.DefaultPort, 493 "127.0.0.1:1234", "127.0.0.1:1234", 494 }, 495 {[]string{"start", "--sql-addr", "[::2]"}, 496 ":" + base.DefaultPort, ":" + base.DefaultPort, 497 "[::2]:" + base.DefaultPort, "[::2]:" + base.DefaultPort, 498 }, 499 {[]string{"start", "--sql-addr", "[::2]:1234"}, 500 ":" + base.DefaultPort, ":" + base.DefaultPort, 501 "[::2]:1234", "[::2]:1234", 502 }, 503 504 // Configuring the components of the SQL address separately. 505 {[]string{"start", "--listen-addr", "127.0.0.1", "--sql-addr", "127.0.0.2"}, 506 "127.0.0.1:" + base.DefaultPort, "127.0.0.1:" + base.DefaultPort, 507 "127.0.0.2:" + base.DefaultPort, "127.0.0.2:" + base.DefaultPort, 508 }, 509 {[]string{"start", "--listen-addr", "127.0.0.1", "--sql-addr", ":1234"}, 510 "127.0.0.1:" + base.DefaultPort, "127.0.0.1:" + base.DefaultPort, 511 "127.0.0.1:1234", "127.0.0.1:1234", 512 }, 513 {[]string{"start", "--listen-addr", "127.0.0.1", "--sql-addr", "127.0.0.2:1234"}, 514 "127.0.0.1:" + base.DefaultPort, "127.0.0.1:" + base.DefaultPort, 515 "127.0.0.2:1234", "127.0.0.2:1234", 516 }, 517 {[]string{"start", "--listen-addr", "[::2]", "--sql-addr", ":1234"}, 518 "[::2]:" + base.DefaultPort, "[::2]:" + base.DefaultPort, 519 "[::2]:1234", "[::2]:1234"}, 520 521 // --advertise-addr overrides. 522 {[]string{"start", "--advertise-addr", "192.168.0.111"}, 523 ":" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 524 ":" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 525 }, 526 {[]string{"start", "--advertise-addr", "192.168.0.111:12345"}, 527 ":" + base.DefaultPort, "192.168.0.111:12345", 528 ":" + base.DefaultPort, "192.168.0.111:12345", 529 }, 530 {[]string{"start", "--listen-addr", "127.0.0.1", "--advertise-addr", "192.168.0.111"}, 531 "127.0.0.1:" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 532 "127.0.0.1:" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 533 }, 534 {[]string{"start", "--listen-addr", "127.0.0.1:12345", "--advertise-addr", "192.168.0.111"}, 535 "127.0.0.1:12345", "192.168.0.111:12345", 536 "127.0.0.1:12345", "192.168.0.111:12345", 537 }, 538 {[]string{"start", "--listen-addr", "127.0.0.1", "--advertise-addr", "192.168.0.111:12345"}, 539 "127.0.0.1:" + base.DefaultPort, "192.168.0.111:12345", 540 "127.0.0.1:" + base.DefaultPort, "192.168.0.111:12345", 541 }, 542 {[]string{"start", "--listen-addr", "127.0.0.1:54321", "--advertise-addr", "192.168.0.111:12345"}, 543 "127.0.0.1:54321", "192.168.0.111:12345", 544 "127.0.0.1:54321", "192.168.0.111:12345", 545 }, 546 {[]string{"start", "--advertise-addr", "192.168.0.111", "--listen-addr", ":12345"}, 547 ":12345", "192.168.0.111:12345", 548 ":12345", "192.168.0.111:12345", 549 }, 550 {[]string{"start", "--advertise-addr", "192.168.0.111:12345", "--listen-addr", ":54321"}, 551 ":54321", "192.168.0.111:12345", 552 ":54321", "192.168.0.111:12345", 553 }, 554 555 // Show that if the SQL address does not have a name default, its 556 // advertised form picks up the RPC advertised address. 557 {[]string{"start", "--advertise-addr", "192.168.0.111:12345", "--sql-addr", ":54321"}, 558 ":" + base.DefaultPort, "192.168.0.111:12345", 559 ":54321", "192.168.0.111:54321", 560 }, 561 562 // Show that if the SQL address is overridden, its advertised form picks the 563 // advertised RPC address but keeps the port. 564 {[]string{"start", "--advertise-addr", "192.168.0.111:12345", "--sql-addr", "127.0.0.1:54321"}, 565 ":" + base.DefaultPort, "192.168.0.111:12345", 566 "127.0.0.1:54321", "192.168.0.111:54321", 567 }, 568 {[]string{"start", "--advertise-addr", "192.168.0.111:12345", "--sql-addr", "127.0.0.1"}, 569 ":" + base.DefaultPort, "192.168.0.111:12345", 570 "127.0.0.1:" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 571 }, 572 {[]string{"start", "--advertise-addr", "192.168.0.111", "--sql-addr", "127.0.0.1:12345"}, 573 ":" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 574 "127.0.0.1:12345", "192.168.0.111:12345", 575 }, 576 577 // Backward-compatibility flag combinations. 578 {[]string{"start", "--host", "192.168.0.111"}, 579 "192.168.0.111:" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 580 "192.168.0.111:" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 581 }, 582 {[]string{"start", "--port", "12345"}, 583 ":12345", ":12345", 584 ":12345", ":12345", 585 }, 586 {[]string{"start", "--advertise-host", "192.168.0.111"}, 587 ":" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 588 ":" + base.DefaultPort, "192.168.0.111:" + base.DefaultPort, 589 }, 590 {[]string{"start", "--advertise-addr", "192.168.0.111", "--advertise-port", "12345"}, 591 ":" + base.DefaultPort, "192.168.0.111:12345", 592 ":" + base.DefaultPort, "192.168.0.111:12345", 593 }, 594 {[]string{"start", "--listen-addr", "127.0.0.1", "--port", "12345"}, 595 "127.0.0.1:12345", "127.0.0.1:12345", 596 "127.0.0.1:12345", "127.0.0.1:12345", 597 }, 598 {[]string{"start", "--listen-addr", "127.0.0.1:12345", "--port", "55555"}, 599 "127.0.0.1:55555", "127.0.0.1:55555", 600 "127.0.0.1:55555", "127.0.0.1:55555", 601 }, 602 {[]string{"start", "--listen-addr", "127.0.0.1", "--advertise-addr", "192.168.0.111", "--port", "12345"}, 603 "127.0.0.1:12345", "192.168.0.111:12345", 604 "127.0.0.1:12345", "192.168.0.111:12345", 605 }, 606 {[]string{"start", "--listen-addr", "127.0.0.1", "--advertise-addr", "192.168.0.111", "--port", "12345"}, 607 "127.0.0.1:12345", "192.168.0.111:12345", 608 "127.0.0.1:12345", "192.168.0.111:12345", 609 }, 610 {[]string{"start", "--listen-addr", "127.0.0.1", "--advertise-addr", "192.168.0.111", "--advertise-port", "12345"}, 611 "127.0.0.1:" + base.DefaultPort, "192.168.0.111:12345", 612 "127.0.0.1:" + base.DefaultPort, "192.168.0.111:12345", 613 }, 614 {[]string{"start", "--listen-addr", "127.0.0.1", "--advertise-addr", "192.168.0.111", "--port", "54321", "--advertise-port", "12345"}, 615 "127.0.0.1:54321", "192.168.0.111:12345", 616 "127.0.0.1:54321", "192.168.0.111:12345", 617 }, 618 {[]string{"start", "--advertise-addr", "192.168.0.111", "--port", "12345"}, 619 ":12345", "192.168.0.111:12345", 620 ":12345", "192.168.0.111:12345", 621 }, 622 {[]string{"start", "--advertise-addr", "192.168.0.111", "--advertise-port", "12345"}, 623 ":" + base.DefaultPort, "192.168.0.111:12345", 624 ":" + base.DefaultPort, "192.168.0.111:12345", 625 }, 626 {[]string{"start", "--advertise-addr", "192.168.0.111", "--port", "54321", "--advertise-port", "12345"}, 627 ":54321", "192.168.0.111:12345", 628 ":54321", "192.168.0.111:12345", 629 }, 630 } 631 632 for i, td := range testData { 633 t.Run(strings.Join(td.args, " "), func(t *testing.T) { 634 initCLIDefaults() 635 if err := f.Parse(td.args); err != nil { 636 t.Fatalf("Parse(%#v) got unexpected error: %v", td.args, err) 637 } 638 639 if err := extraServerFlagInit(startCmd); err != nil { 640 t.Fatal(err) 641 } 642 if td.expectedAddr != serverCfg.Addr { 643 t.Errorf("%d. serverCfg.Addr expected '%s', but got '%s'. td.args was '%#v'.", 644 i, td.expectedAddr, serverCfg.Addr, td.args) 645 } 646 if td.expectedAdvertiseAddr != serverCfg.AdvertiseAddr { 647 t.Errorf("%d. serverCfg.AdvertiseAddr expected '%s', but got '%s'. td.args was '%#v'.", 648 i, td.expectedAdvertiseAddr, serverCfg.AdvertiseAddr, td.args) 649 } 650 651 wantSQLSplit := false 652 for _, r := range td.args { 653 if r == "--sql-addr" { 654 wantSQLSplit = true 655 break 656 } 657 } 658 if wantSQLSplit != serverCfg.SplitListenSQL { 659 t.Errorf("%d. expected combined RPC/SQL listen = %v, found %v", i, wantSQLSplit, serverCfg.SplitListenSQL) 660 } 661 662 if td.expSQLAddr != serverCfg.SQLAddr { 663 t.Errorf("%d. serverCfg.SQLAddr expected '%s', got '%s'. td.args was '%#v'.", 664 i, td.expSQLAddr, serverCfg.SQLAddr, td.args) 665 } 666 if td.expSQLAdvAddr != serverCfg.SQLAdvertiseAddr { 667 t.Errorf("%d. serverCfg.SQLAdvertiseAddr expected '%s', got '%s'. td.args was '%#v'.", 668 i, td.expSQLAdvAddr, serverCfg.SQLAdvertiseAddr, td.args) 669 } 670 }) 671 } 672 } 673 674 func TestServerSocketSettings(t *testing.T) { 675 defer leaktest.AfterTest(t)() 676 677 // Avoid leaking configuration changes after the tests end. 678 defer initCLIDefaults() 679 680 f := startCmd.Flags() 681 testData := []struct { 682 args []string 683 expectedSocket string 684 }{ 685 {[]string{"start"}, ""}, 686 // No socket unless requested. 687 {[]string{"start", "--listen-addr=:12345"}, ""}, 688 // File name is auto-generated. 689 {[]string{"start", "--socket-dir=/blah"}, "/blah/.s.PGSQL." + base.DefaultPort}, 690 {[]string{"start", "--socket-dir=/blah", "--listen-addr=:12345"}, "/blah/.s.PGSQL.12345"}, 691 // Empty socket dir disables the socket. 692 {[]string{"start", "--socket-dir="}, ""}, 693 {[]string{"start", "--socket-dir=", "--listen-addr=:12345"}, ""}, 694 // Deprecated behavior (remove in 20.2): 695 {[]string{"start", "--socket=/blah/xxxx"}, "/blah/xxxx"}, 696 {[]string{"start", "--socket-dir=/foo", "--socket=/blah/xxxx"}, "/blah/xxxx"}, 697 } 698 699 for i, td := range testData { 700 t.Run(strings.Join(td.args, " "), func(t *testing.T) { 701 initCLIDefaults() 702 if err := f.Parse(td.args); err != nil { 703 t.Fatalf("Parse(%#v) got unexpected error: %v", td.args, err) 704 } 705 706 if err := extraServerFlagInit(startCmd); err != nil { 707 t.Fatal(err) 708 } 709 if td.expectedSocket != serverCfg.SocketFile { 710 t.Errorf("%d. serverCfg.SocketFile expected '%s', but got '%s'. td.args was '%#v'.", 711 i, td.expectedSocket, serverCfg.SocketFile, td.args) 712 } 713 }) 714 } 715 } 716 717 func TestLocalityAdvAddrFlag(t *testing.T) { 718 defer leaktest.AfterTest(t)() 719 720 // Avoid leaking configuration changes after the tests end. 721 defer initCLIDefaults() 722 723 f := startCmd.Flags() 724 testData := []struct { 725 args []string 726 expLocalityAdvertiseAddr string 727 }{ 728 {[]string{"start", "--host", "127.0.0.1", "--locality-advertise-addr", "zone=1@235.0.0.5"}, 729 "[{{tcp 235.0.0.5:26257} zone=1}]"}, 730 {[]string{"start", "--host", "127.0.0.1", "--locality-advertise-addr", "zone=1@235.0.0.5,zone=2@123.0.0.5"}, 731 "[{{tcp 235.0.0.5:26257} zone=1} {{tcp 123.0.0.5:26257} zone=2}]"}, 732 {[]string{"start", "--host", "127.0.0.1", "--locality-advertise-addr", "zone=1@235.0.0.5:1234"}, 733 "[{{tcp 235.0.0.5:1234} zone=1}]"}, 734 {[]string{"start", "--host", "127.0.0.1", "--locality-advertise-addr", "zone=1@[::2]"}, 735 "[{{tcp [::2]:26257} zone=1}]"}, 736 {[]string{"start", "--host", "127.0.0.1", "--locality-advertise-addr", "zone=1@[::2],zone=2@123.0.0.5"}, 737 "[{{tcp [::2]:26257} zone=1} {{tcp 123.0.0.5:26257} zone=2}]"}, 738 {[]string{"start", "--host", "127.0.0.1", "--locality-advertise-addr", "zone=1@[::2]:1234"}, 739 "[{{tcp [::2]:1234} zone=1}]"}, 740 } 741 742 for i, td := range testData { 743 t.Run(strings.Join(td.args, " "), func(t *testing.T) { 744 initCLIDefaults() 745 if err := f.Parse(td.args); err != nil { 746 t.Fatalf("Parse(%#v) got unexpected error: %v", td.args, err) 747 } 748 if err := extraServerFlagInit(startCmd); err != nil { 749 t.Fatal(err) 750 } 751 var locAddrStr strings.Builder 752 locAddrStr.WriteString("[") 753 for i, a := range serverCfg.LocalityAddresses { 754 if i > 0 { 755 locAddrStr.WriteString(" ") 756 } 757 fmt.Fprintf( 758 &locAddrStr, "{{%s %s} %s}", 759 a.Address.NetworkField, a.Address.AddressField, a.LocalityTier.String(), 760 ) 761 } 762 locAddrStr.WriteString("]") 763 if td.expLocalityAdvertiseAddr != locAddrStr.String() { 764 t.Errorf("%d. serverCfg.expLocalityAdvertiseAddr expected '%s', but got '%s'. td.args was '%#v'.", 765 i, td.expLocalityAdvertiseAddr, locAddrStr.String(), td.args) 766 } 767 }) 768 } 769 } 770 771 func TestServerJoinSettings(t *testing.T) { 772 defer leaktest.AfterTest(t)() 773 774 // Avoid leaking configuration changes after the tests end. 775 defer initCLIDefaults() 776 777 f := startCmd.Flags() 778 testData := []struct { 779 args []string 780 expectedJoin []string 781 }{ 782 {[]string{"start", "--join=a"}, []string{"a:" + base.DefaultPort}}, 783 {[]string{"start", "--join=:"}, []string{"HOSTNAME:" + base.DefaultPort}}, 784 {[]string{"start", "--join=:123"}, []string{"HOSTNAME:123"}}, 785 {[]string{"start", "--join=a,b,c"}, []string{"a:" + base.DefaultPort, "b:" + base.DefaultPort, "c:" + base.DefaultPort}}, 786 {[]string{"start", "--join=a", "--join=b"}, []string{"a:" + base.DefaultPort, "b:" + base.DefaultPort}}, 787 {[]string{"start", "--join=127.0.0.1"}, []string{"127.0.0.1:" + base.DefaultPort}}, 788 {[]string{"start", "--join=127.0.0.1:"}, []string{"127.0.0.1:" + base.DefaultPort}}, 789 {[]string{"start", "--join=127.0.0.1,abc"}, []string{"127.0.0.1:" + base.DefaultPort, "abc:" + base.DefaultPort}}, 790 {[]string{"start", "--join=[::1],[::2]"}, []string{"[::1]:" + base.DefaultPort, "[::2]:" + base.DefaultPort}}, 791 {[]string{"start", "--join=[::1]:123,[::2]"}, []string{"[::1]:123", "[::2]:" + base.DefaultPort}}, 792 {[]string{"start", "--join=[::1],127.0.0.1"}, []string{"[::1]:" + base.DefaultPort, "127.0.0.1:" + base.DefaultPort}}, 793 {[]string{"start", "--join=[::1]:123", "--join=[::2]"}, []string{"[::1]:123", "[::2]:" + base.DefaultPort}}, 794 } 795 796 for i, td := range testData { 797 initCLIDefaults() 798 if err := f.Parse(td.args); err != nil { 799 t.Fatalf("Parse(%#v) got unexpected error: %v", td.args, err) 800 } 801 802 if err := extraClientFlagInit(); err != nil { 803 t.Fatal(err) 804 } 805 806 var actual []string 807 myHostname, _ := os.Hostname() 808 for _, addr := range serverCfg.JoinList { 809 res, err := resolver.NewResolver(addr) 810 if err != nil { 811 t.Error(err) 812 } 813 actualAddr := res.Addr() 814 // Normalize the local hostname to make the test location-agnostic. 815 actualAddr = strings.ReplaceAll(actualAddr, myHostname, "HOSTNAME") 816 actual = append(actual, actualAddr) 817 } 818 if !reflect.DeepEqual(td.expectedJoin, actual) { 819 t.Errorf("%d. serverCfg.JoinList expected %#v, but got %#v. td.args was '%#v'.", 820 i, td.expectedJoin, actual, td.args) 821 } 822 } 823 } 824 825 func TestClientConnSettings(t *testing.T) { 826 defer leaktest.AfterTest(t)() 827 828 // For some reason, when run under stress all these test cases fail due to the 829 // `--host` flag being unknown to quitCmd. Just skip this under stress. 830 if testutils.NightlyStress() { 831 t.Skip() 832 } 833 834 // Avoid leaking configuration changes after the tests end. 835 defer initCLIDefaults() 836 837 f := quitCmd.Flags() 838 testData := []struct { 839 args []string 840 expectedAddr string 841 }{ 842 {[]string{"quit"}, ":" + base.DefaultPort}, 843 {[]string{"quit", "--host", "127.0.0.1"}, "127.0.0.1:" + base.DefaultPort}, 844 {[]string{"quit", "--host", "192.168.0.111"}, "192.168.0.111:" + base.DefaultPort}, 845 {[]string{"quit", "--host", ":12345"}, ":12345"}, 846 {[]string{"quit", "--host", "127.0.0.1:12345"}, "127.0.0.1:12345"}, 847 // confirm hostnames will work 848 {[]string{"quit", "--host", "my.host.name"}, "my.host.name:" + base.DefaultPort}, 849 {[]string{"quit", "--host", "myhostname"}, "myhostname:" + base.DefaultPort}, 850 // confirm IPv6 works too 851 {[]string{"quit", "--host", "[::1]"}, "[::1]:" + base.DefaultPort}, 852 {[]string{"quit", "--host", "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]"}, 853 "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]:" + base.DefaultPort}, 854 855 // Deprecated syntax. 856 {[]string{"quit", "--port", "12345"}, ":12345"}, 857 {[]string{"quit", "--host", "127.0.0.1", "--port", "12345"}, "127.0.0.1:12345"}, 858 } 859 860 for i, td := range testData { 861 initCLIDefaults() 862 if err := f.Parse(td.args); err != nil { 863 t.Fatalf("Parse(%#v) got unexpected error: %v", td.args, err) 864 } 865 866 if err := extraClientFlagInit(); err != nil { 867 t.Fatal(err) 868 } 869 if td.expectedAddr != serverCfg.Addr { 870 t.Errorf("%d. serverCfg.Addr expected '%s', but got '%s'. td.args was '%#v'.", 871 i, td.expectedAddr, serverCfg.Addr, td.args) 872 } 873 } 874 } 875 876 func TestHttpHostFlagValue(t *testing.T) { 877 defer leaktest.AfterTest(t)() 878 879 // Avoid leaking configuration changes after the tests end. 880 defer initCLIDefaults() 881 882 f := startCmd.Flags() 883 testData := []struct { 884 args []string 885 expected string 886 tlsEnabled bool 887 }{ 888 {[]string{"start", "--http-addr", "127.0.0.1"}, "127.0.0.1:" + base.DefaultHTTPPort, true}, 889 {[]string{"start", "--http-addr", "192.168.0.111"}, "192.168.0.111:" + base.DefaultHTTPPort, true}, 890 // confirm --http-host still works 891 {[]string{"start", "--http-host", "127.0.0.1"}, "127.0.0.1:" + base.DefaultHTTPPort, true}, 892 {[]string{"start", "--http-addr", ":12345", "--http-host", "192.168.0.111"}, "192.168.0.111:12345", true}, 893 // confirm --http-port still works 894 {[]string{"start", "--http-port", "12345"}, ":12345", true}, 895 {[]string{"start", "--http-addr", "192.168.0.111", "--" + cliflags.ListenHTTPPort.Name, "12345"}, "192.168.0.111:12345", true}, 896 // confirm hostnames will work 897 {[]string{"start", "--http-addr", "my.host.name"}, "my.host.name:" + base.DefaultHTTPPort, true}, 898 {[]string{"start", "--http-addr", "myhostname"}, "myhostname:" + base.DefaultHTTPPort, true}, 899 // confirm IPv6 works too 900 {[]string{"start", "--http-addr", "[::1]"}, "[::1]:" + base.DefaultHTTPPort, true}, 901 {[]string{"start", "--http-addr", "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]"}, 902 "[2622:6221:e663:4922:fc2b:788b:fadd:7b48]:" + base.DefaultHTTPPort, true}, 903 // Confirm that the host part is derived from --listen-addr if not specified. 904 {[]string{"start", "--listen-addr=blah:1111"}, "blah:" + base.DefaultHTTPPort, true}, 905 {[]string{"start", "--listen-addr=blah:1111", "--http-addr=:1234"}, "blah:1234", true}, 906 {[]string{"start", "--listen-addr=blah", "--http-addr=:1234"}, "blah:1234", true}, 907 // Confirm that --insecure implies no TLS. 908 {[]string{"start", "--http-addr=127.0.0.1", "--insecure"}, "127.0.0.1:" + base.DefaultHTTPPort, false}, 909 // Confirm that --unencrypted-localhost-http overrides the hostname part (and disables TLS). 910 {[]string{"start", "--http-addr=:1234", "--unencrypted-localhost-http"}, "localhost:1234", false}, 911 {[]string{"start", "--listen-addr=localhost:1111", "--unencrypted-localhost-http"}, "localhost:" + base.DefaultHTTPPort, false}, 912 {[]string{"start", "--http-addr=127.0.0.1", "--unencrypted-localhost-http"}, 913 "ERROR: --unencrypted-localhost-http is incompatible with --http-addr=127.0.0.1:8080", false}, 914 {[]string{"start", "--http-addr=incompatible", "--unencrypted-localhost-http"}, 915 "ERROR: --unencrypted-localhost-http is incompatible with --http-addr=incompatible:8080", false}, 916 {[]string{"start", "--http-addr=incompatible:1111", "--unencrypted-localhost-http"}, 917 "ERROR: --unencrypted-localhost-http is incompatible with --http-addr=incompatible:1111", false}, 918 } 919 920 for i, td := range testData { 921 initCLIDefaults() 922 923 if err := f.Parse(td.args); err != nil { 924 t.Fatalf("Parse(%#v) got unexpected error: %v", td.args, err) 925 } 926 927 err := extraServerFlagInit(startCmd) 928 929 expectErr := strings.HasPrefix(td.expected, "ERROR:") 930 expectedErr := strings.TrimPrefix(td.expected, "ERROR: ") 931 if err != nil { 932 if !expectErr { 933 t.Fatalf("%d. error: %v", i, err) 934 } 935 if !testutils.IsError(err, expectedErr) { 936 t.Fatalf("%d. expected error %q, got: %v", i, expectedErr, err) 937 } 938 continue 939 } 940 if err == nil && expectErr { 941 t.Fatalf("%d expected error %q, got none", i, expectedErr) 942 } 943 exp := "http" 944 if td.tlsEnabled { 945 exp = "https" 946 } 947 if td.expected != serverCfg.HTTPAddr { 948 t.Errorf("%d. serverCfg.HTTPAddr expected '%s', but got '%s'. td.args was '%#v'.", i, td.expected, serverCfg.HTTPAddr, td.args) 949 } 950 if exp != serverCfg.HTTPRequestScheme() { 951 t.Errorf("%d. TLS config expected %s, got %s. td.args was '%#v'.", i, exp, serverCfg.HTTPRequestScheme(), td.args) 952 } 953 } 954 }