github.com/github/skeema@v1.2.6/util/config_test.go (about) 1 package util 2 3 import ( 4 "io/ioutil" 5 "os" 6 "reflect" 7 "strings" 8 "testing" 9 10 "github.com/skeema/mybase" 11 ) 12 13 func TestAddGlobalConfigFiles(t *testing.T) { 14 cmdSuite := mybase.NewCommandSuite("skeematest", "", "") 15 AddGlobalOptions(cmdSuite) 16 cmd := mybase.NewCommand("diff", "", "", nil) 17 cmd.AddArg("environment", "production", false) 18 cmdSuite.AddSubCommand(cmd) 19 20 // Expectation: global config files not existing isn't fatal 21 cfg := mybase.ParseFakeCLI(t, cmdSuite, "skeema diff") 22 AddGlobalConfigFiles(cfg) 23 if cfg.Supplied("password") || cfg.Changed("password") { 24 t.Errorf("Expected password to be unsupplied and unchanged from default; instead found %q", cfg.GetRaw("password")) 25 } 26 27 os.MkdirAll("fake-etc", 0777) 28 os.MkdirAll("fake-home", 0777) 29 ioutil.WriteFile("fake-etc/skeema", []byte("user=one\npassword=foo\n"), 0777) 30 ioutil.WriteFile("fake-home/.my.cnf", []byte("doesnt-exist\nuser=two\nhost=uhoh\n"), 0777) 31 defer func() { 32 os.RemoveAll("fake-etc") 33 os.RemoveAll("fake-home") 34 }() 35 36 // Expectation: both global option files get applied; the one in home 37 // overrides the one in etc; undefined options don't cause problems for 38 // a file ending in .my.cnf; host is ignored in .my.cnf 39 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff") 40 AddGlobalConfigFiles(cfg) 41 if actualUser := cfg.Get("user"); actualUser != "two" { 42 t.Errorf("Expected user in fake-home/.my.cnf to take precedence; instead found %s", actualUser) 43 } 44 if actualPassword := cfg.Get("password"); actualPassword != "foo" { 45 t.Errorf("Expected password to come from fake-etc/skeema; instead found %s", actualPassword) 46 } 47 if cfg.Supplied("host") { 48 t.Error("Expected host to be ignored in .my.cnf, but it was parsed anyway") 49 } 50 51 // Test --skip-my-cnf to avoid parsing .my.cnf 52 // Expectation: both only the skeema file in etc gets used due to the override option 53 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --skip-my-cnf") 54 AddGlobalConfigFiles(cfg) 55 if actualUser := cfg.Get("user"); actualUser != "one" { 56 t.Errorf("Expected user in fake-home/.my.cnf to be skipped; instead found %s", actualUser) 57 } 58 59 // Introduce an invalid option into fake-etc/skeema. Expectation: the file 60 // is no longer used as a source, even for options declared above the invalid 61 // one. 62 ioutil.WriteFile("fake-etc/skeema", []byte("user=one\npassword=foo\nthis will not parse"), 0777) 63 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff") 64 AddGlobalConfigFiles(cfg) 65 if actualUser := cfg.Get("user"); actualUser != "two" { 66 t.Errorf("Expected user in fake-home/.my.cnf to take precedence; instead found %s", actualUser) 67 } 68 if cfg.Supplied("password") || cfg.Changed("password") { 69 t.Errorf("Expected password to be unsupplied and unchanged from default; instead found %q", cfg.GetRaw("password")) 70 } 71 } 72 73 func TestPasswordOption(t *testing.T) { 74 cmdSuite := mybase.NewCommandSuite("skeematest", "", "") 75 AddGlobalOptions(cmdSuite) 76 cmdSuite.AddSubCommand(mybase.NewCommand("diff", "", "", nil)) 77 78 // No MYSQL_PWD env, no password option set on CLI: should stay default 79 os.Unsetenv("MYSQL_PWD") 80 cfg := mybase.ParseFakeCLI(t, cmdSuite, "skeema diff") 81 if err := ProcessSpecialGlobalOptions(cfg); err != nil { 82 t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err) 83 } 84 if cfg.Changed("password") { 85 t.Errorf("Expected password to remain default, instead it is set to %s", cfg.Get("password")) 86 } 87 88 // Password set in env but to a blank string: should be same as specifying 89 // nothing at all 90 os.Setenv("MYSQL_PWD", "") 91 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff") 92 if err := ProcessSpecialGlobalOptions(cfg); err != nil { 93 t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err) 94 } 95 if cfg.Changed("password") { 96 t.Errorf("Expected password to remain default, instead it is set to %s", cfg.Get("password")) 97 } 98 99 // Password set in env only, to a non-blank string 100 os.Setenv("MYSQL_PWD", "helloworld") 101 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff") 102 if err := ProcessSpecialGlobalOptions(cfg); err != nil { 103 t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err) 104 } 105 if cfg.Get("password") != "helloworld" { 106 t.Errorf("Expected password to be helloworld, instead found %s", cfg.Get("password")) 107 } 108 109 // Password set on CLI and in env: CLI should win out 110 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password=heyearth") 111 if err := ProcessSpecialGlobalOptions(cfg); err != nil { 112 t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err) 113 } 114 if cfg.Get("password") != "heyearth" { 115 t.Errorf("Expected password to be heyearth, instead found %s", cfg.Get("password")) 116 } 117 118 // Password set in file and env: file should win out 119 fakeFileSource := mybase.SimpleSource(map[string]string{ 120 "password": "howdyplanet", 121 }) 122 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff", fakeFileSource) 123 if err := ProcessSpecialGlobalOptions(cfg); err != nil { 124 t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err) 125 } 126 if cfg.Get("password") != "howdyplanet" { 127 t.Errorf("Expected password to be howdyplanet, instead found %s", cfg.Get("password")) 128 } 129 130 // ProcessSpecialGlobalOptions should error with valueless password if STDIN 131 // isn't a TTY. Test bare "password" (no =) on both CLI and config file. 132 oldStdin := os.Stdin 133 defer func() { 134 os.Stdin = oldStdin 135 }() 136 var err error 137 if os.Stdin, err = os.Open("../testdata/setup.sql"); err != nil { 138 t.Fatalf("Unable to open ../testdata/setup.sql: %s", err) 139 } 140 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password") 141 if err := ProcessSpecialGlobalOptions(cfg); err == nil { 142 t.Error("Expected ProcessSpecialGlobalOptions to return an error for non-TTY STDIN, but it did not") 143 } 144 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password", fakeFileSource) 145 if err := ProcessSpecialGlobalOptions(cfg); err == nil { 146 t.Error("Expected ProcessSpecialGlobalOptions to return an error for non-TTY STDIN, but it did not") 147 } 148 fakeFileSource["password"] = "" 149 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff", fakeFileSource) 150 if err := ProcessSpecialGlobalOptions(cfg); err == nil { 151 t.Error("Expected ProcessSpecialGlobalOptions to return an error for non-TTY STDIN, but it did not") 152 } 153 154 // Setting password to an empty string explicitly should not trigger TTY prompt 155 // (note: STDIN intentionally still points to a file here, from test above) 156 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password=") 157 if err := ProcessSpecialGlobalOptions(cfg); err != nil { 158 t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %v", err) 159 } 160 if cfg.Changed("password") { 161 t.Error("Password unexpectedly considered changed from default") 162 } 163 cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password=''") 164 if err := ProcessSpecialGlobalOptions(cfg); err != nil { 165 t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %v", err) 166 } 167 } 168 169 func TestSplitConnectOptions(t *testing.T) { 170 assertConnectOpts := func(connectOptions string, expectedPair ...string) { 171 result, err := SplitConnectOptions(connectOptions) 172 if err != nil { 173 t.Errorf("Unexpected error from SplitConnectOptions(\"%s\"): %s", connectOptions, err) 174 } 175 expected := make(map[string]string, len(expectedPair)) 176 for _, pair := range expectedPair { 177 tokens := strings.SplitN(pair, "=", 2) 178 expected[tokens[0]] = tokens[1] 179 } 180 if !reflect.DeepEqual(expected, result) { 181 t.Errorf("Expected SplitConnectOptions(\"%s\") to return %v, instead received %v", connectOptions, expected, result) 182 } 183 } 184 assertConnectOpts("") 185 assertConnectOpts("foo='bar'", "foo='bar'") 186 assertConnectOpts("bool=true,quotes='yes,no'", "bool=true", "quotes='yes,no'") 187 assertConnectOpts(`escaped=we\'re ok`, `escaped=we\'re ok`) 188 assertConnectOpts(`escquotes='we\'re still quoted',this=that`, `escquotes='we\'re still quoted'`, "this=that") 189 190 expectError := []string{ 191 "foo=bar,'bip'=bap", 192 "flip=flap=flarb", 193 "foo=,yes=no", 194 "too_many_commas=1,,between_these='yeah'", 195 "one=true,two=false,", 196 ",bad=true", 197 ",", 198 "unterminated='yep", 199 "trailingBackSlash=true\\", 200 "bareword", 201 "twice=true,bool=true,twice=true", 202 "start=1,bareword", 203 } 204 for _, connOpts := range expectError { 205 if _, err := SplitConnectOptions(connOpts); err == nil { 206 t.Errorf("Did not get expected error from SplitConnectOptions(\"%s\")", connOpts) 207 } 208 } 209 } 210 211 func TestRealConnectOptions(t *testing.T) { 212 assertResult := func(input, expected string) { 213 actual, err := RealConnectOptions(input) 214 if err != nil { 215 t.Errorf("Unexpected error result from RealConnectOptions(\"%s\"): %s", input, err) 216 } else if actual != expected { 217 t.Errorf("Expected RealConnectOptions(\"%s\") to return \"%s\", instead found \"%s\"", input, expected, actual) 218 } 219 } 220 assertResult("", "") 221 assertResult("foo=1", "foo=1") 222 assertResult("allowAllFiles=true", "") 223 assertResult("foo='ok,cool',multiStatements=true", "foo='ok,cool'") 224 assertResult("timeout=1s,bar=123", "bar=123") 225 assertResult("strict=1,foo=2,charset='utf8mb4,utf8'", "foo=2") 226 assertResult("timeout=10ms,TIMEOUT=20ms,timeOut=30ms", "") 227 228 // Ensure errors from SplitConnectOptions are passed through 229 if _, err := RealConnectOptions("foo='ok,cool',multiStatements=true,bareword"); err == nil { 230 t.Error("Expected error from SplitConnectOptions to be passed through to RealConnectOptions, but err is nil") 231 } 232 }