• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tarantool / go-tarantool / 12350021459

16 Dec 2024 09:06AM UTC coverage: 73.845%. First build
12350021459

push

github

oleg-jukovec
Release v2.2.0

6042 of 8182 relevant lines covered (73.85%)

4998.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

1.31
/test_helpers/main.go
1
// Helpers for managing Tarantool process for testing purposes.
2
//
3
// Package introduces go helpers for starting a tarantool process and
4
// validating Tarantool version. Helpers are based on os/exec calls.
5
// Retries to connect test tarantool instance handled explicitly,
6
// see tarantool/go-tarantool/#136.
7
//
8
// Tarantool's instance Lua scripts use environment variables to configure
9
// box.cfg. Listen port is set in the end of script so it is possible to
10
// connect only if every other thing was set up already.
11
package test_helpers
12

13
import (
14
        "context"
15
        "errors"
16
        "fmt"
17
        "io"
18
        "log"
19
        "os"
20
        "os/exec"
21
        "path/filepath"
22
        "regexp"
23
        "strconv"
24
        "time"
25

26
        "github.com/tarantool/go-tarantool/v2"
27
)
28

29
type StartOpts struct {
30
        // Auth is an authentication method for a Tarantool instance.
31
        Auth tarantool.Auth
32

33
        // InitScript is a Lua script for tarantool to run on start.
34
        InitScript string
35

36
        // Listen is box.cfg listen parameter for tarantool.
37
        // Use this address to connect to tarantool after configuration.
38
        // https://www.tarantool.io/en/doc/latest/reference/configuration/#cfg-basic-listen
39
        Listen string
40

41
        // WorkDir is box.cfg work_dir parameter for a Tarantool instance:
42
        // a folder to store data files. If not specified, helpers create a
43
        // new temporary directory.
44
        // Folder must be unique for each Tarantool process used simultaneously.
45
        // https://www.tarantool.io/en/doc/latest/reference/configuration/#confval-work_dir
46
        WorkDir string
47

48
        // SslCertsDir is a path to a directory with SSL certificates. It will be
49
        // copied to the working directory.
50
        SslCertsDir string
51

52
        // WaitStart is a time to wait before starting to ping tarantool.
53
        WaitStart time.Duration
54

55
        // ConnectRetry is a count of retry attempts to ping tarantool. If the
56
        // value < 0 then there will be no ping tarantool at all.
57
        ConnectRetry int
58

59
        // RetryTimeout is a time between tarantool ping retries.
60
        RetryTimeout time.Duration
61

62
        // MemtxUseMvccEngine is flag to enable transactional
63
        // manager if set to true.
64
        MemtxUseMvccEngine bool
65

66
        // Dialer to check that connection established.
67
        Dialer tarantool.Dialer
68
}
69

70
// TarantoolInstance is a data for instance graceful shutdown and cleanup.
71
type TarantoolInstance struct {
72
        // Cmd is a Tarantool command. Used to kill Tarantool process.
73
        Cmd *exec.Cmd
74

75
        // Options for restarting a tarantool instance.
76
        Opts StartOpts
77

78
        // Dialer to check that connection established.
79
        Dialer tarantool.Dialer
80
}
81

82
func isReady(dialer tarantool.Dialer, opts *tarantool.Opts) error {
×
83
        var err error
×
84
        var conn *tarantool.Connection
×
85

×
86
        ctx, cancel := GetConnectContext()
×
87
        defer cancel()
×
88
        conn, err = tarantool.Connect(ctx, dialer, *opts)
×
89
        if err != nil {
×
90
                return err
×
91
        }
×
92
        if conn == nil {
×
93
                return errors.New("connection is nil after connect")
×
94
        }
×
95
        defer conn.Close()
×
96

×
97
        _, err = conn.Do(tarantool.NewPingRequest()).Get()
×
98
        if err != nil {
×
99
                return err
×
100
        }
×
101

102
        return nil
×
103
}
104

105
var (
106
        // Used to extract Tarantool version (major.minor.patch).
107
        tarantoolVersionRegexp *regexp.Regexp
108
)
109

110
func init() {
1✔
111
        tarantoolVersionRegexp = regexp.MustCompile(`Tarantool (?:Enterprise )?(\d+)\.(\d+)\.(\d+).*`)
1✔
112
}
1✔
113

114
// atoiUint64 parses string to uint64.
115
func atoiUint64(str string) (uint64, error) {
×
116
        res, err := strconv.ParseUint(str, 10, 64)
×
117
        if err != nil {
×
118
                return 0, fmt.Errorf("cast to number error (%s)", err)
×
119
        }
×
120
        return res, nil
×
121
}
122

123
func getTarantoolExec() string {
×
124
        if tar_bin := os.Getenv("TARANTOOL_BIN"); tar_bin != "" {
×
125
                return tar_bin
×
126
        }
×
127
        return "tarantool"
×
128
}
129

