github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/dtestutils/sql_server_driver/server.go (about) 1 // Copyright 2022 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package sql_server_driver 16 17 import ( 18 "io" 19 "os" 20 "path/filepath" 21 "strings" 22 23 "github.com/creasty/defaults" 24 "gopkg.in/yaml.v3" 25 ) 26 27 // |Connection| represents a single connection to a sql-server instance defined 28 // in the test. The connection will be established and every |Query| in 29 // |Queries| will be run against it. At the end, the connection will be torn down. 30 // If |RestartServer| is non-nil, the server which the connection targets will 31 // be restarted after the connection is terminated. 32 type Connection struct { 33 On string `yaml:"on"` 34 Queries []Query `yaml:"queries"` 35 RestartServer *RestartArgs `yaml:"restart_server"` 36 37 // Rarely needed, allows the entire connection assertion to be retried 38 // on an assertion failure. Use this is only for idempotent connection 39 // interactions and only if the sql-server is prone to tear down the 40 // connection based on things that are happening, such as cluster role 41 // transitions. 42 RetryAttempts int `yaml:"retry_attempts"` 43 44 // The user to connect as. 45 User string `default:"root" yaml:"user"` 46 // The password to connect with. 47 Pass string `yaml:"password"` 48 PassFile string `yaml:"password_file"` 49 // Any driver params to pass in the DSN. 50 DriverParams map[string]string `yaml:"driver_params"` 51 } 52 53 func (c *Connection) UnmarshalYAML(unmarshal func(interface{}) error) error { 54 defaults.Set(c) 55 type plain Connection 56 if err := unmarshal((*plain)(c)); err != nil { 57 return err 58 } 59 return nil 60 } 61 62 func (c Connection) Password() (string, error) { 63 if c.PassFile != "" { 64 passFile := c.PassFile 65 if v := os.Getenv("TESTGENDIR"); v != "" { 66 passFile = strings.ReplaceAll(passFile, "$TESTGENDIR", v) 67 } 68 bs, err := os.ReadFile(passFile) 69 if err != nil { 70 return "", err 71 } 72 return strings.TrimSpace(string(bs)), nil 73 } 74 return c.Pass, nil 75 } 76 77 // |RestartArgs| are possible arguments, to change the arguments which are 78 // provided to the sql-server process when it is restarted. This is used, for 79 // example, to change server config on a restart. 80 type RestartArgs struct { 81 Args *[]string `yaml:"args"` 82 Envs *[]string `yaml:"envs"` 83 } 84 85 // |TestRepo| represents an init'd dolt repository that is available to a 86 // server instance. It can be created with some files and with remotes defined. 87 // |Name| can include path components separated by `/`, which will create the 88 // repository in a subdirectory. 89 type TestRepo struct { 90 Name string `yaml:"name"` 91 WithFiles []WithFile `yaml:"with_files"` 92 WithRemotes []WithRemote `yaml:"with_remotes"` 93 94 // Only valid on Test.Repos, not in Test.MultiRepos.Repos. If set, a 95 // sql-server process will be run against this TestRepo. It will be 96 // available as TestRepo.Name. 97 Server *Server `yaml:"server"` 98 ExternalServer *ExternalServer `yaml:"external-server"` 99 } 100 101 // |MultiRepo| is a subdirectory where many |TestRepo|s can be defined. You can 102 // start a sql-server on a |MultiRepo|, in which case there will be no default 103 // database to connect to. 104 type MultiRepo struct { 105 Name string `yaml:"name"` 106 Repos []TestRepo `yaml:"repos"` 107 WithFiles []WithFile `yaml:"with_files"` 108 109 // If set, a sql-server process will be run against this TestRepo. It 110 // will be available as MultiRepo.Name. 111 Server *Server `yaml:"server"` 112 } 113 114 // |WithRemote| defines remotes which should be defined on the repository 115 // before the sql-server is started. 116 type WithRemote struct { 117 Name string `yaml:"name"` 118 URL string `yaml:"url"` 119 } 120 121 // |WithFile| defines a file and its contents to be created in a |Repo| or 122 // |MultiRepo| before the servers are started. 123 type WithFile struct { 124 Name string `yaml:"name"` 125 126 // The contents of the file, provided inline in the YAML. 127 Contents string `yaml:"contents"` 128 129 // A source file path to copy to |Name|. Mutually exclusive with 130 // Contents. 131 SourcePath string `yaml:"source_path"` 132 } 133 134 func (f WithFile) WriteAtDir(dir string) error { 135 path := filepath.Join(dir, f.Name) 136 d := filepath.Dir(path) 137 err := os.MkdirAll(d, 0750) 138 if err != nil { 139 return err 140 } 141 if f.SourcePath != "" { 142 sourcePath := f.SourcePath 143 if v := os.Getenv("TESTGENDIR"); v != "" { 144 sourcePath = strings.ReplaceAll(sourcePath, "$TESTGENDIR", v) 145 } 146 source, err := os.Open(sourcePath) 147 if err != nil { 148 return err 149 } 150 defer source.Close() 151 dest, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0550) 152 if err != nil { 153 return err 154 } 155 _, err = io.Copy(dest, source) 156 return err 157 } else { 158 return os.WriteFile(path, []byte(f.Contents), 0550) 159 } 160 } 161 162 // |Server| defines a sql-server process to start. |Name| must match the 163 // top-level |Name| of a |TestRepo| or |MultiRepo|. 164 type Server struct { 165 Name string `yaml:"name"` 166 Args []string `yaml:"args"` 167 Envs []string `yaml:"envs"` 168 169 // The |Port| which the server will be running on. For now, it is up to 170 // the |Args| to make sure this is true. Defaults to 3308. 171 Port int `yaml:"port"` 172 173 // DebugPort if set to a non-zero value will cause this server to be started with |dlv| listening for a debugger 174 // connection on the port given. 175 DebugPort int `yaml:"debug_port"` 176 177 // Assertions to be run against the log output of the server process 178 // after the server process successfully terminates. 179 LogMatches []string `yaml:"log_matches"` 180 181 // Assertions to be run against the log output of the server process 182 // after the server process exits with an error. If |ErrorMatches| is 183 // defined, then the server process must exit with a non-0 exit code 184 // after it is launched. This will be asserted before any |Connections| 185 // interactions are performed. 186 ErrorMatches []string `yaml:"error_matches"` 187 } 188 189 type ExternalServer struct { 190 Name string `yaml:"name"` 191 Host string `yaml:"host"` 192 User string `yaml:"user"` 193 Password string `yaml:"password"` 194 // The |Port| which the server will be running on. For now, it is up to 195 // the |Args| to make sure this is true. Defaults to 3308. 196 Port int `yaml:"port"` 197 } 198 199 // The primary interaction of a |Connection|. Either |Query| or |Exec| should 200 // be set, not both. 201 type Query struct { 202 // Run a query against the connection. 203 Query string `yaml:"query"` 204 205 // Run a command against the connection. 206 Exec string `yaml:"exec"` 207 208 // Args to be passed as query parameters to either Query or Exec. 209 Args []string `yaml:"args"` 210 211 // This can only be non-empty for a |Query|. Asserts the results of the 212 // |Query|. 213 Result QueryResult `yaml:"result"` 214 215 // If this is non-empty, asserts the |Query| or the |Exec| 216 // generates an error that matches this string. 217 ErrorMatch string `yaml:"error_match"` 218 219 // If this is non-zero, it represents the number of times to try the 220 // |Query| or the |Exec| and to check its assertions before we fail the 221 // test as a result of failed assertions. When interacting with queries 222 // that introspect things like replication state, this can be used to 223 // wait for quiescence in an inherently racey process. Interactions 224 // will be delayed slightly between each failure. 225 RetryAttempts int `yaml:"retry_attempts"` 226 } 227 228 // |QueryResult| specifies assertions on the results of a |Query|. Columns must 229 // be specified for a |Query| and the query results must fully match. If Rows 230 // are omitted, anything is allowed as long as all rows are read successfully. 231 // All assertions here are string equality. 232 type QueryResult struct { 233 Columns []string `yaml:"columns"` 234 Rows ResultRows `yaml:"rows"` 235 } 236 237 type ResultRows struct { 238 Or *[][][]string 239 } 240 241 func (r *ResultRows) UnmarshalYAML(value *yaml.Node) error { 242 if value.Kind == yaml.SequenceNode { 243 res := make([][][]string, 1) 244 r.Or = &res 245 return value.Decode(&(*r.Or)[0]) 246 } 247 var or struct { 248 Or *[][][]string `yaml:"or"` 249 } 250 err := value.Decode(&or) 251 if err != nil { 252 return err 253 } 254 r.Or = or.Or 255 return nil 256 }