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  }