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/.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..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,8 +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: test_integration +test_unittest: + $(PYTHON) -m unittest discover test/unittest/ -.PHONY: lint flake8 luacheck test test_integration +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/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..54780b9b 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,11 +287,13 @@ 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) for line in process.stdout.readlines(): - yield json.loads(line) + yield json.loads(bytes_to_str(line)) def extract_schema_from_snapshot(snapshot_path): @@ -303,7 +309,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 +317,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 diff --git a/requirements-test.txt b/requirements-test.txt index 5c71e7df..d5e80422 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1 +1,3 @@ +coverage==5.* flake8==3.7.9 +hypothesis==4.* 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() diff --git a/test/unittest/00000000000000000003.snap b/test/unittest/00000000000000000003.snap new file mode 100644 index 00000000..0b2e62eb Binary files /dev/null and b/test/unittest/00000000000000000003.snap differ diff --git a/test/unittest/test_lib_utils.py b/test/unittest/test_lib_utils.py new file mode 100644 index 00000000..8fe1ff72 --- /dev/null +++ b/test/unittest/test_lib_utils.py @@ -0,0 +1,32 @@ +import socket +import unittest + +from hypothesis import given, settings +from hypothesis.strategies import integers + +import lib.utils as utils + +class TestUtils(unittest.TestCase): + def test_extract_schema_from_snapshot(self): + snapshot_path = 'test/unittest/00000000000000000003.snap' + v = utils.extract_schema_from_snapshot(snapshot_path) + self.assertEqual(v, (2, 3, 1)) + + @settings(max_examples=5) + @given(port=integers(65100, 65535)) + def test_check_port(self, port): + def open_socket(p): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('localhost', p)) + s.listen(0) + return s + status = utils.check_port(port, rais=False, ipv4=True, ipv6=False) + self.assertEqual(status, True) + s = open_socket(port) + status = utils.check_port(port, rais=False, ipv4=True, ipv6=False) + s.close() + self.assertEqual(status, False) + + +if __name__ == "__main__": + unittest.main()