Skip to content

Commit e2e32ab

Browse files
committed
Add Windows CI (AppVeyor)
Rewrote admin_connection.py to reconnect on a sendall() error, because checking of a socket liveness does not work under Windows. Fixes #126.
1 parent aa51b0f commit e2e32ab

File tree

8 files changed

+581
-75
lines changed

8 files changed

+581
-75
lines changed

README.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,25 @@ NOTE
8181

8282
This driver is synchronous, so connection mustn't be shared between threads/processes.
8383

84+
Run tests
85+
^^^^^^^^^
86+
87+
On Linux:
88+
89+
.. code-block:: console
90+
$ python setup.py test
91+
92+
On Windows:
93+
94+
* Setup a Linux machine with installed tarantool (called ``remote`` later).
95+
* (on ``remote``) Copy ``unit/suites/lib/tarantool_python_ci.lua`` to
96+
``/etc/tarantool/instances.available``.
97+
* (on ``remote``) Run ``tarantoolctl start tarantool_python_ci``.
98+
* Set the following environment variables:
99+
* ``REMOTE_TARANTOOL_HOST=...``,
100+
* ``REMOTE_TARANTOOL_CONSOLE_PORT=3302``.
101+
* Run ``python setup.py test``.
102+
84103
.. _`Tarantool`:
85104
.. _`Tarantool Database`:
86105
.. _`Tarantool Homepage`: http://tarantool.org

appveyor.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
environment:
2+
matrix:
3+
- PYTHON: "C:\\Python27"
4+
- PYTHON: "C:\\Python27-x64"
5+
- PYTHON: "C:\\Python34"
6+
- PYTHON: "C:\\Python34-x64"
7+
- PYTHON: "C:\\Python35"
8+
- PYTHON: "C:\\Python35-x64"
9+
- PYTHON: "C:\\Python36"
10+
- PYTHON: "C:\\Python36-x64"
11+
- PYTHON: "C:\\Python37"
12+
- PYTHON: "C:\\Python37-x64"
13+
14+
install:
15+
# install runtime dependencies
16+
- "%PYTHON%\\python.exe -m pip install -r requirements.txt"
17+
# install testing dependencies
18+
- "%PYTHON%\\python.exe -m pip install pyyaml"
19+
20+
build: off
21+
22+
test_script:
23+
- "%PYTHON%\\python.exe setup.py test"
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from __future__ import print_function
2+
3+
import sys
4+
import os
5+
import random
6+
import string
7+
import time
8+
9+
from .tarantool_admin import TarantoolAdmin
10+
11+
12+
# a time during which try to acquire a lock
13+
AWAIT_TIME = 60 # seconds
14+
15+
# on which port bind a socket for binary protocol
16+
BINARY_PORT = 3301
17+
18+
19+
def get_random_string():
20+
return ''.join(random.choice(string.ascii_lowercase) for _ in range(16))
21+
22+
23+
class RemoteTarantoolServer(object):
24+
def __init__(self):
25+
self.host = os.environ['REMOTE_TARANTOOL_HOST']
26+
27+
self.args = {}
28+
self.args['primary'] = BINARY_PORT
29+
self.args['admin'] = os.environ['REMOTE_TARANTOOL_CONSOLE_PORT']
30+
31+
assert(self.args['primary'] != self.args['admin'])
32+
33+
# a name to using for a lock
34+
self.whoami = get_random_string()
35+
36+
self.admin = TarantoolAdmin(self.host, self.args['admin'])
37+
self.lock_is_acquired = False
38+
39+
# emulate stopped server
40+
self.acquire_lock()
41+
self.admin.execute('box.cfg{listen = box.NULL}')
42+
43+
def acquire_lock(self):
44+
deadline = time.time() + AWAIT_TIME
45+
while True:
46+
res = self.admin.execute('return acquire_lock("%s")' % self.whoami)
47+
ok = res[0]
48+
err = res[1] if not ok else None
49+
if ok:
50+
break
51+
if time.time() > deadline:
52+
raise RuntimeError('can not acquire "%s" lock: %s' % (
53+
self.whoami, str(err)))
54+
print('waiting to acquire "%s" lock' % self.whoami,
55+
file=sys.stderr)
56+
time.sleep(1)
57+
self.lock_is_acquired = True
58+
59+
def touch_lock(self):
60+
assert(self.lock_is_acquired)
61+
res = self.admin.execute('return touch_lock("%s")' % self.whoami)
62+
ok = res[0]
63+
err = res[1] if not ok else None
64+
if not ok:
65+
raise RuntimeError('can not update "%s" lock: %s' % (
66+
self.whoami, str(err)))
67+
68+
def release_lock(self):
69+
res = self.admin.execute('return release_lock("%s")' % self.whoami)
70+
ok = res[0]
71+
err = res[1] if not ok else None
72+
if not ok:
73+
raise RuntimeError('can not release "%s" lock: %s' % (
74+
self.whoami, str(err)))
75+
self.lock_is_acquired = False
76+
77+
def start(self):
78+
if not self.lock_is_acquired:
79+
self.acquire_lock()
80+
self.admin.execute('box.cfg{listen = "0.0.0.0:%s"}' %
81+
self.args['primary'])
82+
83+
def stop(self):
84+
self.admin.execute('box.cfg{listen = box.NULL}')
85+
self.release_lock()
86+
87+
def is_started(self):
88+
return self.lock_is_acquired
89+
90+
def clean(self):
91+
pass
92+
93+
def __del__(self):
94+
self.admin.disconnect()

unit/suites/lib/tarantool_admin.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import socket
2+
import yaml
3+
4+
5+
class TarantoolAdmin(object):
6+
def __init__(self, host, port):
7+
self.host = host
8+
self.port = port
9+
self.is_connected = False
10+
self.socket = None
11+
12+
def connect(self):
13+
self.socket = socket.create_connection((self.host, self.port))
14+
self.socket.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
15+
self.is_connected = True
16+
self.socket.recv(256) # skip greeting
17+
18+
def disconnect(self):
19+
if self.is_connected:
20+
self.socket.close()
21+
self.socket = None
22+
self.is_connected = False
23+
24+
def reconnect(self):
25+
self.disconnect()
26+
self.connect()
27+
28+
def __enter__(self):
29+
self.connect()
30+
return self
31+
32+
def __exit__(self, type, value, tb):
33+
self.disconnect()
34+
35+
def __call__(self, command):
36+
return self.execute(command)
37+
38+
def execute(self, command):
39+
if not command:
40+
return
41+
42+
if not self.is_connected:
43+
self.connect()
44+
45+
cmd = (command.replace('\n', ' ') + '\n').encode()
46+
try:
47+
self.socket.sendall(cmd)
48+
except socket.error:
49+
# reconnect and try again
50+
self.reconnect()
51+
self.socket.sendall(cmd)
52+
53+
bufsiz = 4096
54+
res = ""
55+
56+
while True:
57+
buf = self.socket.recv(bufsiz)
58+
if not buf:
59+
break
60+
res = res + buf.decode()
61+
if (res.rfind("\n...\n") >= 0 or res.rfind("\r\n...\r\n") >= 0):
62+
break
63+
64+
return yaml.load(res)

0 commit comments

Comments
 (0)