github.com/thiagoyeds/go-cloud@v0.26.0/postgres/postgres_test.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 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 // https://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 postgres 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "io/ioutil" 22 "net/url" 23 "os" 24 "os/exec" 25 "os/user" 26 "path/filepath" 27 "runtime" 28 "testing" 29 "time" 30 ) 31 32 func TestOpen(t *testing.T) { 33 if runtime.GOOS == "windows" { 34 t.Skip("Can't use Unix sockets on Windows") 35 } 36 postgresPath, err := exec.LookPath("postgres") 37 if err != nil { 38 t.Skip("Can't find postgres:", err) 39 } 40 initdbPath, err := exec.LookPath("initdb") 41 if err != nil { 42 t.Skip("Can't find initdb:", err) 43 } 44 45 // Create a temporary database data directory. 46 currUser, err := user.Current() 47 if err != nil { 48 t.Fatal(err) 49 } 50 dir, err := ioutil.TempDir("", "gocloud_postgres_test") 51 if err != nil { 52 t.Fatal(err) 53 } 54 defer func() { 55 if err := os.RemoveAll(dir); err != nil { 56 t.Errorf("Cleaning up: %v", err) 57 } 58 }() 59 dataDir := filepath.Join(dir, "data") 60 initdbCmd := exec.Command(initdbPath, "-U", currUser.Username, "-D", dataDir) 61 initdbOutput := new(bytes.Buffer) 62 initdbCmd.Stdout = initdbOutput 63 initdbCmd.Stderr = initdbOutput 64 err = initdbCmd.Run() 65 if err != nil { 66 t.Log(initdbOutput) 67 t.Fatal(err) 68 } 69 70 // Configure the database server to listen on a Unix socket located in the temporary directory. 71 socketDir, err := filepath.Abs(filepath.Join(dir, "socket")) 72 if err != nil { 73 t.Fatal(err) 74 } 75 if err := os.Mkdir(socketDir, 0777); err != nil { 76 t.Fatal(err) 77 } 78 confData := new(bytes.Buffer) 79 fmt.Fprintf(confData, "unix_socket_directories = '%s'\n", socketDir) 80 err = ioutil.WriteFile(filepath.Join(dataDir, "postgresql.conf"), confData.Bytes(), 0666) 81 if err != nil { 82 t.Fatal(err) 83 } 84 85 // Start the database server (and arrange for it to be stopped at test end). 86 server := exec.Command(postgresPath, "-D", dataDir) 87 serverOutput := new(bytes.Buffer) 88 server.Stdout = serverOutput 89 server.Stderr = serverOutput 90 if err := server.Start(); err != nil { 91 t.Fatal(err) 92 } 93 serverSignaled := false 94 defer func() { 95 if !serverSignaled { 96 if err := server.Process.Kill(); err != nil { 97 t.Error("Stopping server:", err) 98 } 99 } 100 // Wait for server to exit, but ignore the expected failure error code. 101 server.Wait() 102 if t.Failed() { 103 t.Log(serverOutput) 104 } 105 }() 106 107 // Now the actual test: can we connect to the database via URL opener? 108 ctx := context.Background() 109 dbURL := &url.URL{ 110 Scheme: "blablabla", // Intentionally not "postgres" to ensure any scheme works. 111 User: url.User(currUser.Username), 112 Path: "/postgres", 113 // Use the query parameter to avoid https://github.com/lib/pq/issues/796 114 RawQuery: url.Values{"host": {socketDir}}.Encode(), 115 } 116 t.Logf("PostgreSQL URL: %s", dbURL) 117 db, err := new(URLOpener).OpenPostgresURL(ctx, dbURL) 118 if err != nil { 119 t.Fatal(err) 120 } 121 // Developing a realistic query would be hard, so instead we trust that the 122 // PostgreSQL library reports healthy correctly. Since there's no way to 123 // synchronize the server start and the ping, we might have to ping a few 124 // times before it is healthy. (The overall test runner timeout will interrupt 125 // if this takes too long.) 126 for { 127 err := db.Ping() 128 if err == nil { 129 break 130 } 131 t.Log("Ping:", err) 132 time.Sleep(100 * time.Millisecond) 133 } 134 if err := db.Close(); err != nil { 135 t.Error("Close:", err) 136 } 137 server.Process.Signal(os.Interrupt) 138 serverSignaled = true 139 }