130
// IsTarantoolVersionLess checks if tarantool version is less
131
// than passed <major.minor.patch>. Returns error if failed
132
// to extract version.
133
func IsTarantoolVersionLess(majorMin uint64, minorMin uint64, patchMin uint64) (bool, error) {
×
134
        var major, minor, patch uint64
×
135

×
136
        out, err := exec.Command(getTarantoolExec(), "--version").Output()
×
137

×
138
        if err != nil {
×
139
                return true, err
×
140
        }
×
141

142
        parsed := tarantoolVersionRegexp.FindStringSubmatch(string(out))
×
143

×
144
        if parsed == nil {
×
145
                return true, fmt.Errorf("failed to parse output %q", out)
×
146
        }
×
147

148
        if major, err = atoiUint64(parsed[1]); err != nil {
×
149
                return true, fmt.Errorf("failed to parse major from output %q: %w", out, err)
×
150
        }
×
151

152
        if minor, err = atoiUint64(parsed[2]); err != nil {
×
153
                return true, fmt.Errorf("failed to parse minor from output %q: %w", out, err)
×
154
        }
×
155

156
        if patch, err = atoiUint64(parsed[3]); err != nil {
×
157
                return true, fmt.Errorf("failed to parse patch from output %q: %w", out, err)
×
158
        }
×
159

160
        if major != majorMin {
×
161
                return major < majorMin, nil
×
162
        } else if minor != minorMin {
×
163
                return minor < minorMin, nil
×
164
        } else {
×
165
                return patch < patchMin, nil
×
166
        }
×
167
}
168

169
// RestartTarantool restarts a tarantool instance for tests
170
// with specifies parameters (refer to StartOpts)
171
// which were specified in inst parameter.
172
// inst is a tarantool instance that was started by
173
// StartTarantool. Rewrites inst.Cmd.Process to stop
174
// instance with StopTarantool.
175
// Process must be stopped with StopTarantool.
176
func RestartTarantool(inst *TarantoolInstance) error {
×
177
        startedInst, err := StartTarantool(inst.Opts)
×
178
        inst.Cmd.Process = startedInst.Cmd.Process
×
179
        return err
×
180
}
×
181

182
// StartTarantool starts a tarantool instance for tests
183
// with specifies parameters (refer to StartOpts).
184
// Process must be stopped with StopTarantool.
185
func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
×
186
        // Prepare tarantool command.
×
187
        var inst TarantoolInstance
×
188
        var dir string
×
189
        var err error
×
190

×
191
        inst.Dialer = startOpts.Dialer
×
192

×
193
        if startOpts.WorkDir == "" {
×
194
                dir, err = os.MkdirTemp("", "work_dir")
×
195
                if err != nil {
×
196
                        return inst, err
×
197
                }
×
198
                startOpts.WorkDir = dir
×
199
        } else {
×
200
                // Clean up existing work_dir.
×
201
                err = os.RemoveAll(startOpts.WorkDir)
×
202
                if err != nil {
×
203
                        return inst, err
×
204
                }
×
205

206
                // Create work_dir.
207
                err = os.Mkdir(startOpts.WorkDir, 0755)
×
208
                if err != nil {
×
209
                        return inst, err
×
210
                }
×
211
        }
212

213
        inst.Cmd = exec.Command(getTarantoolExec(), startOpts.InitScript)
×
214

×
215
        inst.Cmd.Env = append(
×
216
                os.Environ(),
×
217
                fmt.Sprintf("TEST_TNT_WORK_DIR=%s", startOpts.WorkDir),
×
218
                fmt.Sprintf("TEST_TNT_LISTEN=%s", startOpts.Listen),
×
219
                fmt.Sprintf("TEST_TNT_MEMTX_USE_MVCC_ENGINE=%t", startOpts.MemtxUseMvccEngine),
×
220
                fmt.Sprintf("TEST_TNT_AUTH_TYPE=%s", startOpts.Auth),
×
221
        )
×
222

×
223
        // Copy SSL certificates.
×
224
        if startOpts.SslCertsDir != "" {
×
225
                err = copySslCerts(startOpts.WorkDir, startOpts.SslCertsDir)
×
226
                if err != nil {
×
227
                        return inst, err
×
228
                }
×
229
        }
230

231
        // Options for restarting tarantool instance.
232
        inst.Opts = startOpts
×
233

×
234
        // Start tarantool.
×
235
        err = inst.Cmd.Start()
×
236
        if err != nil {
×
237
                return inst, err
×
238
        }
×
239

240
        // Try to connect and ping tarantool.
241
        // Using reconnect opts do not help on Connect,
242
        // see https://github.com/tarantool/go-tarantool/issues/136
243
        time.Sleep(startOpts.WaitStart)
×
244

×
245
        opts := tarantool.Opts{
×
246
                Timeout:    500 * time.Millisecond,
×
247
                SkipSchema: true,
×
248
        }
×
249

×
250
        var i int
