1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package tests contains end to end tests meant to verify the Cloud SQL Auth proxy
// works as expected when executed as a binary.
//
// Required flags:
//
// -mysql_conn_name, -db_user, -db_pass
package tests
import (
"bufio"
"bytes"
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"runtime"
"strings"
"testing"
)
var (
binPath = ""
)
func TestMain(m *testing.M) {
flag.Parse()
// compile the proxy as a binary
var err error
binPath, err = compileProxy()
if err != nil {
log.Fatalf("failed to compile proxy: %s", err)
}
// Run tests and cleanup
rtn := m.Run()
os.RemoveAll(binPath)
os.Exit(rtn)
}
// compileProxy compiles the binary into a temporary directory, and returns the path to the file or any error that occured.
func compileProxy() (string, error) {
// get path of the cmd pkg
_, f, _, ok := runtime.Caller(0)
if !ok {
return "", fmt.Errorf("failed to find cmd pkg")
}
projRoot := path.Dir(path.Dir(f)) // cd ../..
pkgPath := path.Join(projRoot, "cmd", "cloud_sql_proxy")
// compile the proxy into a tmp directory
tmp, err := ioutil.TempDir("", "")
if err != nil {
return "", fmt.Errorf("failed to create temp dir: %s", err)
}
b := path.Join(tmp, "cloud_sql_proxy")
if runtime.GOOS == "windows" {
b += ".exe"
}
cmd := exec.Command("go", "build", "-o", b, pkgPath)
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("failed to run 'go build': %w \n %s", err, out)
}
return b, nil
}
// proxyExec represents an execution of the Cloud SQL proxy.
type ProxyExec struct {
Out io.ReadCloser
cmd *exec.Cmd
cancel context.CancelFunc
closers []io.Closer
done chan bool // closed once the cmd is completed
err error
}
// StartProxy returns a proxyExec representing a running instance of the proxy.
func StartProxy(ctx context.Context, args ...string) (*ProxyExec, error) {
var err error
ctx, cancel := context.WithCancel(ctx)
p := ProxyExec{
cmd: exec.CommandContext(ctx, binPath, args...),
cancel: cancel,
done: make(chan bool),
}
pr, pw, err := os.Pipe()
if err != nil {
return nil, fmt.Errorf("unable to open stdout pipe: %w", err)
}
defer pw.Close()
p.Out, p.cmd.Stdout, p.cmd.Stderr = pr, pw, pw
p.closers = append(p.closers, pr)
if err := p.cmd.Start(); err != nil {
defer p.Close()
return nil, fmt.Errorf("unable to start cmd: %w", err)
}
// when process is complete, mark as finished
go func() {
defer close(p.done)
p.err = p.cmd.Wait()
}()
return &p, nil
}
// Stop sends the pskill signal to the proxy and returns.
func (p *ProxyExec) Kill() {
p.cancel()
}
// Waits until the execution is completed and returns any error.
func (p *ProxyExec) Wait() error {
select {
case <-p.done:
return p.err
}
}
// Stop sends the pskill signal to the proxy and returns.
func (p *ProxyExec) Done() bool {
select {
case <-p.done:
return true
default:
}
return false
}
// Close releases any resources assotiated with the instance.
func (p *ProxyExec) Close() {
p.cancel()
for _, c := range p.closers {
c.Close()
}
}
// WaitForServe waits until the proxy ready to serve traffic. Returns any output from the proxy
// while starting or any errors experienced before the proxy was ready to server.
func (p *ProxyExec) WaitForServe(ctx context.Context) (output string, err error) {
// Watch for the "Ready for new connections" to indicate the proxy is listening
buf, in, errCh := new(bytes.Buffer), bufio.NewReader(p.Out), make(chan error, 1)
go func() {
defer close(errCh)
for {
// if ctx is finished, stop processing
select {
case <-ctx.Done():
return
default:
}
s, err := in.ReadString('\n')
if err != nil {
errCh <- err
return
}
buf.WriteString(s)
if strings.Contains(s, "Ready for new connections") {
errCh <- nil
return
}
}
}()
// Wait for either the background thread of the context to complete
select {
case <-ctx.Done():
return buf.String(), fmt.Errorf("context done: %w", ctx.Err())
case err := <-errCh:
if err != nil {
return buf.String(), fmt.Errorf("proxy start failed: %w", err)
}
}
return buf.String(), nil
}
|