From 510da5f9cdc37daba42a88702abb11f2221cf082 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Fri, 14 Jan 2022 11:13:18 +0300 Subject: [PATCH] test: handle everything with go test Before this patch, it was required to set up test tarantool processes manually (and also handle their dependencies, like making working dir). You can see an example in CI scripts. This patch introduces go helpers for starting a tarantool process and installing rock requirements with tarantoolctl. Helpers are based on `os/exec` calls. Retries to connect test tarantool instance handled explicitly, see tarantool/go-tarantool#136. Setup scripts are reworked to use environment variables to configure `box.cfg`. Listen port is set in the end of script so it is possible to connect only if every other thing was set up already. Every test is reworked to start a tarantool process (or processes) in TestMain before test run. Now it is possible to run a test with plain `go test`. Queue tests changes may be broken because it is impossible to verify before tarantool/go-tarantool#115 is fixed. Closes #107 --- .github/workflows/reusable_testing.yml | 14 --- .github/workflows/testing.yml | 14 --- .gitignore | 4 +- config.lua | 13 ++- multi/config.lua | 19 ++++ multi/config_m1.lua | 14 --- multi/config_m2.lua | 14 --- multi/multi_test.go | 50 ++++++++++ queue/config.lua | 13 ++- queue/queue_test.go | 38 ++++++++ tarantool_test.go | 30 ++++++ test_helpers/main.go | 126 +++++++++++++++++++++++++ uuid/config.lua | 11 ++- uuid/uuid_test.go | 42 +++++++-- 14 files changed, 326 insertions(+), 76 deletions(-) create mode 100644 multi/config.lua delete mode 100644 multi/config_m1.lua delete mode 100644 multi/config_m2.lua create mode 100644 test_helpers/main.go diff --git a/.github/workflows/reusable_testing.yml b/.github/workflows/reusable_testing.yml index f0897a8d9..b84b95f17 100644 --- a/.github/workflows/reusable_testing.yml +++ b/.github/workflows/reusable_testing.yml @@ -40,35 +40,21 @@ jobs: - name: Run base tests run: | - mkdir snap xlog - TNT_PID=$(tarantool ./config.lua > tarantool.log 2>&1 & echo $!) go clean -testcache && go test -v - kill $TNT_PID # TODO(ylobankov): Uncomment this when tarantool/go-tarantool#115 is resolved. # - name: Run queue tests # working-directory: ./queue # run: | -# mkdir snap xlog -# tarantoolctl rocks install queue 1.1.0 -# TNT_PID=$(tarantool ./config.lua > tarantool.log 2>&1 & echo $!) # go clean -testcache && go test -v -# kill $TNT_PID - name: Run uuid tests working-directory: ./uuid run: | - mkdir snap xlog - TNT_PID=$(tarantool ./config.lua > tarantool.log 2>&1 & echo $!) go clean -testcache && go test -v - kill $TNT_PID if: ${{ !startsWith(env.TNT_VERSION, 'Tarantool 1.10') }} - name: Run multi tests working-directory: ./multi run: | - mkdir -p m1/{snap,xlog} m2/{snap,xlog} - TNT_PID_1=$(tarantool ./config_m1.lua > tarantool_m1.log 2>&1 & echo $!) - TNT_PID_2=$(tarantool ./config_m2.lua > tarantool_m2.log 2>&1 & echo $!) go clean -testcache && go test -v - kill $TNT_PID_1 $TNT_PID_2 diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8b894210a..0083d43bf 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -41,35 +41,21 @@ jobs: - name: Run base tests run: | - mkdir snap xlog - TNT_PID=$(tarantool ./config.lua > tarantool.log 2>&1 & echo $!) go clean -testcache && go test -v - kill $TNT_PID # TODO(ylobankov): Uncomment this when tarantool/go-tarantool#115 is resolved. # - name: Run queue tests # working-directory: ./queue # run: | -# mkdir snap xlog -# tarantoolctl rocks install queue 1.1.0 -# TNT_PID=$(tarantool ./config.lua > tarantool.log 2>&1 & echo $!) # go clean -testcache && go test -v -# kill $TNT_PID - name: Run uuid tests working-directory: ./uuid run: | - mkdir snap xlog - TNT_PID=$(tarantool ./config.lua > tarantool.log 2>&1 & echo $!) go clean -testcache && go test -v - kill $TNT_PID if: ${{ matrix.tarantool != 1.10 }} - name: Run multi tests working-directory: ./multi run: | - mkdir -p m1/{snap,xlog} m2/{snap,xlog} - TNT_PID_1=$(tarantool ./config_m1.lua > tarantool_m1.log 2>&1 & echo $!) - TNT_PID_2=$(tarantool ./config_m2.lua > tarantool_m2.log 2>&1 & echo $!) go clean -testcache && go test -v - kill $TNT_PID_1 $TNT_PID_2 diff --git a/.gitignore b/.gitignore index 8fcb790f1..8958050e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ *.DS_Store *.swp .idea/ -snap -xlog +work_dir* +.rocks diff --git a/config.lua b/config.lua index c2a9ae966..8ce372cc9 100644 --- a/config.lua +++ b/config.lua @@ -1,7 +1,7 @@ +-- Do not set listen for now so connector won't be +-- able to send requests until everything is configured. box.cfg{ - listen = 3013, - wal_dir='xlog', - snap_dir='snap', + work_dir = os.getenv("TARANTOOL_WORK_DIR"), } box.once("init", function() @@ -56,7 +56,10 @@ function simple_incr(a) end box.space.test:truncate() -local console = require 'console' -console.listen '0.0.0.0:33015' --box.schema.user.revoke('guest', 'read,write,execute', 'universe') + +-- Set listen only when every other thing is configured. +box.cfg{ + listen = os.getenv("TARANTOOL_LISTEN"), +} diff --git a/multi/config.lua b/multi/config.lua new file mode 100644 index 000000000..7913886cd --- /dev/null +++ b/multi/config.lua @@ -0,0 +1,19 @@ +local nodes_load = require("config_load_nodes") + +-- Do not set listen for now so connector won't be +-- able to send requests until everything is configured. +box.cfg{ + work_dir = os.getenv("TARANTOOL_WORK_DIR"), +} + +get_cluster_nodes = nodes_load.get_cluster_nodes + +box.once("init", function() + box.schema.user.create('test', { password = 'test' }) + box.schema.user.grant('test', 'read,write,execute', 'universe') +end) + +-- Set listen only when every other thing is configured. +box.cfg{ + listen = os.getenv("TARANTOOL_LISTEN"), +} diff --git a/multi/config_m1.lua b/multi/config_m1.lua deleted file mode 100644 index 5e364b5cc..000000000 --- a/multi/config_m1.lua +++ /dev/null @@ -1,14 +0,0 @@ -local nodes_load = require("config_load_nodes") - -box.cfg { - listen = 3013, - wal_dir = 'm1/xlog', - snap_dir = 'm1/snap', -} - -get_cluster_nodes = nodes_load.get_cluster_nodes - -box.once("init", function() - box.schema.user.create('test', { password = 'test' }) - box.schema.user.grant('test', 'read,write,execute', 'universe') -end) diff --git a/multi/config_m2.lua b/multi/config_m2.lua deleted file mode 100644 index cf73da319..000000000 --- a/multi/config_m2.lua +++ /dev/null @@ -1,14 +0,0 @@ -local nodes_load = require("config_load_nodes") - -box.cfg { - listen = 3014, - wal_dir = 'm2/xlog', - snap_dir = 'm2/snap', -} - -get_cluster_nodes = nodes_load.get_cluster_nodes - -box.once("init", function() - box.schema.user.create('test', { password = 'test' }) - box.schema.user.grant('test', 'read,write,execute', 'universe') -end) diff --git a/multi/multi_test.go b/multi/multi_test.go index 4515c6232..4741bbdf6 100644 --- a/multi/multi_test.go +++ b/multi/multi_test.go @@ -1,10 +1,14 @@ package multi import ( + "log" + "os" + "os/exec" "testing" "time" "github.com/tarantool/go-tarantool" + "github.com/tarantool/go-tarantool/test_helpers" ) var server1 = "127.0.0.1:3013" @@ -204,3 +208,49 @@ func TestRefresh(t *testing.T) { t.Error("Expect to get data after reconnect") } } + +// See https://stackoverflow.com/questions/27629380/how-to-exit-a-go-program-honoring-deferred-calls +// on issue with defer + os.Exit. +func runTestMain(m *testing.M) int { + var err error + var cmd1, cmd2 *exec.Cmd + + cmd1, err = test_helpers.StartTarantool(test_helpers.StartOpts{ + Command: "config.lua", + Server: server1, + WorkDir: "work_dir1", + User: connOpts.User, + Pass: connOpts.Pass, + WaitStart: 100 * time.Millisecond, + ConnectRetry: 3, + RetryTimeout: 200 * time.Millisecond, + }) + defer test_helpers.StopTarantool(cmd1) + + if err != nil { + log.Panic("Failed to prepare test tarantool: ", err) + } + + cmd2, err = test_helpers.StartTarantool(test_helpers.StartOpts{ + Command: "config.lua", + Server: server2, + WorkDir: "work_dir2", + User: connOpts.User, + Pass: connOpts.Pass, + WaitStart: 100 * time.Millisecond, + ConnectRetry: 3, + RetryTimeout: 200 * time.Millisecond, + }) + defer test_helpers.StopTarantool(cmd2) + + if err != nil { + log.Panic("Failed to prepare test tarantool: ", err) + } + + return m.Run() +} + +func TestMain(m *testing.M) { + code := runTestMain(m) + os.Exit(code) +} diff --git a/queue/config.lua b/queue/config.lua index 2488ff300..981e148c5 100644 --- a/queue/config.lua +++ b/queue/config.lua @@ -1,9 +1,9 @@ -queue = require 'queue' +queue = require('queue') +-- Do not set listen for now so connector won't be +-- able to send requests until everything is configured. box.cfg{ - listen = 3013, - wal_dir='xlog', - snap_dir='snap', + work_dir = os.getenv("TARANTOOL_WORK_DIR"), } box.once("init", function() @@ -27,3 +27,8 @@ box.schema.user.grant('test', 'read,write', 'space', '_queue_consumers') box.schema.user.grant('test', 'read,write', 'space', '_priv') box.schema.user.grant('test', 'read,write', 'space', '_queue_taken') end) + +-- Set listen only when every other thing is configured. +box.cfg{ + listen = os.getenv("TARANTOOL_LISTEN"), +} diff --git a/queue/queue_test.go b/queue/queue_test.go index d1a909120..03bd05083 100644 --- a/queue/queue_test.go +++ b/queue/queue_test.go @@ -2,12 +2,15 @@ package queue_test import ( "fmt" + "log" "math" + "os" "testing" "time" . "github.com/tarantool/go-tarantool" "github.com/tarantool/go-tarantool/queue" + "github.com/tarantool/go-tarantool/test_helpers" "gopkg.in/vmihailenco/msgpack.v2" ) @@ -818,3 +821,38 @@ func TestUtube_Put(t *testing.T) { t.Fatalf("Blocking time is less than expected: actual = %.2fs, expected = 1s", end.Sub(start).Seconds()) } } + +// See https://stackoverflow.com/questions/27629380/how-to-exit-a-go-program-honoring-deferred-calls +// on issue with defer + os.Exit. +func runTestMain(m *testing.M) int { + if err := os.RemoveAll(".rocks"); err != nil { + log.Panic(err) + } + + if err := test_helpers.InstallRock("queue", "1.1.0"); err != nil { + log.Panic(err) + } + + cmd, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Command: "config.lua", + Server: server, + WorkDir: "work_dir", + User: opts.User, + Pass: opts.Pass, + WaitStart: 100 * time.Millisecond, + ConnectRetry: 3, + RetryTimeout: 200 * time.Millisecond, + }) + defer test_helpers.StopTarantool(cmd) + + if err != nil { + log.Panic("Failed to prepare test tarantool: ", err) + } + + return m.Run() +} + +func TestMain(m *testing.M) { + code := runTestMain(m) + os.Exit(code) +} diff --git a/tarantool_test.go b/tarantool_test.go index ae725fdc7..504d9a76c 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -2,12 +2,15 @@ package tarantool_test import ( "fmt" + "log" + "os" "strings" "sync" "testing" "time" . "github.com/tarantool/go-tarantool" + "github.com/tarantool/go-tarantool/test_helpers" "gopkg.in/vmihailenco/msgpack.v2" ) @@ -1005,3 +1008,30 @@ func TestComplexStructs(t *testing.T) { return } } + +// See https://stackoverflow.com/questions/27629380/how-to-exit-a-go-program-honoring-deferred-calls +// on issue with defer + os.Exit. +func runTestMain(m *testing.M) int { + cmd, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Command: "config.lua", + Server: server, + WorkDir: "work_dir", + User: opts.User, + Pass: opts.Pass, + WaitStart: 100 * time.Millisecond, + ConnectRetry: 3, + RetryTimeout: 200 * time.Millisecond, + }) + defer test_helpers.StopTarantool(cmd) + + if err != nil { + log.Panic("Failed to prepare test tarantool: ", err) + } + + return m.Run() +} + +func TestMain(m *testing.M) { + code := runTestMain(m) + os.Exit(code) +} diff --git a/test_helpers/main.go b/test_helpers/main.go new file mode 100644 index 000000000..605a2b876 --- /dev/null +++ b/test_helpers/main.go @@ -0,0 +1,126 @@ +package test_helpers + +import ( + "errors" + "fmt" + "os" + "os/exec" + "time" + + "github.com/tarantool/go-tarantool" +) + +type StartOpts struct { + Command string + Server string + WorkDir string + User string + Pass string + WaitStart time.Duration + ConnectRetry uint + RetryTimeout time.Duration +} + +func InstallRock(rock string, version string) error { + cmd := exec.Command("tarantoolctl", "rocks", "install", rock, version) + + err := cmd.Run() + if err != nil { + return err + } + + return nil +} + +func isReady(server string, opts *tarantool.Opts) error { + var err error + var conn *tarantool.Connection + var resp *tarantool.Response + + conn, err = tarantool.Connect(server, *opts) + if err != nil { + return err + } + if conn == nil { + return errors.New("Conn is nil after connect") + } + defer conn.Close() + + resp, err = conn.Ping() + if err != nil { + return err + } + if resp == nil { + return errors.New("Response is nil after ping") + } + + return nil +} + +func StartTarantool(startOpts StartOpts) (*exec.Cmd, error) { + var err error + var cmd *exec.Cmd + + // Prepare tarantool command + cmd = exec.Command("tarantool", "config.lua") + + cmd.Env = append( + os.Environ(), + fmt.Sprintf("TARANTOOL_WORK_DIR=%s", startOpts.WorkDir), + fmt.Sprintf("TARANTOOL_LISTEN=%s", startOpts.Server), + ) + + // Clean up existing work_dir. + err = os.RemoveAll(startOpts.WorkDir) + if err != nil { + return cmd, err + } + + // Create work_dir. + err = os.Mkdir(startOpts.WorkDir, 0755) + if err != nil { + return cmd, err + } + + // Start tarantool. + err = cmd.Start() + if err != nil { + return cmd, err + } + + // Try to connect and ping tarantool. + // Using reconnect opts do not help on Connect, + // see https://github.com/tarantool/go-tarantool/issues/136 + time.Sleep(startOpts.WaitStart) + + opts := tarantool.Opts{ + Timeout: 500 * time.Millisecond, + User: startOpts.User, + Pass: startOpts.Pass, + SkipSchema: true, + } + + var i uint + for i = 0; i <= startOpts.ConnectRetry; i++ { + err = isReady(startOpts.Server, &opts) + + // Both connect and ping is ok. + if err == nil { + break + } + + if i != startOpts.ConnectRetry { + time.Sleep(startOpts.RetryTimeout) + } + } + + return cmd, err +} + +func StopTarantool(cmd *exec.Cmd) { + if cmd == nil || cmd.Process == nil { + return + } + + cmd.Process.Kill() +} diff --git a/uuid/config.lua b/uuid/config.lua index 34b5d26c4..f1826dca1 100644 --- a/uuid/config.lua +++ b/uuid/config.lua @@ -1,10 +1,10 @@ local uuid = require('uuid') local msgpack = require('msgpack') +-- Do not set listen for now so connector won't be +-- able to send requests until everything is configured. box.cfg{ - listen = 3013, - wal_dir = 'xlog', - snap_dir = 'snap', + work_dir = os.getenv("TARANTOOL_WORK_DIR"), } box.schema.user.create('test', { password = 'test' , if_not_exists = true }) @@ -29,3 +29,8 @@ s:truncate() box.schema.user.grant('test', 'read,write', 'space', 'testUUID', { if_not_exists = true }) s:insert({ uuid.fromstr("c8f0fa1f-da29-438c-a040-393f1126ad39") }) + +-- Set listen only when every other thing is configured. +box.cfg{ + listen = os.getenv("TARANTOOL_LISTEN"), +} diff --git a/uuid/uuid_test.go b/uuid/uuid_test.go index 61eb632d2..00db471ad 100644 --- a/uuid/uuid_test.go +++ b/uuid/uuid_test.go @@ -2,13 +2,16 @@ package uuid_test import ( "fmt" + "log" + "os" "testing" "time" + "github.com/google/uuid" . "github.com/tarantool/go-tarantool" - _ "github.com/tarantool/go-tarantool/uuid" + "github.com/tarantool/go-tarantool/test_helpers" + _ "github.com/tarantool/go-tarantool/uuid" "gopkg.in/vmihailenco/msgpack.v2" - "github.com/google/uuid" ) var server = "127.0.0.1:3013" @@ -98,7 +101,7 @@ func TestSelect(t *testing.T) { t.Errorf("Failed to prepare test uuid: %s", uuidErr) } - resp, errSel := conn.Select(space, index, 0, 1, IterEq, []interface{}{ id }) + resp, errSel := conn.Select(space, index, 0, 1, IterEq, []interface{}{id}) if errSel != nil { t.Errorf("UUID select failed: %s", errSel.Error()) } @@ -108,7 +111,7 @@ func TestSelect(t *testing.T) { tupleValueIsId(t, resp.Data, id) var tuples []TupleUUID - errTyp := conn.SelectTyped(space, index, 0, 1, IterEq, []interface{}{ id }, &tuples) + errTyp := conn.SelectTyped(space, index, 0, 1, IterEq, []interface{}{id}, &tuples) if errTyp != nil { t.Errorf("Failed to SelectTyped: %s", errTyp.Error()) } @@ -131,7 +134,7 @@ func TestReplace(t *testing.T) { t.Errorf("Failed to prepare test uuid: %s", uuidErr) } - respRep, errRep := conn.Replace(space, []interface{}{ id }) + respRep, errRep := conn.Replace(space, []interface{}{id}) if errRep != nil { t.Errorf("UUID replace failed: %s", errRep) } @@ -140,7 +143,7 @@ func TestReplace(t *testing.T) { } tupleValueIsId(t, respRep.Data, id) - respSel, errSel := conn.Select(space, index, 0, 1, IterEq, []interface{}{ id }) + respSel, errSel := conn.Select(space, index, 0, 1, IterEq, []interface{}{id}) if errSel != nil { t.Errorf("UUID select failed: %s", errSel) } @@ -149,3 +152,30 @@ func TestReplace(t *testing.T) { } tupleValueIsId(t, respSel.Data, id) } + +// See https://stackoverflow.com/questions/27629380/how-to-exit-a-go-program-honoring-deferred-calls +// on issue with defer + os.Exit. +func runTestMain(m *testing.M) int { + cmd, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Command: "config.lua", + Server: server, + WorkDir: "work_dir", + User: opts.User, + Pass: opts.Pass, + WaitStart: 100 * time.Millisecond, + ConnectRetry: 3, + RetryTimeout: 200 * time.Millisecond, + }) + defer test_helpers.StopTarantool(cmd) + + if err != nil { + log.Panic("Failed to prepare test tarantool: ", err) + } + + return m.Run() +} + +func TestMain(m *testing.M) { + code := runTestMain(m) + os.Exit(code) +}