Skip to content

Commit af16688

Browse files
committed
Added SQLREPLTestSuite
Signed-off-by: Lou Marvin Caraig <[email protected]>
1 parent 78c9106 commit af16688

File tree

2 files changed

+215
-67
lines changed

2 files changed

+215
-67
lines changed

cmd/srcd/cmd/sql_test.go

+202-67
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,225 @@ package cmd
55
import (
66
"bytes"
77
"context"
8+
"fmt"
89
"io"
910
"io/ioutil"
1011
"log"
1112
"os"
1213
"os/exec"
1314
"path"
1415
"path/filepath"
16+
"regexp"
1517
"strings"
1618
"testing"
1719
"time"
1820

21+
"github.com/kr/pty"
1922
cmdtest "github.com/src-d/engine/cmd/test-utils"
20-
23+
"github.com/src-d/engine/components"
24+
"github.com/src-d/engine/docker"
2125
"github.com/stretchr/testify/assert"
2226
"github.com/stretchr/testify/require"
2327
"github.com/stretchr/testify/suite"
2428
)
2529

30+
var showTablesOutput = sqlOutput(`+--------------+
31+
| Table |
32+
+--------------+
33+
| blobs |
34+
| commit_blobs |
35+
| commit_files |
36+
| commit_trees |
37+
| commits |
38+
| files |
39+
| ref_commits |
40+
| refs |
41+
| remotes |
42+
| repositories |
43+
| tree_entries |
44+
+--------------+
45+
`)
46+
47+
var showRepoTableDescOutput = sqlOutput(`+---------------+------+
48+
| name | type |
49+
+---------------+------+
50+
| repository_id | TEXT |
51+
+---------------+------+
52+
`)
53+
54+
type SQLREPLTestSuite struct {
55+
cmdtest.IntegrationSuite
56+
testDir string
57+
}
58+
59+
func TestSQLREPLTestSuite(t *testing.T) {
60+
s := SQLREPLTestSuite{}
61+
suite.Run(t, &s)
62+
}
63+
64+
func (s *SQLREPLTestSuite) SetupTest() {
65+
var err error
66+
s.testDir, err = ioutil.TempDir("", "sql-repl-test")
67+
if err != nil {
68+
log.Fatal(err)
69+
}
70+
}
71+
72+
func (s *SQLREPLTestSuite) TearDownTest() {
73+
s.RunCommand(context.Background(), "prune")
74+
os.RemoveAll(s.testDir)
75+
}
76+
77+
func (s *SQLREPLTestSuite) TestREPL() {
78+
// When it is not in a terminal, the command reads stdin and exits.
79+
// You can see it running:
80+
// $ echo "show tables;" | ./srcd sql
81+
// So this test does not really interacts with the REPL prompt, but we still
82+
// test that the code that processes each read line is working as expected.
83+
84+
require := s.Require()
85+
86+
var out, in bytes.Buffer
87+
88+
command := s.CommandContext(context.TODO(), "sql")
89+
command.Stdout = &out
90+
command.Stderr = &out
91+
command.Stdin = &in
92+
93+
io.WriteString(&in, "show tables;\n")
94+
io.WriteString(&in, "describe table repositories;\n")
95+
96+
err := command.Start()
97+
require.NoError(err)
98+
99+
err = command.Wait()
100+
require.NoError(err)
101+
102+
require.Contains(out.String(), showTablesOutput)
103+
require.Contains(out.String(), showRepoTableDescOutput)
104+
}
105+
106+
func (s *SQLREPLTestSuite) TestInteractiveREPL() {
107+
require := s.Require()
108+
109+
command, stdin, ch := s.runInteractiveRepl()
110+
111+
res := s.runInteractiveQuery(stdin, "show tables;\n", ch)
112+
require.Contains(res, showTablesOutput)
113+
114+
res = s.runInteractiveQuery(stdin, "describe table repositories;\n", ch)
115+
require.Contains(res, showRepoTableDescOutput)
116+
117+
require.NoError(s.exitInteractiveAndWait(10*time.Second, stdin, ch))
118+
require.NoError(s.waitMysqlCliContainerStopped(10 * time.Second))
119+
120+
command.Wait()
121+
}
122+
123+
func (s *SQLREPLTestSuite) runInteractiveRepl() (*exec.Cmd, *os.File, <-chan string) {
124+
s.T().Helper()
125+
126+
command := s.CommandContext(context.TODO(), "sql")
127+
128+
ch := make(chan string)
129+
cr := cmdtest.NewChannelWriter(ch)
130+
131+
command.Stdout = cr
132+
command.Stderr = cr
133+
134+
f, err := pty.Start(command)
135+
if err != nil {
136+
panic(err)
137+
}
138+
139+
mysqlCliReady := make(chan struct{})
140+
141+
go func() {
142+
for s := range ch {
143+
lines := strings.Split(s, "\n")
144+
for _, l := range lines {
145+
if strings.HasPrefix(l, "mysql>") {
146+
mysqlCliReady <- struct{}{}
147+
return
148+
}
149+
}
150+
}
151+
}()
152+
153+
<-mysqlCliReady
154+
155+
return command, f, ch
156+
}
157+
158+
func (s *SQLREPLTestSuite) runInteractiveQuery(stdin *os.File, query string, ch <-chan string) string {
159+
io.WriteString(stdin, query)
160+
161+
var res strings.Builder
162+
for c := range ch {
163+
fmt.Printf("Message: %s\n", c)
164+
res.WriteString(c)
165+
if s.containsSQLOutput(res.String()) {
166+
break
167+
}
168+
}
169+
170+
return res.String()
171+
}
172+
173+
func (s *SQLREPLTestSuite) exitInteractiveAndWait(timeout time.Duration, stdin *os.File, ch <-chan string) error {
174+
io.WriteString(stdin, "exit;\n")
175+
176+
done := make(chan struct{})
177+
go func() {
178+
for c := range ch {
179+
if strings.Contains(c, "Bye") {
180+
done <- struct{}{}
181+
// don't return in order to consume all output and let the process exit
182+
}
183+
}
184+
}()
185+
186+
select {
187+
case <-done:
188+
stdin.Close()
189+
return nil
190+
case <-time.After(timeout):
191+
return fmt.Errorf("timeout of %v elapsed while waiting to exit", timeout)
192+
}
193+
}
194+
195+
func (s *SQLREPLTestSuite) waitMysqlCliContainerStopped(timeout time.Duration) error {
196+
done := make(chan struct{})
197+
go func() {
198+
for {
199+
running, err := docker.IsRunning(components.MysqlCli.Name, "")
200+
if !running {
201+
done <- struct{}{}
202+
return
203+
}
204+
205+
if err != nil {
206+
panic(err)
207+
}
208+
209+
time.Sleep(timeout / 5)
210+
}
211+
}()
212+
213+
select {
214+
case <-done:
215+
return nil
216+
case <-time.After(timeout):
217+
return fmt.Errorf("timeout of %v elapsed while waiting to stop container", timeout)
218+
}
219+
}
220+
221+
func (s *SQLREPLTestSuite) containsSQLOutput(out string) bool {
222+
sep := regexp.MustCompile(`\+[-]+\+`)
223+
matches := sep.FindAllStringIndex(out, -1)
224+
return len(matches) == 3
225+
}
226+
26227
type SQLTestSuite struct {
27228
cmdtest.IntegrationSuite
28229
testDir string
@@ -46,23 +247,6 @@ func (s *SQLTestSuite) TearDownTest() {
46247
os.RemoveAll(s.testDir)
47248
}
48249

49-
var showTablesOutput = sqlOutput(`+--------------+
50-
| Table |
51-
+--------------+
52-
| blobs |
53-
| commit_blobs |
54-
| commit_files |
55-
| commit_trees |
56-
| commits |
57-
| files |
58-
| ref_commits |
59-
| refs |
60-
| remotes |
61-
| repositories |
62-
| tree_entries |
63-
+--------------+
64-
`)
65-
66250
func (s *SQLTestSuite) TestInit() {
67251
require := s.Require()
68252

@@ -165,55 +349,6 @@ func (s *SQLTestSuite) TestWrongQuery() {
165349
}
166350
}
167351

168-
func (s *SQLTestSuite) TestREPL() {
169-
// When it is not in a terminal, the command reads stdin and exits.
170-
// You can see it running:
171-
// $ echo "show tables;" | ./srcd sql
172-
// So this test does not really interacts with the REPL prompt, but we still
173-
// test that the code that processes each read line is working as expected.
174-
175-
require := s.Require()
176-
177-
var out, in bytes.Buffer
178-
179-
command := s.CommandContext(context.TODO(), "sql")
180-
command.Stdout = &out
181-
command.Stderr = &out
182-
command.Stdin = &in
183-
184-
io.WriteString(&in, "show tables;\n")
185-
io.WriteString(&in, "describe table repositories;\n")
186-
187-
err := command.Start()
188-
require.NoError(err)
189-
190-
err = command.Wait()
191-
require.NoError(err)
192-
193-
expected := sqlOutput(`+--------------+
194-
| Table |
195-
+--------------+
196-
| blobs |
197-
| commit_blobs |
198-
| commit_files |
199-
| commit_trees |
200-
| commits |
201-
| files |
202-
| ref_commits |
203-
| refs |
204-
| remotes |
205-
| repositories |
206-
| tree_entries |
207-
+--------------+
208-
+---------------+------+
209-
| name | type |
210-
+---------------+------+
211-
| repository_id | TEXT |
212-
+---------------+------+`)
213-
214-
require.Contains(out.String(), expected)
215-
}
216-
217352
func (s *SQLTestSuite) TestIndexesWorkdirChange() {
218353
require := s.Require()
219354

cmd/test-utils/common.go

+13
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,16 @@ func TraceLogMessages(fn func(), memLog *bytes.Buffer) []LogMessage {
164164

165165
return result
166166
}
167+
168+
type ChannelWriter struct {
169+
ch chan string
170+
}
171+
172+
func NewChannelWriter(ch chan string) *ChannelWriter {
173+
return &ChannelWriter{ch: ch}
174+
}
175+
176+
func (cr *ChannelWriter) Write(b []byte) (int, error) {
177+
cr.ch <- string(b)
178+
return len(b), nil
179+
}

0 commit comments

Comments
 (0)