×
251
        for i = 0; i <= startOpts.ConnectRetry; i++ {
×
252
                err = isReady(inst.Dialer, &opts)
×
253

×
254
                // Both connect and ping is ok.
×
255
                if err == nil {
×
256
                        break
×
257
                }
258

259
                if i != startOpts.ConnectRetry {
×
260
                        time.Sleep(startOpts.RetryTimeout)
×
261
                }
×
262
        }
263

264
        return inst, err
×
265
}
266

267
// StopTarantool stops a tarantool instance started
268
// with StartTarantool. Waits until any resources
269
// associated with the process is released. If something went wrong, fails.
270
func StopTarantool(inst TarantoolInstance) {
×
271
        if inst.Cmd != nil && inst.Cmd.Process != nil {
×
272
                if err := inst.Cmd.Process.Kill(); err != nil {
×
273
                        log.Fatalf("Failed to kill tarantool (pid %d), got %s", inst.Cmd.Process.Pid, err)
×
274
                }
×
275

276
                // Wait releases any resources associated with the Process.
277
                if _, err := inst.Cmd.Process.Wait(); err != nil {
×
278
                        log.Fatalf("Failed to wait for Tarantool process to exit, got %s", err)
×
279
                }
×
280

281
                inst.Cmd.Process = nil
×
282
        }
283
}
284

285
// StopTarantoolWithCleanup stops a tarantool instance started
286
// with StartTarantool. Waits until any resources
287
// associated with the process is released.
288
// Cleans work directory after stop. If something went wrong, fails.
289
func StopTarantoolWithCleanup(inst TarantoolInstance) {
×
290
        StopTarantool(inst)
×
291

×
292
        if inst.Opts.WorkDir != "" {
×
293
                if err := os.RemoveAll(inst.Opts.WorkDir); err != nil {
×
294
                        log.Fatalf("Failed to clean work directory, got %s", err)
×
295
                }
×
296
        }
297
}
298

299
func copySslCerts(dst string, sslCertsDir string) (err error) {
×
300
        dstCertPath := filepath.Join(dst, sslCertsDir)
×
301
        if err = os.Mkdir(dstCertPath, 0755); err != nil {
×
302
                return
×
303
        }
×
304
        if err = copyDirectoryFiles(sslCertsDir, dstCertPath); err != nil {
×
305
                return
×
306
        }
×
307
        return
×
308
}
309

310
func copyDirectoryFiles(scrDir, dest string) error {
×
311
        entries, err := os.ReadDir(scrDir)
×
312
        if err != nil {
×
313
                return err
×
314
        }
×
315
        for _, entry := range entries {
×
316
                if entry.IsDir() {
×
317
                        continue
×
318
                }
319
                sourcePath := filepath.Join(scrDir, entry.Name())
×
320
                destPath := filepath.Join(dest, entry.Name())
×
321
                _, err := os.Stat(sourcePath)
×
322
                if err != nil {
×
323
                        return err
×
324
                }
×
325

326
                if err := copyFile(sourcePath, destPath); err != nil {
×
327
                        return err
×
328
                }
×
329

330
                info, err := entry.Info()
×
331
                if err != nil {
×
332
                        return err
×
333
                }
×
334

335
                if err := os.Chmod(destPath, info.Mode()); err != nil {
×
336
                        return err
×
337
                }
×
338
        }
339
        return nil
×
340
}
341

342
func copyFile(srcFile, dstFile string) error {
×
343
        out, err := os.Create(dstFile)
×
344
        if err != nil {
×
345
                return err
×
346
        }
×
347

348
        defer out.Close()
×
349

×
350
        in, err := os.Open(srcFile)
×
351
        if err != nil {
×
352
                return err
×
353
        }
×
354
        defer in.Close()
×
355

×
356
        _, err = io.Copy(out, in)
×
357
        if err != nil {
×
358
                return err
×
359
        }
×
360

361
        return nil
×
362
}
363

364
// msgpack.v5 decodes different uint types depending on value. The
365
// function helps to unify a result.
366
func ConvertUint64(v interface{}) (result uint64, err error) {
×
367
        switch v := v.(type) {
×
368
        case uint:
×
369
                result = uint64(v)
×
370
        case uint8:
×
371
                result = uint64(v)
×
372
        case uint16:
×
373
                result = uint64(v)
×
374
        case uint32:
×
375
                result = uint64(v)
×
376
        case uint64:
×
377
                result = v
×
378
        case int:
×
379
                result = uint64(v)
×
380
        case int8:
×
381
                result = uint64(v)
×
382
        case int16:
×
383
                result = uint64(v)
×
384
        case int32:
×
385
                result = uint64(v)
×
386
        case int64:
×
387
                result = uint64(v)
×
388
        default:
×
389
                err = fmt.Errorf("non-number value %T", v)
×
390
        }
391
        return
×
392
}
393

394
func GetConnectContext() (context.Context, context.CancelFunc) {
×
395
        return context.WithTimeout(context.Background(), 500*time.Millisecond)
×
396
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc