File: common_test.go

package info (click to toggle)
cloudsql-proxy 1.33.14-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 900 kB
  • sloc: sh: 57; makefile: 25
file content (194 lines) | stat: -rw-r--r-- 4,782 bytes parent folder | download
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
}