Skip to content

Commit 5d605bc

Browse files
committed
tests: add helpers for TcS
Simple helpers to make easy create tests required Taranatool centralized configuration storage.
1 parent 5368646 commit 5d605bc

File tree

6 files changed

+289
-4
lines changed

6 files changed

+289
-4
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.20
44

55
require (
66
github.com/google/uuid v1.3.0
7+
github.com/mitchellh/mapstructure v1.5.0
78
github.com/shopspring/decimal v1.3.1
89
github.com/stretchr/testify v1.9.0
910
github.com/tarantool/go-iproto v1.1.0

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
33
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
44
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
55
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
6+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
7+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
68
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
79
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
810
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=

test_helpers/main.go

+64-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ type StartOpts struct {
3333
// InitScript is a Lua script for tarantool to run on start.
3434
InitScript string
3535

36+
// ConfigFile is a path to a configuration file for a Tarantool instance.
37+
// Required in pair with InstanceName.
38+
ConfigFile string
39+
40+
// InstanceName is a name of an instance to run.
41+
// Required in pair with ConfigFile.
42+
InstanceName string
43+
3644
// Listen is box.cfg listen parameter for tarantool.
3745
// Use this address to connect to tarantool after configuration.
3846
// https://www.tarantool.io/en/doc/latest/reference/configuration/#cfg-basic-listen
@@ -77,6 +85,25 @@ type TarantoolInstance struct {
7785

7886
// Dialer to check that connection established.
7987
Dialer tarantool.Dialer
88+
89+
done chan error
90+
}
91+
92+
// WorkStatus checks if Tarantool instance is still running.
93+
// Return true if it is running, false if it is not.
94+
// If instance was exit and error is nil - process completed success with zero status code.
95+
func (t *TarantoolInstance) WorkStatus() (bool, error) {
96+
select {
97+
case err := <-t.done:
98+
return false, err
99+
default:
100+
return true, nil
101+
}
102+
}
103+
104+
func (t *TarantoolInstance) checkDone() {
105+
t.done = make(chan error, 1)
106+
t.done <- t.Cmd.Wait()
80107
}
81108

82109
func isReady(dialer tarantool.Dialer, opts *tarantool.Opts) error {
@@ -108,7 +135,7 @@ var (
108135
)
109136

110137
func init() {
111-
tarantoolVersionRegexp = regexp.MustCompile(`Tarantool (?:Enterprise )?(\d+)\.(\d+)\.(\d+).*`)
138+
tarantoolVersionRegexp = regexp.MustCompile(`Tarantool (Enterprise )?(\d+)\.(\d+)\.(\d+).*`)
112139
}
113140

114141
// atoiUint64 parses string to uint64.
@@ -145,15 +172,15 @@ func IsTarantoolVersionLess(majorMin uint64, minorMin uint64, patchMin uint64) (
145172
return true, fmt.Errorf("failed to parse output %q", out)
146173
}
147174

148-
if major, err = atoiUint64(parsed[1]); err != nil {
175+
if major, err = atoiUint64(parsed[2]); err != nil {
149176
return true, fmt.Errorf("failed to parse major from output %q: %w", out, err)
150177
}
151178

152-
if minor, err = atoiUint64(parsed[2]); err != nil {
179+
if minor, err = atoiUint64(parsed[3]); err != nil {
153180
return true, fmt.Errorf("failed to parse minor from output %q: %w", out, err)
154181
}
155182

156-
if patch, err = atoiUint64(parsed[3]); err != nil {
183+
if patch, err = atoiUint64(parsed[4]); err != nil {
157184
return true, fmt.Errorf("failed to parse patch from output %q: %w", out, err)
158185
}
159186

@@ -166,6 +193,21 @@ func IsTarantoolVersionLess(majorMin uint64, minorMin uint64, patchMin uint64) (
166193
}
167194
}
168195

196+
// IsTarantoolEE checks if Tarantool is Enterprise edition.
197+
func IsTarantoolEE() (bool, error) {
198+
out, err := exec.Command(getTarantoolExec(), "--version").Output()
199+
if err != nil {
200+
return true, err
201+
}
202+
203+
parsed := tarantoolVersionRegexp.FindStringSubmatch(string(out))
204+
if parsed == nil {
205+
return true, fmt.Errorf("failed to parse output %q", out)
206+
}
207+
208+
return parsed[1] != "", nil
209+
}
210+
169211
// RestartTarantool restarts a tarantool instance for tests
170212
// with specifies parameters (refer to StartOpts)
171213
// which were specified in inst parameter.
@@ -211,6 +253,7 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
211253
}
212254

213255
inst.Cmd = exec.Command(getTarantoolExec(), startOpts.InitScript)
256+
inst.Cmd.Dir = startOpts.WorkDir
214257

215258
inst.Cmd.Env = append(
216259
os.Environ(),
@@ -219,6 +262,11 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
219262
fmt.Sprintf("TEST_TNT_MEMTX_USE_MVCC_ENGINE=%t", startOpts.MemtxUseMvccEngine),
220263
fmt.Sprintf("TEST_TNT_AUTH_TYPE=%s", startOpts.Auth),
221264
)
265+
if startOpts.ConfigFile != "" && startOpts.InstanceName != "" {
266+
inst.Cmd.Env = append(inst.Cmd.Env, fmt.Sprintf("TT_CONFIG=%s", startOpts.ConfigFile))
267+
inst.Cmd.Env = append(inst.Cmd.Env,
268+
fmt.Sprintf("TT_INSTANCE_NAME=%s", startOpts.InstanceName))
269+
}
222270

223271
// Copy SSL certificates.
224272
if startOpts.SslCertsDir != "" {
@@ -242,6 +290,8 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
242290
// see https://github.com/tarantool/go-tarantool/issues/136
243291
time.Sleep(startOpts.WaitStart)
244292

293+
go inst.checkDone()
294+
245295
opts := tarantool.Opts{
246296
Timeout: 500 * time.Millisecond,
247297
SkipSchema: true,
@@ -261,6 +311,16 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
261311
}
262312
}
263313

314+
if err != nil {
315+
StopTarantool(inst)
316+
return TarantoolInstance{}, fmt.Errorf("failed to connect Tarantool: %w", err)
317+
}
318+
319+
working, err := inst.WorkStatus()
320+
if !working || err != nil {
321+
StopTarantool(inst)
322+
return TarantoolInstance{}, fmt.Errorf("unexpected terminated Tarantool: %w", err)
323+
}
264324
return inst, err
265325
}
266326

test_helpers/tcs/prepare.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package tcs
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"time"
9+
10+
"github.com/tarantool/go-tarantool/v2"
11+
"github.com/tarantool/go-tarantool/v2/test_helpers"
12+
)
13+
14+
const (
15+
waitTimeout = 500 * time.Millisecond
16+
connectRetry = 3
17+
)
18+
19+
//go:embed testdata/config.yaml
20+
var tcsConfig []byte
21+
22+
func makeOpts(port int) (test_helpers.StartOpts, error) {
23+
opts := test_helpers.StartOpts{}
24+
dir, err := os.MkdirTemp("", "tcs_dir")
25+
if err != nil {
26+
return opts, err
27+
}
28+
os.WriteFile(filepath.Join(dir, "config.yaml"), tcsConfig, 0644)
29+
30+
address := fmt.Sprintf("localhost:%d", port)
31+
32+
opts = test_helpers.StartOpts{
33+
ConfigFile: "config.yaml",
34+
WorkDir: dir,
35+
WaitStart: waitTimeout,
36+
ConnectRetry: connectRetry,
37+
RetryTimeout: waitTimeout,
38+
InstanceName: "master",
39+
Listen: address,
40+
Dialer: tarantool.NetDialer{
41+
Address: address,
42+
User: "client",
43+
Password: "secret",
44+
},
45+
}
46+
return opts, nil
47+
}

test_helpers/tcs/tcs.go

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package tcs
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"testing"
8+
9+
"github.com/tarantool/go-tarantool/v2"
10+
"github.com/tarantool/go-tarantool/v2/test_helpers"
11+
)
12+
13+
var ErrNotSupported = errors.New("required Tarantool EE 3.3+")
14+
15+
// TCS is a Tarantool centralized configuration storage connection.
16+
type TCS struct {
17+
inst test_helpers.TarantoolInstance
18+
conn *tarantool.Connection
19+
tb testing.TB
20+
}
21+
22+
// getResponse is a response of a get request.
23+
type getResponse struct {
24+
Data []struct {
25+
Path string
26+
Value string
27+
ModRevision int64 `mapstructure:"mod_revision"`
28+
}
29+
}
30+
31+
func isSupported() bool {
32+
if less, err := test_helpers.IsTarantoolEE(); less || err != nil {
33+
return false
34+
}
35+
if less, err := test_helpers.IsTarantoolVersionLess(3, 3, 0); less || err != nil {
36+
return false
37+
}
38+
return true
39+
40+
}
41+
42+
// Start starts a Tarantool centralized configuration storage.
43+
// Returns a Tcs instance and a cleanup function.
44+
func Start(port int) (TCS, error) {
45+
tcs := TCS{}
46+
if !isSupported() {
47+
return tcs, ErrNotSupported
48+
}
49+
opts, err := makeOpts(port)
50+
if err != nil {
51+
return tcs, err
52+
}
53+
54+
tcs.inst, err = test_helpers.StartTarantool(opts)
55+
if err != nil {
56+
return tcs, fmt.Errorf("failed to start Tarantool config storage: %w", err)
57+
}
58+
59+
tcs.conn, err = tarantool.Connect(context.Background(), tcs.inst.Dialer, tarantool.Opts{})
60+
if err != nil {
61+
return tcs, fmt.Errorf("failed to connect to Tarantool config storage: %w", err)
62+
}
63+
64+
return tcs, nil
65+
}
66+
67+
// Start starts a Tarantool centralized configuration storage.
68+
// Returns a Tcs instance and a cleanup function.
69+
func StartTesting(tb testing.TB, port int) TCS {
70+
tcs, err := Start(port)
71+
if err != nil {
72+
tb.Fatal(err)
73+
}
74+
return tcs
75+
}
76+
77+
// Doer return interface for interacting with Tarantool.
78+
func (t *TCS) Doer() tarantool.Doer {
79+
return t.conn
80+
}
81+
82+
// Stop stops the Tarantool centralized configuration storage.
83+
func (t *TCS) Stop() {
84+
if t.tb != nil {
85+
t.tb.Helper()
86+
}
87+
if t.conn != nil {
88+
t.conn.Close()
89+
}
90+
test_helpers.StopTarantoolWithCleanup(t.inst)
91+
}
92+
93+
// Put implements "config.storage.put" method.
94+
func (t *TCS) Put(ctx context.Context, path string, value string) error {
95+
if t.tb != nil {
96+
t.tb.Helper()
97+
}
98+
req := tarantool.NewCallRequest("config.storage.put").
99+
Args([]any{path, value}).
100+
Context(ctx)
101+
if _, err := t.conn.Do(req).GetResponse(); err != nil {
102+
return fmt.Errorf("failed save data to tarantool: %w", err)
103+
}
104+
return nil
105+
}
106+
107+
// Delete implements "config.storage.delete" method.
108+
func (t *TCS) Delete(ctx context.Context, path string) error {
109+
if t.tb != nil {
110+
t.tb.Helper()
111+
}
112+
req := tarantool.NewCallRequest("config.storage.delete").
113+
Args([]any{path}).
114+
Context(ctx)
115+
if _, err := t.conn.Do(req).GetResponse(); err != nil {
116+
return fmt.Errorf("failed delete data from tarantool: %w", err)
117+
}
118+
return nil
119+
}
120+
121+
// Get implements "config.storage.get" method.
122+
func (t *TCS) Get(ctx context.Context, path string) (string, error) {
123+
if t.tb != nil {
124+
t.tb.Helper()
125+
}
126+
req := tarantool.NewCallRequest("config.storage.get").
127+
Args([]any{path}).
128+
Context(ctx)
129+
130+
resp := []getResponse{}
131+
err := t.conn.Do(req).GetTyped(&resp)
132+
if err != nil {
133+
return "", fmt.Errorf("failed to fetch data from tarantool: %w", err)
134+
}
135+
136+
if len(resp) != 1 {
137+
return "", errors.New("unexpected response from tarantool")
138+
}
139+
140+
return resp[0].Data[0].Value, nil
141+
}

test_helpers/tcs/testdata/config.yaml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
credentials:
2+
users:
3+
replicator:
4+
password: 'topsecret'
5+
roles: [replication]
6+
client:
7+
password: 'secret'
8+
privileges:
9+
- permissions: [execute]
10+
universe: true
11+
- permissions: [read, write]
12+
spaces: [config_storage, config_storage_meta]
13+
14+
iproto:
15+
advertise:
16+
peer:
17+
login: replicator
18+
19+
replication:
20+
failover: election
21+
22+
database:
23+
use_mvcc_engine: true
24+
25+
groups:
26+
group-001:
27+
replicasets:
28+
replicaset-001:
29+
roles: [config.storage]
30+
roles_cfg:
31+
config_storage:
32+
status_check_interval: 3
33+
instances:
34+
master: {}

0 commit comments

Comments
 (0)