Fixed/improved tests startup and exit
[src/xds/xds-server.git] / test / main_test.go
1 /*
2  * Copyright (C) 2017-2018 "IoT.bzh"
3  * Author Clément Bénier <clement.benier@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package xdsservertest
18
19 import (
20         "encoding/json"
21         "fmt"
22         "io"
23         "log"
24         "os"
25         "os/exec"
26         "sync"
27         "testing"
28         "time"
29
30         common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
31         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xdsconfig"
32         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
33         socketio_client "github.com/sebd71/go-socket.io-client"
34 )
35
36 // IOSockClient
37 type IOSockClient struct {
38         URL       string
39         Conn      *socketio_client.Client
40         Options   *socketio_client.Options
41         EmitMutex *sync.Mutex
42         Connected bool
43         //ServerDiscoChan chan Disconnection
44         EscapeKeys []byte
45 }
46
47 //global client
48 var HTTPCli *common.HTTPClient
49 var logDir string
50 var sCli *IOSockClient
51 var prefixURL string
52
53 // Debug function used to print debug logs
54 func Debug(t *testing.T, args ...interface{}) {
55         if os.Getenv("VERBOSE") != "" {
56                 t.Log(args...)
57         }
58 }
59
60 // Debugf function used to print debug logs
61 func Debugf(t *testing.T, format string, args ...interface{}) {
62         if os.Getenv("VERBOSE") != "" {
63                 t.Logf(format, args...)
64         }
65 }
66
67 // Copy copies from src to dst until either EOF
68 func Copy(src, dst string) error {
69         in, err := os.Open(src)
70         if err != nil {
71                 return err
72         }
73         defer in.Close()
74
75         out, err := os.Create(dst)
76         if err != nil {
77                 return err
78         }
79         defer out.Close()
80
81         _, err = io.Copy(out, in)
82         if err != nil {
83                 return err
84         }
85         return out.Close()
86 }
87
88 // init function will run once before execution of test functions begins.
89 func init() {
90         // Check dependency
91         err := checkTestDep()
92         if err != nil {
93                 log.Fatal(err)
94         }
95 }
96
97 // isCommandAvailable verify if a command/utility is available
98 func isCommandAvailable(name string) bool {
99         cmd := exec.Command("/bin/sh", "-c", "command -v "+name)
100         if err := cmd.Run(); err != nil {
101                 return false
102         }
103         return true
104 }
105
106 // checkTestDep checks if all dependency tools are available to be able to run tests
107 func checkTestDep() error {
108         for _, cmd := range dependency_tools {
109                 if !isCommandAvailable(cmd) {
110                         return fmt.Errorf(cmd + " is not installed and is mandatory to run tests")
111                 }
112         }
113         return nil
114 }
115
116 // initEnv
117 func initEnv(launchProcess bool) {
118         if launchProcess {
119                 /*kill xds-server if needed*/
120                 cmd := exec.Command("pkill", "-9", "xds-server")
121                 if err := cmd.Start(); err != nil {
122                         log.Fatal(err)
123                 }
124                 cmd.Wait()
125         }
126         /*set environment variable*/
127         rootTestLog := "/tmp/xds-server-test"
128         if err := os.Setenv(envRootCfgDir, rootTestLog); err != nil {
129                 log.Fatal(err)
130         }
131         sdkDir := rootTestLog + "/sdks/"
132         if err := os.Setenv(envXdtSdk, sdkDir); err != nil {
133                 log.Fatal(err)
134         }
135         if err := os.Setenv(envXdsServerWorkspaceDir, rootTestLog); err != nil {
136                 log.Fatal(err)
137         }
138         if err := os.Setenv(envXdsServerRootCfgDir, rootTestLog); err != nil {
139                 log.Fatal(err)
140         }
141         if err := os.Setenv("XDS_LOG_SILLY", "1"); err != nil {
142                 log.Fatal(err)
143         }
144         /*remove and recreate working directories*/
145         os.RemoveAll(rootTestLog)
146         os.MkdirAll(rootTestLog, 0755)
147         logDir = rootTestLog + "/logs/"
148         os.MkdirAll(logDir, 0755)
149 }
150
151 /*prepare xds-server process*/
152 func launchXdsServer(proc **os.Process) *os.File {
153         logFile := logDir + logFileXdsServer
154         file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0644)
155         if err != nil {
156                 log.Fatal(err)
157         }
158         var argsProcess = []string{"../bin/xds-server",
159                 "-l", "debug",
160                 "-c", testConfigFile,
161         }
162
163         tmpProc, err := os.StartProcess(argsProcess[0], argsProcess, &os.ProcAttr{
164                 Files: []*os.File{os.Stdin, file, file},
165         })
166         if err != nil {
167                 log.Fatal(err)
168         }
169         *proc = tmpProc
170         return file
171 }
172
173 // getHTTPClient
174 func getHTTPClient(lvl int, url string) (*common.HTTPClient, *os.File) {
175         logFile := logDir + logFileClient
176         file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0644)
177         if err != nil {
178                 log.Fatal(err)
179         }
180         conf := common.HTTPClientConfig{
181                 URLPrefix:           "/api/v1",
182                 HeaderClientKeyName: "Xds-Sid",
183                 CsrfDisable:         true,
184                 LogOut:              file,
185                 LogPrefix:           "XDSSERVERTEST: ",
186                 LogLevel:            lvl,
187         }
188
189         // Try to connect during 30 seconds
190         var HTTPcli *common.HTTPClient
191         retry := 30
192         for retry > 0 {
193                 if HTTPcli, err = common.HTTPNewClient(url, conf); err == nil {
194                         break
195                 }
196                 if retry < 25 {
197                         log.Printf("Establishing connection to XDS Server (retry %d/10)", retry)
198                 }
199                 time.Sleep(time.Second)
200                 retry--
201         }
202         if retry == 0 {
203                 log.Fatalf("Cannot establish connection with xds-server:\n %v", err)
204         }
205
206         log.Printf("HTTP session ID : %v", HTTPcli.GetClientID())
207
208         // Basic check
209         var ver xsapiv1.Version
210         err = HTTPcli.Get("/version", &ver)
211         if err != nil {
212                 log.Fatal(err)
213         }
214         return HTTPcli, file
215 }
216
217 func NewIoSocketClient(url, clientID string) (*IOSockClient, error) {
218         var err error
219
220         sCli := &IOSockClient{
221                 URL:       url,
222                 EmitMutex: &sync.Mutex{},
223                 Options: &socketio_client.Options{
224                         Transport: "websocket",
225                         Header:    make(map[string][]string),
226                 },
227         }
228         sCli.Options.Header["XDS-SID"] = []string{clientID}
229
230         sCli.Conn, err = socketio_client.NewClient(url, sCli.Options)
231         if err != nil {
232                 return nil, fmt.Errorf("IO.socket connection error: " + err.Error())
233         }
234
235         sCli.Conn.On("connection", func() {
236                 sCli.Connected = true
237         })
238
239         sCli.Conn.On("disconnection", func(err error) {
240                 if err != nil {
241                         log.Printf("WS disconnection event with err: %v\n", err)
242                 }
243                 sCli.Connected = false
244         })
245
246         return sCli, nil
247 }
248
249 // TestMain is the main entry point of testsuite
250 func TestMain(m *testing.M) {
251
252         /* useful for debugging, preventing from launching xds-server
253          * it can be launch separately */
254         launchProcess := true
255         log.Printf("TestMain: launchProcess is %v, so launching xds-server", launchProcess)
256         initEnv(launchProcess)
257
258         var proc *os.Process
259         var fileXdsServer *os.File
260         if launchProcess {
261                 fileXdsServer = launchXdsServer(&proc)
262                 go func(p *os.Process) {
263                         log.Print("xds-server is launching")
264                         if status, err := p.Wait(); err != nil {
265                                 log.Fatalf("status=%v\n err=%v\n", status, err)
266                         }
267                         os.Exit(0)
268                 }(proc)
269                 defer fileXdsServer.Close()
270         }
271
272         // Extract URL from _test-config.json file
273         fd, _ := os.Open(testConfigFile)
274         defer fd.Close()
275         fCfg := xdsconfig.FileConfig{}
276         if err := json.NewDecoder(fd).Decode(&fCfg); err != nil {
277                 log.Fatalf("Cannot decode test config file %v : %v", testConfigFile, err)
278         }
279
280         if fCfg.HTTPPort == "" {
281                 log.Fatalf("Cannot retrieve httpPort in test config file %v", testConfigFile)
282         }
283         prefixURL := "http://localhost:" + fCfg.HTTPPort
284
285         // Start HTTP client to send test commands
286         lvl := common.HTTPLogLevelDebug
287         var fileHTTPClient *os.File
288         HTTPCli, fileHTTPClient = getHTTPClient(lvl, prefixURL)
289         defer fileHTTPClient.Close()
290         var err error
291         sCli, err = NewIoSocketClient(prefixURL, HTTPCli.GetClientID())
292         if err != nil {
293                 log.Fatal(err)
294         }
295         if HTTPCli == nil {
296                 log.Fatal("HTTPCli is nil")
297         }
298
299         // Run tests
300         res := m.Run()
301         defer os.Exit(res)
302
303         // Gracefully stop xds-server process
304         if err := proc.Signal(os.Interrupt); err != nil {
305                 log.Fatalf("xds-server proc interrupt error : %v", err)
306         }
307
308         // Should not be executed, normal exit must be above go routine after on p.Wait returns
309         time.Sleep(10 * time.Second)
310         log.Printf("xds-server not gravefully stop, kill it\n")
311         proc.Kill()
312 }