From c8aad19a2fa3a8532755cc35308146fa378dbaa0 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 26 Mar 2021 10:29:07 +0300 Subject: [PATCH 1/6] test: add regression test for box-py/call.test.py Added test that covers fix in tarantool-python library: 8847b8c8bd1092d87601987d858e3cb9ff54f9f6 ("python3: make json.dumps compatible with Python 2") [1]. 1. https://github.com/tarantool/tarantool-python/pull/186/commits/8847b8c8bd1092d87601987d858e3cb9ff54f9f6 --- test/test-tarantool/call.result | 592 +++++++++++++++++++++++++++++++ test/test-tarantool/call.test.py | 214 +++++++++++ 2 files changed, 806 insertions(+) create mode 100644 test/test-tarantool/call.result create mode 100644 test/test-tarantool/call.test.py diff --git a/test/test-tarantool/call.result b/test/test-tarantool/call.result new file mode 100644 index 00000000..b8d621d2 --- /dev/null +++ b/test/test-tarantool/call.result @@ -0,0 +1,592 @@ +box.schema.user.create('test', { password = 'test' }) +--- +... +box.schema.user.grant('test', 'execute,read,write', 'universe') +--- +... +exp_notation = 1e123 +--- +... +function f1() return 'testing', 1, false, -1, 1.123, math.abs(exp_notation - 1e123) < 0.1, nil end +--- +... +f1() +--- +- testing +- 1 +- false +- -1 +- 1.123 +- true +- null +... +call f1 () +- 'testing' +- 1 +- False +- -1 +- 1.123 +- True +- None +f1=nil +--- +... +call f1 () +{ + "error": { + "code": "ER_NO_SUCH_PROC", + "reason": "Procedure 'f1' is not defined" + } +} +function f1() return f1 end +--- +... +call f1 () +{ + "error": { + "code": "ER_PROC_LUA", + "reason": "unsupported Lua type 'function'" + } +} +call box.error (33333, 'Hey!') +{ + "error": { + "code": "U", + "reason": "Unknown error" + } +} + +# A test case for Bug#103491 +# server CALL processing bug with name path longer than two +# https://bugs.launchpad.net/tarantool/+bug/1034912 + +f = function() return 'OK' end +--- +... +test = {} +--- +... +test.f = f +--- +... +test.test = {} +--- +... +test.test.f = f +--- +... +call f () +- 'OK' +call test.f () +- 'OK' +call test.test.f () +- 'OK' + +# Test for Bug #955226 +# Lua Numbers are passed back wrongly as strings +# + +function foo() return 1, 2, '1', '2' end +--- +... +call foo () +- 1 +- 2 +- '1' +- '2' +function f1(...) return {...} end +--- +... +function f2(...) return f1({...}) end +--- +... +call f1 ('test_', 'test_') +- ['test_', 'test_'] +call f2 ('test_', 'test_') +- [['test_', 'test_']] +call f1 () +- [] +call f2 () +- [[]] +function f3() return {{'hello'}, {'world'}} end +--- +... +call f3 () +- [['hello'], ['world']] +function f3() return {'hello', {'world'}} end +--- +... +call f3 () +- ['hello', ['world']] +function f3() return 'hello', {{'world'}, {'canada'}} end +--- +... +call f3 () +- 'hello' +- [['world'], ['canada']] +function f3() return {}, '123', {{}, {}} end +--- +... +call f3 () +- [] +- '123' +- [[], []] +function f3() return { {{'hello'}} } end +--- +... +call f3 () +- [[['hello']]] +function f3() return { box.tuple.new('hello'), {'world'} } end +--- +... +call f3 () +- [['hello'], ['world']] +function f3() return { {'world'}, box.tuple.new('hello') } end +--- +... +call f3 () +- [['world'], ['hello']] +function f3() return { { test={1,2,3} }, { test2={1,2,3} } } end +--- +... +call f3 () +- [{'test': [1, 2, 3]}, {'test2': [1, 2, 3]}] +call f1 ('jason',) +- ['jason'] +call f1 ('jason', 1, 'test', 2, 'stewart') +- ['jason', 1, 'test', 2, 'stewart'] +space = box.schema.space.create('tweedledum') +--- +... +index = space:create_index('primary', { type = 'hash' }) +--- +... +function myreplace(...) return space:replace{...} end +--- +... +function myinsert(...) return space:insert{...} end +--- +... +call myinsert (1, 'test box delete') +- [1, 'test box delete'] +call space:delete (1,) +- [1, 'test box delete'] +call myinsert (1, 'test box delete') +- [1, 'test box delete'] +call space:delete (1,) +- [1, 'test box delete'] +call space:delete (1,) + +call myinsert (2, 'test box delete') +- [2, 'test box delete'] +call space:delete (1,) + +call space:delete (2,) +- [2, 'test box delete'] +call space:delete (2,) + +space:delete{2} +--- +... +call myinsert (2, 'test box delete') +- [2, 'test box delete'] +call space:get (2,) +- [2, 'test box delete'] +space:delete{2} +--- +- [2, 'test box delete'] +... +call space:get (2,) + +call myinsert (2, 'test box.select()') +- [2, 'test box.select()'] +call space:get (2,) +- [2, 'test box.select()'] +call space:select (2,) +- [[2, 'test box.select()']] +space:get{2} +--- +- [2, 'test box.select()'] +... +space:select{2} +--- +- - [2, 'test box.select()'] +... +space:get{1} +--- +... +space:select{1} +--- +- [] +... +call myreplace (2, 'hello', 'world') +- [2, 'hello', 'world'] +call myreplace (2, 'goodbye', 'universe') +- [2, 'goodbye', 'universe'] +call space:get (2,) +- [2, 'goodbye', 'universe'] +call space:select (2,) +- [[2, 'goodbye', 'universe']] +space:get{2} +--- +- [2, 'goodbye', 'universe'] +... +space:select{2} +--- +- - [2, 'goodbye', 'universe'] +... +call myreplace (2,) +- [2] +call space:get (2,) +- [2] +call space:select (2,) +- [[2]] +call space:delete (2,) +- [2] +call space:delete (2,) + +call myinsert (3, 'old', 2) +- [3, 'old', 2] +space:update({3}, {{'=', 1, 4}, {'=', 2, 'new'}}) +--- +- error: Attempt to modify a tuple field which is part of index 'primary' in space + 'tweedledum' +... +space:insert(space:get{3}:update{{'=', 1, 4}, {'=', 2, 'new'}}) space:delete{3} +--- +... +call space:get (4,) +- [4, 'new', 2] +call space:select (4,) +- [[4, 'new', 2]] +space:update({4}, {{'+', 3, 1}}) +--- +- [4, 'new', 3] +... +space:update({4}, {{'-', 3, 1}}) +--- +- [4, 'new', 2] +... +call space:get (4,) +- [4, 'new', 2] +call space:select (4,) +- [[4, 'new', 2]] +function field_x(key, field_index) return space:get(key)[field_index] end +--- +... +call field_x (4, 1) +- 4 +call field_x (4, 2) +- 'new' +call space:delete (4,) +- [4, 'new', 2] +space:drop() +--- +... +space = box.schema.space.create('tweedledum') +--- +... +index = space:create_index('primary', { type = 'tree' }) +--- +... +eval (return 1)() +--- +- 1 +function f(...) return 1 end +--- +... +call f() +--- +- 1 +eval (return 1, 2, 3)() +--- +- 1 +- 2 +- 3 +function f(...) return 1, 2, 3 end +--- +... +call f() +--- +- 1 +- 2 +- 3 +eval (return true)() +--- +- true +function f(...) return true end +--- +... +call f() +--- +- true +eval (return nil)() +--- +- null +function f(...) return nil end +--- +... +call f() +--- +- null +eval (return )() +--- + +function f(...) return end +--- +... +call f() +--- + +eval (return {})() +--- +- [] +function f(...) return {} end +--- +... +call f() +--- +- [] +eval (return {1})() +--- +- [1] +function f(...) return {1} end +--- +... +call f() +--- +- [1] +eval (return {1, 2, 3})() +--- +- [1, 2, 3] +function f(...) return {1, 2, 3} end +--- +... +call f() +--- +- [1, 2, 3] +eval (return {k1 = 'v1', k2 = 'v2'})() +--- +- {"k1": "v1", "k2": "v2"} +function f(...) return {k1 = 'v1', k2 = 'v2'} end +--- +... +call f() +--- +- {"k1": "v1", "k2": "v2"} +eval (return {k1 = 'v1', k2 = 'v2'})() +--- +- {"k1": "v1", "k2": "v2"} +function f(...) return {k1 = 'v1', k2 = 'v2'} end +--- +... +call f() +--- +- {"k1": "v1", "k2": "v2"} +eval (return {s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}})() +--- +- {"c": {"106": [1, 1428578535], "2": [1, 1428578535]}, "pc": {"106": [1, 1428578535, 9243], "2": [1, 1428578535, 9243]}, "s": [1, 1428578535], "u": 1428578535, "v": []} +function f(...) return {s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}} end +--- +... +call f() +--- +- {"c": {"106": [1, 1428578535], "2": [1, 1428578535]}, "pc": {"106": [1, 1428578535, 9243], "2": [1, 1428578535, 9243]}, "s": [1, 1428578535], "u": 1428578535, "v": []} +eval (return true, {s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}})() +--- +- true +- {"c": {"106": [1, 1428578535], "2": [1, 1428578535]}, "pc": {"106": [1, 1428578535, 9243], "2": [1, 1428578535, 9243]}, "s": [1, 1428578535], "u": 1428578535, "v": []} +function f(...) return true, {s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}} end +--- +... +call f() +--- +- true +- {"c": {"106": [1, 1428578535], "2": [1, 1428578535]}, "pc": {"106": [1, 1428578535, 9243], "2": [1, 1428578535, 9243]}, "s": [1, 1428578535], "u": 1428578535, "v": []} +eval (return {s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}}, true)() +--- +- {"c": {"106": [1, 1428578535], "2": [1, 1428578535]}, "pc": {"106": [1, 1428578535, 9243], "2": [1, 1428578535, 9243]}, "s": [1, 1428578535], "u": 1428578535, "v": []} +- true +function f(...) return {s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}}, true end +--- +... +call f() +--- +- {"c": {"106": [1, 1428578535], "2": [1, 1428578535]}, "pc": {"106": [1, 1428578535, 9243], "2": [1, 1428578535, 9243]}, "s": [1, 1428578535], "u": 1428578535, "v": []} +- true +t = box.tuple.new('tuple', {1, 2, 3}, { k1 = 'v', k2 = 'v2'}) +--- +... +eval (return t)() +--- +- ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}] +function f(...) return t end +--- +... +call f() +--- +- ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}] +eval (return t, t, t)() +--- +- ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}] +- ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}] +- ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}] +function f(...) return t, t, t end +--- +... +call f() +--- +- ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}] +- ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}] +- ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}] +eval (return {t})() +--- +- [["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}]] +function f(...) return {t} end +--- +... +call f() +--- +- [["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}]] +eval (return {t, t, t})() +--- +- [["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}], ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}], ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}]] +function f(...) return {t, t, t} end +--- +... +call f() +--- +- [["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}], ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}], ["tuple", [1, 2, 3], {"k1": "v", "k2": "v2"}]] +eval (return error('exception'))() +--- +{ + "error": { + "code": "ER_PROC_LUA", + "reason": "exception" + } +} +function f(...) return error('exception') end +--- +... +call f() +--- +{ + "error": { + "code": "ER_PROC_LUA", + "reason": "exception" + } +} +eval (return box.error(0))() +--- + +function f(...) return box.error(0) end +--- +... +call f() +--- + +eval (return ...)() +--- + +function f(...) return ... end +--- +... +call f() +--- + +eval (return ...)(1, 2, 3) +--- +- 1 +- 2 +- 3 +function f(...) return ... end +--- +... +call f(1, 2, 3) +--- +- 1 +- 2 +- 3 +eval (return ...)(null, null, null) +--- +- null +- null +- null +function f(...) return ... end +--- +... +call f(null, null, null) +--- +- null +- null +- null +eval (return ...)({"k1": "v1", "k2": "v2"}) +--- +- {"k1": "v1", "k2": "v2"} +function f(...) return ... end +--- +... +call f({"k1": "v1", "k2": "v2"}) +--- +- {"k1": "v1", "k2": "v2"} +eval (return space:auto_increment({"transaction"}))() +--- +- [1, "transaction"] +function f(...) return space:auto_increment({"transaction"}) end +--- +... +call f() +--- +- [2, "transaction"] +eval (return space:select{})() +--- +- [[1, "transaction"], [2, "transaction"]] +function f(...) return space:select{} end +--- +... +call f() +--- +- [[1, "transaction"], [2, "transaction"]] +eval (return box.begin(), space:auto_increment({"failed"}), box.rollback())() +--- +- null +- [3, "failed"] +function f(...) return box.begin(), space:auto_increment({"failed"}), box.rollback() end +--- +... +call f() +--- +- null +- [3, "failed"] +eval (return space:select{})() +--- +- [[1, "transaction"], [2, "transaction"]] +function f(...) return space:select{} end +--- +... +call f() +--- +- [[1, "transaction"], [2, "transaction"]] +eval (return require("fiber").sleep(0))() +--- + +function f(...) return require("fiber").sleep(0) end +--- +... +call f() +--- + +eval (!invalid expression)() +--- +{ + "error": { + "code": "ER_PROC_LUA", + "reason": "eval:1: unexpected symbol near '!'" + } +} +space:drop() +--- +... +box.schema.user.drop('test') +--- +... diff --git a/test/test-tarantool/call.test.py b/test/test-tarantool/call.test.py new file mode 100644 index 00000000..eaf6a0d2 --- /dev/null +++ b/test/test-tarantool/call.test.py @@ -0,0 +1,214 @@ +""" +Tarantool's test box-py/call.test.py had a problem fixed in +tarantool-python library by commit: +8847b8c8bd1092d87601987d858e3cb9ff54f9f6 +("python3: make json.dumps compatible with Python 2"). +""" + +from __future__ import print_function + +import os +import sys +import json + +def call(name, *args): + return iproto.call(name, *args) + +admin("box.schema.user.create('test', { password = 'test' })") +admin("box.schema.user.grant('test', 'execute,read,write', 'universe')") +iproto.authenticate("test", "test") +# workaround for gh-770 centos 6 float representation +admin("exp_notation = 1e123") +admin("function f1() return 'testing', 1, false, -1, 1.123, math.abs(exp_notation - 1e123) < 0.1, nil end") +admin("f1()") +call("f1") +admin("f1=nil") +call("f1") +admin("function f1() return f1 end") +call("f1") + +# A test case for https://github.com/tarantool/tarantool/issues/44 +# IPROTO required! +call("box.error", 33333, "Hey!") + +print(""" +# A test case for Bug#103491 +# server CALL processing bug with name path longer than two +# https://bugs.launchpad.net/tarantool/+bug/1034912 +""") +admin("f = function() return 'OK' end") +admin("test = {}") +admin("test.f = f") +admin("test.test = {}") +admin("test.test.f = f") +call("f") +call("test.f") +call("test.test.f") + +print(""" +# Test for Bug #955226 +# Lua Numbers are passed back wrongly as strings +# +""") +admin("function foo() return 1, 2, '1', '2' end") +call("foo") + +# +# check how well we can return tables +# +admin("function f1(...) return {...} end") +admin("function f2(...) return f1({...}) end") +call("f1", "test_", "test_") +call("f2", "test_", "test_") +call("f1") +call("f2") +# +# check multi-tuple return +# +admin("function f3() return {{'hello'}, {'world'}} end") +call("f3") +admin("function f3() return {'hello', {'world'}} end") +call("f3") +admin("function f3() return 'hello', {{'world'}, {'canada'}} end") +call("f3") +admin("function f3() return {}, '123', {{}, {}} end") +call("f3") +admin("function f3() return { {{'hello'}} } end") +call("f3") +admin("function f3() return { box.tuple.new('hello'), {'world'} } end") +call("f3") +admin("function f3() return { {'world'}, box.tuple.new('hello') } end") +call("f3") +admin("function f3() return { { test={1,2,3} }, { test2={1,2,3} } } end") +call("f3") + +call("f1", "jason") +call("f1", "jason", 1, "test", 2, "stewart") + +admin("space = box.schema.space.create('tweedledum')") +admin("index = space:create_index('primary', { type = 'hash' })") + +admin("function myreplace(...) return space:replace{...} end") +admin("function myinsert(...) return space:insert{...} end") + +call("myinsert", 1, "test box delete") +call("space:delete", 1) +call("myinsert", 1, "test box delete") +call("space:delete", 1) +call("space:delete", 1) +call("myinsert", 2, "test box delete") +call("space:delete", 1) +call("space:delete", 2) +call("space:delete", 2) +admin("space:delete{2}") + +call("myinsert", 2, "test box delete") +call("space:get", 2) +admin("space:delete{2}") +call("space:get", 2) +call("myinsert", 2, "test box.select()") +call("space:get", 2) +call("space:select", 2) +admin("space:get{2}") +admin("space:select{2}") +admin("space:get{1}") +admin("space:select{1}") +call("myreplace", 2, "hello", "world") +call("myreplace", 2, "goodbye", "universe") +call("space:get", 2) +call("space:select", 2) +admin("space:get{2}") +admin("space:select{2}") +call("myreplace", 2) +call("space:get", 2) +call("space:select", 2) +call("space:delete", 2) +call("space:delete", 2) +call("myinsert", 3, "old", 2) +admin("space:update({3}, {{'=', 1, 4}, {'=', 2, 'new'}})") +admin("space:insert(space:get{3}:update{{'=', 1, 4}, {'=', 2, 'new'}}) space:delete{3}") +call("space:get", 4) +call("space:select", 4) +admin("space:update({4}, {{'+', 3, 1}})") +admin("space:update({4}, {{'-', 3, 1}})") +call("space:get", 4) +call("space:select", 4) +admin("function field_x(key, field_index) return space:get(key)[field_index] end") +call("field_x", 4, 1) +call("field_x", 4, 2) +call("space:delete", 4) +admin("space:drop()") + +admin("space = box.schema.space.create('tweedledum')") +admin("index = space:create_index('primary', { type = 'tree' })") + +json_dumps_kwargs=dict(sort_keys=True, separators=(', ', ': ')) + +def dump_args(*args): + return json.dumps(args, **json_dumps_kwargs)[1:-1] + +def dump_response(response): + if response.return_code: + return str(response) + if not response.data: + return '' + res = [] + for item in response.data: + res.append(json.dumps(item, **json_dumps_kwargs)) + return '- ' + '\n- '.join(res) + +def lua_eval(name, *args): + print("eval ({})({})".format(name, dump_args(*args))) + print("---") + print(dump_response(iproto.py_con.eval(name, args))) + +def lua_call(name, *args): + print("call {}({})".format(name, dump_args(*args))) + print("---") + print(dump_response(iproto.py_con.call(name, args))) + +def test(expr, *args): + lua_eval("return " + expr, *args) + admin("function f(...) return " + expr + " end") + lua_call("f", *args) + +# Return values +test("1") +test("1, 2, 3") +test("true") +test("nil") +test("") +test("{}") +test("{1}") +test("{1, 2, 3}") +test("{k1 = 'v1', k2 = 'v2'}") +test("{k1 = 'v1', k2 = 'v2'}") +# gh-791: maps are wrongly assumed to be arrays +test("{s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}}") +test("true, {s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}}") +test("{s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}}, true") +admin("t = box.tuple.new('tuple', {1, 2, 3}, { k1 = 'v', k2 = 'v2'})") +test("t") +test("t, t, t") +test("{t}") +test("{t, t, t}") +test("error('exception')") +test("box.error(0)") +test("...") +test("...", 1, 2, 3) +test("...", None, None, None) +test("...", { "k1": "v1", "k2": "v2"}) +# Transactions +test("space:auto_increment({\"transaction\"})") +test("space:select{}") +test("box.begin(), space:auto_increment({\"failed\"}), box.rollback()") +test("space:select{}") +test("require(\"fiber\").sleep(0)") +# Other +lua_eval("!invalid expression") + +admin("space:drop()") +admin("box.schema.user.drop('test')") + +# Re-connect after removing user +iproto.py_con.close() From bde7af212fca972d9413646d4987c5112cd00329 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Wed, 31 Mar 2021 15:52:06 +0300 Subject: [PATCH 2/6] lib: replace list with tuple for returned version Co-authored-by: Alexander Turenko --- lib/tarantool_server.py | 4 ++-- lib/utils.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/tarantool_server.py b/lib/tarantool_server.py index 8c8222ae..0f489e35 100644 --- a/lib/tarantool_server.py +++ b/lib/tarantool_server.py @@ -905,8 +905,8 @@ def start(self, silent=True, wait=True, wait_load=True, rais=True, args=[], # Verify that the schema actually was not upgraded. if self.disable_schema_upgrade: expected_version = extract_schema_from_snapshot(self.snapshot_path) - actual_version = yaml.safe_load(self.admin.execute( - 'box.space._schema:get{"version"}'))[0] + actual_version = tuple(yaml.safe_load(self.admin.execute( + 'box.space._schema:get{"version"}'))[0][1:]) if expected_version != actual_version: color_stdout('Schema version check fails: expected ' '{}, got {}\n'.format(expected_version, diff --git a/lib/utils.py b/lib/utils.py index a92db07c..f88ebb5f 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -303,7 +303,7 @@ def extract_schema_from_snapshot(snapshot_path): "BODY": {"space_id": 272, "tuple": ["version", 2, 3, 1]} } - :returns: [u'version', 2, 3, 1] + :returns: (2, 3, 1) """ BOX_SCHEMA_ID = 272 for row in xlog_rows(snapshot_path): @@ -311,7 +311,7 @@ def extract_schema_from_snapshot(snapshot_path): row['BODY']['space_id'] == BOX_SCHEMA_ID: res = row['BODY']['tuple'] if res[0] == 'version': - return res + return tuple(res[1:]) return None From f6a00e508a8c0ad63dbdbbfd351413d9d2ec088f Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Mon, 26 Apr 2021 12:02:44 +0300 Subject: [PATCH 3/6] lib: raise exception when path to snapshot is not found --- lib/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/utils.py b/lib/utils.py index f88ebb5f..cb8e4f58 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -1,3 +1,4 @@ +import errno import os import sys import collections @@ -24,6 +25,9 @@ PY3 = sys.version_info[0] == 3 PY2 = sys.version_info[0] == 2 +if PY2: + FileNotFoundError = IOError + if PY3: string_types = str, integer_types = int, @@ -283,6 +287,8 @@ def xlog_rows(xlog_path): Assume tarantool and tarantoolctl is in PATH. """ + if not os.path.exists(xlog_path): + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), xlog_path) cmd = ['tarantoolctl', 'cat', xlog_path, '--format=json', '--show-system'] with open(os.devnull, 'w') as devnull: process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=devnull) From 90c05b97e9056bb5f0e2763c5794800e9695fa6f Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 26 Mar 2021 10:45:12 +0300 Subject: [PATCH 4/6] test: add unit tests Tests written using unittest [1] and hypothesis [2] modules. We use hypothesis v4.57.0 because it is a latest version with Python 2.7 support [3]. How-to run: $ make test_unittest 1. https://docs.python.org/3/library/unittest.html 2. https://hypothesis.readthedocs.io/en/latest/ 3. https://hypothesis.readthedocs.io/en/latest/changes.html#v4-57-0 --- .gitignore | 1 + Makefile | 7 ++++-- requirements-test.txt | 1 + test/unittest/00000000000000000003.snap | Bin 0 -> 6077 bytes test/unittest/test_lib_utils.py | 32 ++++++++++++++++++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 test/unittest/00000000000000000003.snap create mode 100644 test/unittest/test_lib_utils.py diff --git a/.gitignore b/.gitignore index c494f6c5..c4e42632 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ htmlcov/ .coverage .coverage.* .cache +.hypothesis/ nosetests.xml coverage.xml *,cover diff --git a/Makefile b/Makefile index 5fb4c2a1..32ee6910 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,9 @@ luacheck: test_integration: $(PYTHON) test/test-run.py --force $(TEST_RUN_EXTRA_PARAMS) -test: test_integration +test_unittest: + $(PYTHON) -m unittest discover test/unittest/ -.PHONY: lint flake8 luacheck test test_integration +test: test_unittest test_integration + +.PHONY: lint flake8 luacheck test test_integration test_unittest diff --git a/requirements-test.txt b/requirements-test.txt index 5c71e7df..bfe1c3f6 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1 +1,2 @@ flake8==3.7.9 +hypothesis==4.* diff --git a/test/unittest/00000000000000000003.snap b/test/unittest/00000000000000000003.snap new file mode 100644 index 0000000000000000000000000000000000000000..0b2e62ebc1dfad730bb92c16be8134540ff19aa2 GIT binary patch literal 6077 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 Date: Fri, 30 Apr 2021 11:18:59 +0300 Subject: [PATCH 5/6] lib: use bytes_to_str() in xlog_rows() Since Python 3.6+ in json.loads() object that should be deserialized can be of type bytes or bytearray. The input encoding should be UTF-8, UTF-16 or UTF-32 [1]. Patch follows up commit 395edeb6b743c4479a62dd2183062124973d2b2a ('python3: decouple bytes and strings') and it is a part of task with switching to Python 3. 1. https://docs.python.org/3/library/json.html Follows up: #20 --- lib/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.py b/lib/utils.py index cb8e4f58..54780b9b 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -293,7 +293,7 @@ def xlog_rows(xlog_path): with open(os.devnull, 'w') as devnull: process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=devnull) for line in process.stdout.readlines(): - yield json.loads(line) + yield json.loads(bytes_to_str(line)) def extract_schema_from_snapshot(snapshot_path): From 394472d5447be8eee69c248ac1d3020276452386 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Sat, 3 Apr 2021 10:55:51 +0300 Subject: [PATCH 6/6] make: add target to make a code coverage report Patch introduces integration with coverage.py module [1] that allows to gather code coverage and build a simple report about covered statements and branches. We have a test-run.py module itself and also we have Python helpers for Tarantool tests written in Python. These Python tests runs a separate processes using subprocess module. To make it possible to gather code coverage for a separate processes option 'concurrency' added to .coveragerc. Additionally patch introduces integration with Coveralls service using python-coveralls module [2] that allows to evaluate incremental code coverage changes like we do it for Tarantool [3]. Code coverage for test-run is available on a public page by URL [4]. Name Stmts Miss Branch BrPart Cover ----------------------------------------------------------------- dispatcher.py 281 50 94 12 78.1% lib/__init__.py 45 0 6 1 98.0% lib/admin_connection.py 71 9 22 7 82.8% lib/app_server.py 162 45 38 11 67.0% lib/box_connection.py 54 22 12 1 50.0% lib/colorer.py 111 28 42 6 68.6% lib/connpool.py 85 54 18 1 35.0% lib/inspector.py 81 13 26 4 80.4% lib/options.py 81 15 18 6 78.8% lib/preprocessor.py 329 162 162 40 44.6% lib/pytap13.py 128 48 58 18 55.9% lib/server.py 111 23 40 12 74.2% lib/server_mixins.py 126 83 28 0 27.9% lib/tarantool_connection.py 126 28 22 5 75.0% lib/tarantool_server.py 729 237 268 65 61.5% lib/test.py 249 111 86 17 48.1% lib/test_suite.py 191 35 68 19 77.6% lib/unittest_server.py 63 6 10 4 86.3% lib/utils.py 216 76 86 19 59.9% lib/worker.py 251 81 70 15 60.7% listeners.py 205 78 70 6 60.0% test-run.py 149 76 44 9 42.5% ----------------------------------------------------------------- TOTAL 3844 1280 1288 278 61.2% 1. https://coverage.readthedocs.io/en/latest/config.html 2. https://coveralls-python.readthedocs.io/en/latest/usage/configuration.html#github-actions-support 3. https://coveralls.io/github/tarantool/tarantool 4. https://coveralls.io/github/tarantool/test-run --- .coveragerc | 14 ++++++++++++++ .github/workflows/test.yml | 10 ++++++++++ Makefile | 12 +++++++++++- README.md | 2 ++ requirements-test.txt | 1 + 5 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..115443a4 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,14 @@ +[run] +branch = True +concurrency = + gevent + multiprocessing + +[report] +precision = 1 +include = + ./* +omit = + lib/tarantool-python/* + lib/msgpack-python/* + test/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c1ad3718..067abfa7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,3 +58,13 @@ jobs: - name: run regression tests run: | make test + - name: code coverage + if: ${{ matrix.python-version == '3.8' && matrix.tarantool-version == '2.7' }} + run: | + pip install coveralls==3.* + make coverage + - name: upload coverage data to coveralls.io + if: ${{ matrix.python-version == '3.8' && matrix.tarantool-version == '2.7' }} + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Makefile b/Makefile index 32ee6910..39bc1df6 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +PROJECT_DIR := $(patsubst %/,%,$(dir $(MAKEFILE_PATH))) TEST_RUN_EXTRA_PARAMS?= PYTHON?=python @@ -13,11 +15,19 @@ luacheck: luacheck --config .luacheckrc . test_integration: - $(PYTHON) test/test-run.py --force $(TEST_RUN_EXTRA_PARAMS) + PYTHONPATH=$(PROJECT_DIR) $(PYTHON) test/test-run.py --force $(TEST_RUN_EXTRA_PARAMS) test_unittest: $(PYTHON) -m unittest discover test/unittest/ test: test_unittest test_integration +coverage: + PYTHON="coverage run" make -f $(MAKEFILE_PATH) test + coverage combine $(PROJECT_DIR) $(PROJECT_DIR)/test + coverage report + +clean: + coverage erase + .PHONY: lint flake8 luacheck test test_integration test_unittest diff --git a/README.md b/README.md index 4566579b..cf77b6f6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Tarantool Functional testing framework +[![Coverage Status](https://coveralls.io/repos/github/tarantool/test-run/badge.svg)](https://coveralls.io/github/tarantool/test-run) + ### Test Suite Bunch of tests, that lay down in the subfolder (recursively) with `suite.ini` diff --git a/requirements-test.txt b/requirements-test.txt index bfe1c3f6..d5e80422 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,2 +1,3 @@ +coverage==5.* flake8==3.7.9 hypothesis==4.*