Skip to content

Commit 99d526e

Browse files
author
Mariano Gappa
authored
Merge pull request #17 from marianogappa/adds_tests
Adds tests
2 parents 746d225 + 219abcc commit 99d526e

11 files changed

+345
-105
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
sql
22
dist
3+
.DS_Store

.travis.yml

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
language: go
22

33
go:
4-
- 1.10.3
4+
- 1.11
5+
6+
env:
7+
- DOCKER_COMPOSE_VERSION=1.21.1
8+
9+
before_install:
10+
- sudo rm /usr/local/bin/docker-compose
11+
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
12+
- chmod +x docker-compose
13+
- sudo mv docker-compose /usr/local/bin
14+
15+
script:
16+
- make test

Dockerfile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM golang:1.11
2+
3+
RUN apt-get update && apt-get install -y --no-install-recommends mysql-client && rm -rf /var/lib/apt/lists/*
4+
5+
ENTRYPOINT [ "go", "test", "-v", "." ]

Makefile

+3-28
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,11 @@ OS = $(shell uname | tr [:upper:] [:lower:])
33
ARTIFACT = sql
44

55
build: GOOS ?= ${OS}
6-
build: GOARCH ?= amd64
7-
build: clean test
8-
GOOS=${GOOS} GOARCH=${GOARCH} CGO_ENABLED=0 go build -o ${ARTIFACT} -a .
9-
10-
clean: cleanmac
11-
rm -f ${ARTIFACT}
12-
13-
cleanmac:
14-
find . -name '*.DS_Store' -type f -delete
6+
build: test
7+
GOOS=${GOOS} GOARCH=amd64 CGO_ENABLED=0 go build -o ${ARTIFACT} -a .
158

169
test:
17-
go test
10+
docker-compose --file test-docker-compose.yml up --abort-on-container-exit --force-recreate --renew-anon-volumes
1811

1912
run: build
2013
./${ARTIFACT}
21-
22-
release-linux: TAG ?= latest
23-
release-linux:
24-
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${ARTIFACT} -a .
25-
tar -cf ${ARTIFACT}-linux.tar ${ARTIFACT}
26-
gzip ${ARTIFACT}-linux.tar
27-
rm -rf ${ARTIFACT}
28-
29-
release-darwin: TAG ?= latest
30-
release-darwin:
31-
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o ${ARTIFACT} -a .
32-
tar -cf ${ARTIFACT}-darwin.tar ${ARTIFACT}
33-
gzip ${ARTIFACT}-darwin.tar
34-
rm -rf ${ARTIFACT}
35-
36-
release: TAG ?= latest
37-
release: release-linux release-darwin
38-
git tag ${TAG}

config.go

+8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import (
99
"strings"
1010
)
1111

12+
type database struct {
13+
AppServer string
14+
DbServer string
15+
DbName string
16+
User string
17+
Pass string
18+
}
19+
1220
func mustReadDatabasesConfigFile() map[string]database {
1321
var paths []string
1422
databases := map[string]database{}

main.go

+22-76
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,24 @@ import (
55
"context"
66
"flag"
77
"fmt"
8-
"io"
98
"log"
109
"os"
1110
"os/exec"
12-
"os/signal"
1311
"strings"
1412
"sync"
15-
"syscall"
1613
)
1714

18-
type database struct {
19-
AppServer string
20-
DbServer string
21-
DbName string
22-
User string
23-
Pass string
24-
}
25-
26-
var help = flag.Bool("help", false, "shows usage")
27-
var listDBs = flag.Bool("list-dbs", false, "List all available DBs (used for auto-completion)")
28-
29-
var printLock sync.Mutex
30-
31-
func init() {
32-
flag.BoolVar(help, "h", false, "shows usage")
33-
}
34-
3515
func main() {
16+
var (
17+
flagHelp = flag.Bool("help", false, "shows usage")
18+
flagListDBs = flag.Bool("list-dbs", false, "List all available DBs (used for auto-completion)")
19+
)
20+
flag.BoolVar(flagHelp, "h", false, "shows usage")
3621
flag.Parse()
37-
if *help {
22+
if *flagHelp {
3823
usage("")
3924
}
40-
if *listDBs { // for auto-completion
25+
if *flagListDBs { // for auto-completion
4126
for dbName := range mustReadDatabasesConfigFile() {
4227
fmt.Print(dbName, " ")
4328
}
@@ -51,7 +36,7 @@ func main() {
5136
usage("Target database unspecified; where should I run the query?")
5237
}
5338

54-
var sql string
39+
var query string
5540
var databasesArgs []string
5641

5742
stat, err := os.Stdin.Stat()
@@ -63,17 +48,21 @@ func main() {
6348
if len(os.Args) < 3 {
6449
usage("No SQL to run. Exiting.")
6550
}
66-
sql = os.Args[len(os.Args)-1]
51+
query = os.Args[len(os.Args)-1]
6752
databasesArgs = os.Args[1 : len(os.Args)-1]
6853
} else {
69-
sql = readInput(os.Stdin)
54+
query = readQuery(os.Stdin)
7055
databasesArgs = os.Args[1:]
7156
}
7257

73-
if len(sql) <= 3 {
58+
if len(query) <= 3 {
7459
usage("No SQL to run. Exiting.")
7560
}
7661

62+
os.Exit(_main(databases, databasesArgs, query, newThreadSafePrintliner(os.Stdout).println))
63+
}
64+
65+
func _main(databases map[string]database, databasesArgs []string, query string, println func(string)) int {
7766
targetDatabases := []string{}
7867
for _, k := range databasesArgs {
7968
if _, ok := databases[k]; k != "all" && !ok {
@@ -99,17 +88,17 @@ func main() {
9988
for _, k := range targetDatabases {
10089
go func(db database, k string) {
10190
defer wg.Done()
102-
if r := runSQL(quitContext, db, sql, k, len(targetDatabases) > 1); !r {
91+
if r := runSQL(quitContext, db, query, k, len(targetDatabases) > 1, println); !r {
10392
returnCode = 1
10493
}
10594
}(databases[k], k)
10695
}
10796

10897
wg.Wait()
109-
os.Exit(returnCode)
98+
return returnCode
11099
}
111100

112-
func runSQL(quitContext context.Context, db database, sql string, key string, prependKey bool) bool {
101+
func runSQL(quitContext context.Context, db database, query string, key string, prependKey bool, println func(string)) bool {
113102
userOption := ""
114103
if db.User != "" {
115104
userOption = fmt.Sprintf("-u %v ", db.User)
@@ -135,11 +124,11 @@ func runSQL(quitContext context.Context, db database, sql string, key string, pr
135124

136125
var cmd *exec.Cmd
137126
if db.AppServer != "" {
138-
query := fmt.Sprintf(`'%v'`, strings.Replace(sql, `'`, `'"'"'`, -1))
139-
cmd = exec.CommandContext(quitContext, "ssh", db.AppServer, mysql+options+query)
127+
escapedQuery := fmt.Sprintf(`'%v'`, strings.Replace(query, `'`, `'"'"'`, -1))
128+
cmd = exec.CommandContext(quitContext, "ssh", db.AppServer, mysql+options+escapedQuery)
140129
} else {
141-
args := append(trimEmpty(strings.Split(options, " ")), sql)
142-
cmd = exec.CommandContext(quitContext, "mysql", args...)
130+
args := append(trimEmpty(strings.Split(options, " ")), query)
131+
cmd = exec.CommandContext(quitContext, mysql, args...)
143132
}
144133

145134
stdout, err := cmd.StdoutPipe()
@@ -183,46 +172,3 @@ func runSQL(quitContext context.Context, db database, sql string, key string, pr
183172

184173
return result
185174
}
186-
187-
func println(s string) {
188-
printLock.Lock()
189-
defer printLock.Unlock()
190-
fmt.Println(s)
191-
}
192-
193-
func readInput(r io.Reader) string {
194-
ls := []string{}
195-
var err error
196-
rd := bufio.NewReader(r)
197-
198-
for {
199-
var s string
200-
s, err = rd.ReadString('\n')
201-
202-
if err == io.EOF {
203-
return strings.Join(ls, " ")
204-
}
205-
s = strings.TrimSpace(s)
206-
if len(s) == 0 {
207-
continue
208-
}
209-
ls = append(ls, strings.TrimSpace(s))
210-
}
211-
}
212-
213-
func trimEmpty(s []string) []string {
214-
var r []string
215-
for _, str := range s {
216-
if str != "" {
217-
r = append(r, str)
218-
}
219-
}
220-
return r
221-
}
222-
223-
func awaitSignal(cancel context.CancelFunc) {
224-
signals := make(chan os.Signal)
225-
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
226-
<-signals
227-
cancel()
228-
}

main_test.go

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"log"
6+
"os/exec"
7+
"reflect"
8+
"sort"
9+
"strings"
10+
"testing"
11+
"time"
12+
)
13+
14+
func TestSQL(t *testing.T) {
15+
var err error
16+
for i := 1; i <= 30; i++ { // Try up to 30 times, because MySQL takes a while to become online
17+
var c = exec.Command("mysql", "-h", "test-mysql", "-u", "root", "-e", "SELECT * FROM db1.table1")
18+
if err = c.Run(); err == nil {
19+
break
20+
}
21+
log.Printf("Retrying (%v/30) in 1 sec because MySQL is not yet ready", i)
22+
time.Sleep(1 * time.Second)
23+
}
24+
for err != nil {
25+
t.Errorf("bailing because couldn't connect to MySQL after 30 tries: %v", err)
26+
t.FailNow()
27+
}
28+
29+
var (
30+
testConfig = map[string]database{
31+
"db1": database{DbServer: "test-mysql", DbName: "db1", User: "root", Pass: ""},
32+
"db2": database{DbServer: "test-mysql", DbName: "db2", User: "root", Pass: ""},
33+
"db3": database{DbServer: "test-mysql", DbName: "db3", User: "root", Pass: ""},
34+
}
35+
ts = []struct {
36+
name string
37+
targetDBs []string
38+
query string
39+
expected []string
40+
}{
41+
{
42+
name: "reads from one database",
43+
targetDBs: []string{"db1"},
44+
query: "SELECT id FROM table1",
45+
expected: []string{
46+
"",
47+
"1",
48+
"2",
49+
"3",
50+
},
51+
},
52+
{
53+
name: "reads from two databases",
54+
targetDBs: []string{"db1", "db2"},
55+
query: "SELECT id FROM table1",
56+
expected: []string{
57+
"",
58+
"db1 1",
59+
"db1 2",
60+
"db1 3",
61+
"db2 1",
62+
"db2 2",
63+
"db2 3",
64+
},
65+
},
66+
{
67+
name: "reads from all databases with the all keyword",
68+
targetDBs: []string{"all"},
69+
query: "SELECT id FROM table1",
70+
expected: []string{
71+
"",
72+
"db1 1",
73+
"db1 2",
74+
"db1 3",
75+
"db2 1",
76+
"db2 2",
77+
"db2 3",
78+
"db3 1",
79+
"db3 2",
80+
"db3 3",
81+
},
82+
},
83+
{
84+
name: "reads two fields from all databases",
85+
targetDBs: []string{"all"},
86+
query: "SELECT id, name FROM table1",
87+
expected: []string{
88+
"",
89+
"db1 1 John",
90+
"db1 2 George",
91+
"db1 3 Richard",
92+
"db2 1 Rob",
93+
"db2 2 Ken",
94+
"db2 3 Robert",
95+
"db3 1 Athos",
96+
"db3 2 Porthos",
97+
"db3 3 Aramis",
98+
},
99+
},
100+
}
101+
)
102+
for _, tc := range ts {
103+
t.Run(tc.name, func(t *testing.T) {
104+
var buf = bytes.Buffer{}
105+
_main(testConfig, tc.targetDBs, tc.query, newThreadSafePrintliner(&buf).println)
106+
var actual = strings.Split(buf.String(), "\n")
107+
sort.Strings(actual)
108+
if !reflect.DeepEqual(tc.expected, actual) {
109+
t.Errorf("Expected %v but got %v", tc.expected, actual)
110+
}
111+
})
112+
}
113+
}

test-docker-compose.yml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
version: '3'
2+
services:
3+
test:
4+
build: .
5+
volumes:
6+
- "$PWD:/go/src/sql"
7+
working_dir: /go/src/sql
8+
depends_on:
9+
- test-mysql
10+
test-mysql:
11+
image: mysql:5
12+
volumes:
13+
- ./test_schemas.sql:/docker-entrypoint-initdb.d/test_schemas.sql
14+
environment:
15+
MYSQL_ROOT_PASSWORD:
16+
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"

0 commit comments

Comments
 (0)