3
3
package cmdtests_test
4
4
5
5
import (
6
+ "fmt"
7
+ "io"
6
8
"os"
7
9
"os/exec"
8
10
"path"
9
11
"path/filepath"
12
+ "regexp"
13
+ "runtime"
10
14
"strings"
11
15
"testing"
12
16
"time"
13
17
18
+ "github.com/kr/pty"
14
19
"github.com/src-d/engine/cmdtests"
20
+ "github.com/src-d/engine/components"
21
+ "github.com/src-d/engine/docker"
15
22
"github.com/stretchr/testify/assert"
16
23
"github.com/stretchr/testify/require"
17
24
"github.com/stretchr/testify/suite"
18
25
"gotest.tools/icmd"
19
26
)
20
27
21
- type SQLTestSuite struct {
22
- cmdtests.IntegrationTmpDirSuite
23
- }
24
-
25
- func TestSQLTestSuite (t * testing.T ) {
26
- s := SQLTestSuite {}
27
- suite .Run (t , & s )
28
- }
29
-
30
28
var showTablesOutput = sqlOutput (`+--------------+
31
29
| Table |
32
30
+--------------+
@@ -44,6 +42,181 @@ var showTablesOutput = sqlOutput(`+--------------+
44
42
+--------------+
45
43
` )
46
44
45
+ var showRepoTableDescOutput = sqlOutput (`+---------------+------+
46
+ | name | type |
47
+ +---------------+------+
48
+ | repository_id | TEXT |
49
+ +---------------+------+
50
+ ` )
51
+
52
+ type SQLREPLTestSuite struct {
53
+ cmdtests.IntegrationTmpDirSuite
54
+ testDir string
55
+ }
56
+
57
+ func TestSQLREPLTestSuite (t * testing.T ) {
58
+ s := SQLREPLTestSuite {}
59
+ suite .Run (t , & s )
60
+ }
61
+
62
+ func (s * SQLREPLTestSuite ) TestREPL () {
63
+ // When it is not in a terminal, the command reads stdin and exits.
64
+ // You can see it running:
65
+ // $ echo "show tables;" | ./srcd sql
66
+ // So this test does not really interacts with the REPL prompt, but we still
67
+ // test that the code that processes each read line is working as expected.
68
+
69
+ require := s .Require ()
70
+
71
+ input := "show tables;\n " + "describe table repositories;\n "
72
+ r := s .RunCmd ("sql" , nil , icmd .WithStdin (strings .NewReader (input )))
73
+ require .NoError (r .Error , r .Combined ())
74
+ require .Contains (r .Combined (), showTablesOutput )
75
+ require .Contains (r .Combined (), showRepoTableDescOutput )
76
+ }
77
+
78
+ func (s * SQLREPLTestSuite ) TestInteractiveREPL () {
79
+ if runtime .GOOS == "windows" {
80
+ s .T ().Skip ("Testing interactive REPL on Windows is not supported" )
81
+ }
82
+
83
+ require := s .Require ()
84
+
85
+ command , in , out , err := s .runInteractiveRepl ()
86
+ require .NoError (err )
87
+
88
+ res := s .runInteractiveQuery (in , "show tables;\n " , out )
89
+ require .Contains (res , showTablesOutput )
90
+
91
+ res = s .runInteractiveQuery (in , "describe table repositories;\n " , out )
92
+ require .Contains (res , showRepoTableDescOutput )
93
+
94
+ require .NoError (s .exitInteractiveAndWait (10 * time .Second , in , out ))
95
+ require .NoError (s .waitMysqlCliContainerStopped (10 , 1 * time .Second ))
96
+
97
+ command .Wait ()
98
+ }
99
+
100
+ func (s * SQLREPLTestSuite ) runInteractiveRepl () (* exec.Cmd , io.Writer , <- chan string , error ) {
101
+ s .T ().Helper ()
102
+
103
+ // cannot use `icmd` here, please see: https://github.com/gotestyourself/gotest.tools/issues/151
104
+ command := exec .Command (s .Bin (), "sql" )
105
+
106
+ ch := make (chan string )
107
+ cr := cmdtests .NewChannelWriter (ch )
108
+
109
+ command .Stdout = cr
110
+ command .Stderr = cr
111
+
112
+ in , err := pty .Start (command )
113
+ if err != nil {
114
+ panic (err )
115
+ }
116
+
117
+ linifier := cmdtests .NewStreamLinifier (1 * time .Second )
118
+ out := linifier .Linify (ch )
119
+ for s := range out {
120
+ if strings .HasPrefix (s , "mysql>" ) {
121
+ return command , in , out , nil
122
+ }
123
+ }
124
+
125
+ return nil , nil , nil , fmt .Errorf ("Mysql cli prompt never started" )
126
+ }
127
+
128
+ func (s * SQLREPLTestSuite ) runInteractiveQuery (in io.Writer , query string , out <- chan string ) string {
129
+ io .WriteString (in , query )
130
+
131
+ var res strings.Builder
132
+ for c := range out {
133
+ if strings .HasPrefix (c , "Empty set" ) {
134
+ return ""
135
+ }
136
+
137
+ res .WriteString (c + "\r \n " )
138
+ if s .containsSQLOutput (res .String ()) {
139
+ break
140
+ }
141
+ }
142
+
143
+ return res .String ()
144
+ }
145
+
146
+ func (s * SQLREPLTestSuite ) exitInteractiveAndWait (timeout time.Duration , in io.Writer , out <- chan string ) error {
147
+ io .WriteString (in , "exit;\n " )
148
+
149
+ done := make (chan struct {})
150
+ go func () {
151
+ for c := range out {
152
+ if strings .Contains (c , "Bye" ) {
153
+ done <- struct {}{}
154
+ // don't return in order to consume all output and let the process exit
155
+ }
156
+ }
157
+ }()
158
+
159
+ select {
160
+ case <- done :
161
+ return nil
162
+ case <- time .After (timeout ):
163
+ return fmt .Errorf ("timeout of %v elapsed while waiting to exit" , timeout )
164
+ }
165
+ }
166
+
167
+ func (s * SQLREPLTestSuite ) waitMysqlCliContainerStopped (retries int , retryTimeout time.Duration ) error {
168
+ for i := 0 ; i < retries ; i ++ {
169
+ running , err := docker .IsRunning (components .MysqlCli .Name , "" )
170
+ if ! running {
171
+ return nil
172
+ }
173
+
174
+ if err != nil {
175
+ return err
176
+ }
177
+
178
+ time .Sleep (retryTimeout )
179
+ }
180
+
181
+ return fmt .Errorf ("maximum number of retries (%d) reached while waiting to stop container" , retries )
182
+ }
183
+
184
+ // containsSQLOutput returns `true` if the given string is a SQL output table.
185
+ // To detect whether the `out` is a SQL output table, this checks that there
186
+ // are exactly 3 separators matching this regex ``\+-+\+`.
187
+ // In fact an example of SQL output is the following:
188
+ //
189
+ // +--------------+ <-- first separator
190
+ // | Table |
191
+ // +--------------+ <-- second separator
192
+ // | blobs |
193
+ // | commit_blobs |
194
+ // | commit_files |
195
+ // | commit_trees |
196
+ // | commits |
197
+ // | files |
198
+ // | ref_commits |
199
+ // | refs |
200
+ // | remotes |
201
+ // | repositories |
202
+ // | tree_entries |
203
+ // +--------------+ <-- third separator
204
+ //
205
+ func (s * SQLREPLTestSuite ) containsSQLOutput (out string ) bool {
206
+ sep := regexp .MustCompile (`\+-+\+` )
207
+ matches := sep .FindAllStringIndex (out , - 1 )
208
+ return len (matches ) == 3
209
+ }
210
+
211
+ type SQLTestSuite struct {
212
+ cmdtests.IntegrationTmpDirSuite
213
+ }
214
+
215
+ func TestSQLTestSuite (t * testing.T ) {
216
+ s := SQLTestSuite {}
217
+ suite .Run (t , & s )
218
+ }
219
+
47
220
func (s * SQLTestSuite ) TestInit () {
48
221
require := s .Require ()
49
222
@@ -146,43 +319,6 @@ func (s *SQLTestSuite) TestWrongQuery() {
146
319
}
147
320
}
148
321
149
- func (s * SQLTestSuite ) TestREPL () {
150
- // When it is not in a terminal, the command reads stdin and exits.
151
- // You can see it running:
152
- // $ echo "show tables;" | ./srcd sql
153
- // So this test does not really interacts with the REPL prompt, but we still
154
- // test that the code that processes each read line is working as expected.
155
-
156
- require := s .Require ()
157
-
158
- input := "show tables;\n " + "describe table repositories;\n "
159
- r := s .RunCmd ("sql" , nil , icmd .WithStdin (strings .NewReader (input )))
160
- require .NoError (r .Error , r .Combined ())
161
-
162
- expected := sqlOutput (`+--------------+
163
- | Table |
164
- +--------------+
165
- | blobs |
166
- | commit_blobs |
167
- | commit_files |
168
- | commit_trees |
169
- | commits |
170
- | files |
171
- | ref_commits |
172
- | refs |
173
- | remotes |
174
- | repositories |
175
- | tree_entries |
176
- +--------------+
177
- +---------------+------+
178
- | name | type |
179
- +---------------+------+
180
- | repository_id | TEXT |
181
- +---------------+------+` )
182
-
183
- require .Contains (r .Stdout (), expected )
184
- }
185
-
186
322
func (s * SQLTestSuite ) TestIndexesWorkdirChange () {
187
323
require := s .Require ()
188
324
0 commit comments